下一节地址:https://blog.csdn.net/qq_40515692/article/details/102788157
OpenCv专栏:https://blog.csdn.net/qq_40515692/article/details/102885061

超像素SuperPixel),就是把原本多个像素点,组合成一个大的像素。比如,原本的图片有二十多万个像素,用超像素处理之后,就只有几千个像素了。后面做直方图等处理就会方便许多。经常作为图像处理的预处理步骤。

这一节讲的是用C++实现超像素,下一节讲在超像素基础上用Kmeans分类进行分割,代码先根据超像素SLIC算法编写,后参考github的代码优化了一些地方,然后根据老师说的有更改了一些地方,欢迎大家一起讨论。

题目如下:

简单解法(HSV 直方图阈值)如下(至于为什么不用Matlab了,因为作为C系程序员,写c++真滴好爽呀):
https://blog.csdn.net/qq_40515692/article/details/102749271

这一节先讲SLIC超像素算法,下一节讲在超像素基础上用Kmeans分类进行分割,参考博客如下:
https://www.jianshu.com/p/d0ef931b3ddf
https://blog.csdn.net/duyue3052/article/details/82149877

效果如下(虽然还是有些可以更好的地方,但是可以看到已经分得很不错了,当然还有缺陷更少的算法可以更加好的分割比如像素点较少的蓝色线等的算法),完整代码附在下一节了:

一、分析

在写较复杂的程序时,前期的百度、google参考别人思路、考虑算法的步骤十分关键,甚至应该用一半实现代码以上的时间。

  • 为什么使用超像素?

如果看了之前的颜色阈值分割程序就会发现,之前的阈值分割没有考虑更高维的颜色数据,更重要的是没有利用各个像素点的位置信息(比如相邻的像素点更有可能属于一张分割图片),所以我们使用超像素算法,在保留图像像素的位置、颜色信息的同时,简化问题。

  • 超像素示意图:

    这里贴出SLIC的步骤:
  1. 撒种子。将K个超像素中心分布到图像的像素点上。(这里我的实现里面直接先根据图像大小和超像素的数目,均匀发布)

  2. 微调种子的位置。以K为中心的3×3范围内,移动超像素中心到这9个点中梯度最小的点上。这样是为了避免超像素点落到噪点或者边界上。(这里我也进行了实现,但是对于最终结果貌似没有太大影响,篇幅有限就不进行讲解)

  3. 初始化数据。取一个数组label保存每一个像素点属于哪个超像素。dis数组保存像素点到它属于的那个超像素中心的距离。

  4. 对每一个超像素中心x,它2S范围内的点:如果点到超像素中心x的距离(5维,马上会讲)小于这个点到它原来属于的超像素中心的距离,那么说明这个点属于超像素x。更新dis,更新label。

  5. 对每一个超像素中心,重新计算它的位置(根据的是属于该超像素的所有像素的位置中心)以及其LAB值(马上会讲)。

    重复4 5 两步。

其中关键的4,5步其实用到了kmeans算法的思想,如下图所示,假设有两个超像素点(红点、蓝点),一系列像素点(绿色),首先对每个像素点计算应该归属与哪一个超像素点、分类(如图片b、c所示)。

然后进行第五步计算中心,让超像素移动到中心,不断重复,最终成功划分。

但是应注意实际的SLIC算法和kmeans算法有区别,为了加快计算速度,在进行第4步时只计算了超像素中心有限范围内的点。(这是我实现算法时的理解,如果有误希望指出)

需要注意的是这里的“距离”可以是多维的数据距离,而不一定是比如像素之间的row、col之间的距离(比如RGB的欧式距离等)。

  • 然后就需要考虑如何针对一张图像,度量”距离“?

这里先简单介绍LAB色彩空间。Lab色彩模型是由亮度(L)和有关色彩的a, b三个要素组成。L表示亮度(Luminosity),L的值域由0(黑色)到100(白色)。a表示从洋红色至绿色的范围(a为负值指示绿色而正值指示品红),b表示从黄色至蓝色的范围(b为负值指示蓝色而正值指示黄色)。

Lab色彩模型的绝妙之处还在于它弥补了RGB色彩模型色彩分布不均的不足,因为RGB模型在蓝色到绿色之间的过渡色彩过多,而在绿色到红色之间又缺少黄色和其他色彩。如果我们想在数字图形的处理中保留尽量宽阔的色域和丰富的色彩,最好选择Lab。

然后就是如何计算”距离“,距离计算方法如下,其中,dc代表颜色距离,ds代表空间距离,Ns是类内最大空间距离,Nc为最大的颜色距离:

二、读取图片,完成大致框架

第一步还是先包含头文件,还有定义需要用到的变量,需要配置opencv,在VS上的配置可以参考:
https://blog.csdn.net/qq_40515692/article/details/81042303

#include <opencv2/opencv.hpp>
#include <iostream>  using namespace cv;
using namespace std;#define sqrtK 128       // 超像素个数128*128
#define sqrtN 512       // 图像格式512*512int label[sqrtN][sqrtN];      // 图像各像素点归属
int dis[sqrtN][sqrtN];          // 图像各像素点距离struct cluster{int row, col, l, a, b;
};
cluster clusters[sqrtK*sqrtK];      // 存储超像素的像素坐标

我们先定义好大致框架,首先是读取图片,转换为LAB色彩空间,然后把上面提到的步骤分步定义为函数。

int main(){// 注意修改文件位置Mat src = imread("C:\\Users\\ttp\\Desktop\\map.bmp"), lab;// resize图片并滤波resize(src, src, Size(sqrtN, sqrtN));// GaussianBlur(src, src, Size(3, 3), 1, 1);// 得到Lab色彩空间,需要注意的是:// 1.opencv里面默认为BGR排列方式// 2.LAB通道范围取决于转换前的通道范围,这样其实也方便处理//   例如:开始是0-255,转换后也是0-255,而不是LAB规定的[127,-128]cvtColor(src, lab, CV_BGR2Lab);int N = sqrtN * sqrtN;           // 像素总数 512*512int K = sqrtK * sqrtK;          // 超像素个数 128*128int S = sqrt(N / K);           // 相邻种子点距离(超像素边长) 4// 1.初始化像素init_clusters(lab,S);cout << "1-初始化像素-完成\n";// 2.微调种子的位置 貌似好一点,没有太大区别// 所以这里就直接注释了// move_clusters(lab);// cout << "2-微调种子的位置-完成\n";for (int i = 0; i < 5; i++) {// 3.4.初始化数据update_pixel(lab, 2*S);cout << "3-初始化数据-完成\n";// 5.让超像素位于正中间updaye_clusters(lab);cout << "4-让超像素位于正中间-完成\n";// -------------------这两个函数主要是帮助显示结果的// 6.标识超像素draw_clusters(src.clone());cout << "5-标识超像素-完成\n";// 7.绘制超像素结果图final_draw(lab, lab.clone());cout << "6-绘制超像素结果图-完成\n";// opencv的函数,每1000ms更新一下,动态显示图片waitKey(1000);// -----------------------------------------------}imshow("原图", src);waitKey(0);
}

三、各函数实现

1.init_clusters函数

init_clusters函数就是我们的第一步了,传入的参数为lab的色彩空间和S。

需要注意的是opencv里面Mat类的赋值并不是直接把Mat的数据全部拷贝一份赋值。
而是类似于C++的引用赋值(比如:Mat a,b; b=a; 改变b也会改变a)。
如果想赋值得到一个全新的图像矩阵,可以使用b=a.clone();这种方式。
所以这里就直接传lab了,效率应该不会低。

fill函数用于对一段空间赋值,这里即将矩阵dis赋-1。(在非opencv程序也可以使用)

void init_clusters(const Mat lab,int S) {// 初始化每一个超像素的坐标for (int i = 0; i < sqrtK; i++) {int temp_row = S / 2 + i * S;for (int j = 0; j < sqrtK; j++) {clusters[i * sqrtK + j].row = temp_row;clusters[i * sqrtK + j].col = S / 2 + j * S;// cout << clusters[i * sqrtK + j].row << "\t" << clusters[i * sqrtK + j].col // << "\t" << clusters[i * sqrtK + j].h << endl;}}// 初始化每一个像素的label(即属于哪一个超像素)for (int i = 0; i < sqrtN; i++) {int cluster_row = i / S;for (int j = 0; j < sqrtN; j++) {label[i][j] = cluster_row * sqrtK + j / S;// cout << cluster_row * sqrtK + j / S << endl;}}// 像素与超像素的距离先假设为-1fill(dis[0], dis[0] + (sqrtN * sqrtN), -1);
}

2.update_pixel函数

首先我们还是实现距离计算函数吧,这个函数传入参数为lab,clusters_index表示超像素的索引,
i,j表示像素的横纵坐标。

lab.at(row,col)属于opencv里面的写法,用于访问矩阵lab在坐标(row,col)的值
Vec3b表示3通道,每个通道为uchar类型(0-255)。为什么是Vec3b,参考完成大致框架里面的代码注释。

代码和上面的公式几乎没区别(权重取得有点随意)。

inline int get_distance(const Mat lab,int clusters_index,int i,int j) {int dl = clusters[clusters_index].l - lab.at<Vec3b>(i, j)[0];int da = clusters[clusters_index].a - lab.at<Vec3b>(i, j)[1];int db = clusters[clusters_index].b - lab.at<Vec3b>(i, j)[2];int dx = clusters[clusters_index].row - i;int dy = clusters[clusters_index].col - j;int h_distance = dl * dl + da * da + db * db;int xy_distance = dx * dx + dy * dy;//cout << h_distance << "\t" << xy_distance * 100 << endl;return h_distance + xy_distance * 100;
}

然后就可以完成update_pixel函数了

void update_pixel(const Mat lab,int s) {for (int i = 0; i < sqrtK * sqrtK; i++) {  // 对于每一个超像素int clusters_x = clusters[i].row;int clusters_y = clusters[i].col;for (int x = -s; x <= s; x++) {           // 在它周围-s到s的范围内for (int y = -s; y <= s; y++) {int now_x = clusters_x + x;int now_y = clusters_y + y;if (now_x < 0 || now_x >= sqrtN || now_y < 0 || now_y >= sqrtN)continue;int new_dis = get_distance(lab, i, now_x, now_y);// 如果为-1(还没有更新过)或者新的距离更小,就更换当前像素属于的超像素if (dis[now_x][now_y] > new_dis || dis[now_x][now_y] == -1) {dis[now_x][now_y] = new_dis;label[now_x][now_y] = i;}}}}
}

3. updaye_clusters函数

这个函数就是根据当前超像素的所有归属像素来更新位置。

需要注意的是C++用new申请空间时后面加上()会自动初始化申请的空间。
还有就是记得delete

void updaye_clusters(const Mat lab) {int *sum_count = new int[sqrtK * sqrtK]();int *sum_i = new int[sqrtK * sqrtK]();int *sum_j = new int[sqrtK * sqrtK](); int* sum_l = new int[sqrtK * sqrtK]();int* sum_a = new int[sqrtK * sqrtK]();int* sum_b = new int[sqrtK * sqrtK]();for (int i = 0; i < sqrtN; i++) {for (int j = 0; j < sqrtN; j++) {sum_count[label[i][j]]++;sum_i[label[i][j]] += i;sum_j[label[i][j]] += j; sum_l[label[i][j]] += lab.at<Vec3b>(i, j)[0];sum_a[label[i][j]] += lab.at<Vec3b>(i, j)[1];sum_b[label[i][j]] += lab.at<Vec3b>(i, j)[2];}}for (int i = 0; i < sqrtK * sqrtK; i++) {if (sum_count[i] == 0) {continue;}clusters[i].row = round(sum_i[i] / sum_count[i]);clusters[i].col = round(sum_j[i] / sum_count[i]); clusters[i].l = round(sum_l[i] / sum_count[i]);clusters[i].a = round(sum_a[i] / sum_count[i]);clusters[i].b = round(sum_b[i] / sum_count[i]);}delete[] sum_count;delete[] sum_i;delete[] sum_j;delete[] sum_l;delete[] sum_a;delete[] sum_b;
}

4. 显示函数

OK, 到了这一步其实算法已经完成了。我们在实现一下用于显示的函数吧。draw_clusters函数就是画出每一个超像素点,final_draw函数就是绘制一张超像素分割图。

void draw_clusters(const Mat copy) {for (int index = 0; index < sqrtK * sqrtK; index++) {Point p(clusters[index].row, clusters[index].col);circle(copy, p, 1, Scalar(0, 0, 255), 1);  // 画半径为1的圆(画点)}imshow("超像素示意图", copy);
}void final_draw(const Mat lab,Mat copy) {for (int i = 0; i < sqrtN; i++) {for (int j = 0; j < sqrtN; j++) {int index = label[i][j];copy.at<Vec3b>(i, j)[0] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[0];copy.at<Vec3b>(i, j)[1] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[1];copy.at<Vec3b>(i, j)[2] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[2];}}cvtColor(copy, copy, CV_Lab2BGR);imshow("分割图", copy);
}

最后效果如下:


2020 7.3更新:

完成了超像素框绘制的功能,这里给出汇总代码:

#include <opencv2/opencv.hpp>
#include <iostream>  using namespace cv;
using namespace std;#define sqrtK 32        // 超像素个数32*32
#define sqrtN 512       // 图像格式512*512int label[sqrtN][sqrtN];      // 图像各像素点归属
int dis[sqrtN][sqrtN];          // 图像各像素点距离struct cluster {int row, col, l, a, b;
};
cluster clusters[sqrtK * sqrtK];        // 存储超像素的像素坐标、颜色/*** 初始化每一个超像素的坐标* 初始化每一个像素的label(即属于哪一个超像素)* 像素与超像素的距离先假设为-1
*/
void init_clusters(const Mat lab, int S) {for (int i = 0; i < sqrtK; i++) {int temp_row = S / 2 + i * S;for (int j = 0; j < sqrtK; j++) {clusters[i * sqrtK + j].row = temp_row;clusters[i * sqrtK + j].col = S / 2 + j * S;}}for (int i = 0; i < sqrtN; i++) {int cluster_row = i / S;for (int j = 0; j < sqrtN; j++) {label[i][j] = cluster_row * sqrtK + j / S;}}fill(dis[0], dis[0] + (sqrtN * sqrtN), -1);
}inline int get_distance(const Mat lab, int clusters_index, int i, int j) {int dl = clusters[clusters_index].l - lab.at<Vec3b>(i, j)[0];int da = clusters[clusters_index].a - lab.at<Vec3b>(i, j)[1];int db = clusters[clusters_index].b - lab.at<Vec3b>(i, j)[2];int dx = clusters[clusters_index].row - i;int dy = clusters[clusters_index].col - j;int h_distance = dl * dl + da * da + db * db;int xy_distance = dx * dx + dy * dy;//cout << h_distance << "\t" << xy_distance * 100 << endl;return h_distance + xy_distance * 100;
}void update_pixel(const Mat lab, int s) {for (int i = 0; i < sqrtK * sqrtK; i++) {   // 对于每一个超像素int clusters_x = clusters[i].row;int clusters_y = clusters[i].col;for (int x = -s; x <= s; x++) {           // 在它周围-s到s的范围内for (int y = -s; y <= s; y++) {int now_x = clusters_x + x;int now_y = clusters_y + y;if (now_x < 0 || now_x >= sqrtN || now_y < 0 || now_y >= sqrtN)continue;int new_dis = get_distance(lab, i, now_x, now_y);// 如果为-1(还没有更新过)或者新的距离更小,就更换当前像素属于的超像素if (dis[now_x][now_y] > new_dis || dis[now_x][now_y] == -1) {dis[now_x][now_y] = new_dis;label[now_x][now_y] = i;}}}}
}void updaye_clusters(const Mat lab) {int* sum_count = new int[sqrtK * sqrtK]();int* sum_i = new int[sqrtK * sqrtK]();int* sum_j = new int[sqrtK * sqrtK]();int* sum_l = new int[sqrtK * sqrtK]();int* sum_a = new int[sqrtK * sqrtK]();int* sum_b = new int[sqrtK * sqrtK]();for (int i = 0; i < sqrtN; i++) {for (int j = 0; j < sqrtN; j++) {sum_count[label[i][j]]++;sum_i[label[i][j]] += i;sum_j[label[i][j]] += j;sum_l[label[i][j]] += lab.at<Vec3b>(i, j)[0];sum_a[label[i][j]] += lab.at<Vec3b>(i, j)[1];sum_b[label[i][j]] += lab.at<Vec3b>(i, j)[2];}}for (int i = 0; i < sqrtK * sqrtK; i++) {if (sum_count[i] == 0) {continue;}clusters[i].row = round(sum_i[i] / sum_count[i]);clusters[i].col = round(sum_j[i] / sum_count[i]);clusters[i].l = round(sum_l[i] / sum_count[i]);clusters[i].a = round(sum_a[i] / sum_count[i]);clusters[i].b = round(sum_b[i] / sum_count[i]);}delete[] sum_count;delete[] sum_i;delete[] sum_j;delete[] sum_l;delete[] sum_a;delete[] sum_b;
}void draw_clusters(const Mat copy) {for (int index = 0; index < sqrtK * sqrtK; index++) {Point p(clusters[index].row, clusters[index].col);circle(copy, p, 1, Scalar(0, 0, 255), 1);  // 画半径为1的圆(画点)}imshow("超像素示意图", copy);
}void final_draw(const Mat lab, Mat copy) {for (int i = 0; i < sqrtN; i++) {for (int j = 0; j < sqrtN; j++) {int index = label[i][j];copy.at<Vec3b>(i, j)[0] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[0];copy.at<Vec3b>(i, j)[1] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[1];copy.at<Vec3b>(i, j)[2] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[2];}}cvtColor(copy, copy, CV_Lab2BGR);imshow("分割图", copy);
}void draw_edge(const Mat lab, Mat copy) {// 这里的代码和上面的函数几乎一样,都是同标签的绘制相应的超像素颜色,因为方便用户自己选用绘制函数所以没有调用上面的函数for (int i = 0; i < sqrtN; i++) {for (int j = 0; j < sqrtN; j++) {int index = label[i][j];copy.at<Vec3b>(i, j)[0] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[0];copy.at<Vec3b>(i, j)[1] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[1];copy.at<Vec3b>(i, j)[2] = lab.at<Vec3b>(clusters[index].row, clusters[index].col)[2];}}// 这里的思路是4个方向,一旦有标签不同,就设置为黑色static int X[] = { 0,0,-1,1 };static int Y[] = { 1,-1,0,0 };cvtColor(copy, copy, CV_Lab2BGR); // 改成BGR,方便后面设置边框的颜色。for (int i = 0; i < sqrtN; i++) {for (int j = 0; j < sqrtN; j++) {int index = label[i][j];for (int k = 0; k < 4; k++) {if (index != label[i + X[k]][j + X[k]])copy.at<Vec3b>(i, j)[0] = copy.at<Vec3b>(i, j)[1] = copy.at<Vec3b>(i, j)[2] = 0;}}}imshow("超像素边界", copy);
}int main() {// 注意修改文件位置Mat src = imread("C:\\Users\\ttp\\Desktop\\map.bmp"), lab;// resize图片并高斯滤波(可选)resize(src, src, Size(sqrtN, sqrtN));// GaussianBlur(src, src, Size(3, 3), 1, 1);/**得到Lab色彩空间,需要注意的是:1.opencv里面默认为BGR排列方式2.LAB通道范围取决于转换前的通道范围,这样其实也方便处理例如:开始是0-255,转换后也是0-255,而不是LAB规定的[127,-128]*/cvtColor(src, lab, CV_BGR2Lab);int N = sqrtN * sqrtN;         // 像素总数 512*512int K = sqrtK * sqrtK;          // 超像素个数 128*128int S = sqrt(N / K);           // 相邻种子点距离(超像素边长) 4// 1.初始化像素init_clusters(lab, S);cout << "1-初始化像素-完成\n";// 2.微调种子的位置 貌似好一点,没有太大区别,所以这里就直接省略了for (int i = 0; i < 5; i++) {// 3.4.初始化数据update_pixel(lab, 2 * S);cout << "3-初始化数据-完成\n";// 5.让超像素位于正中间updaye_clusters(lab);cout << "4-让超像素位于正中间-完成\n";// -------------------这两个函数主要是帮助显示结果的// 6.标识超像素draw_clusters(src.clone());cout << "5-标识超像素-完成\n";// 7.绘制超像素结果图final_draw(lab, lab.clone());cout << "6-绘制超像素结果图-完成\n";draw_edge(lab, lab.clone());// opencv的函数,每1000ms更新一下,动态显示图片waitKey(30);// -----------------------------------------------}imshow("原图", src);waitKey(0);
}

运行结果:

【OpenCv3】 VS C++ (五):SLIC超像素分割算法相关推荐

  1. VLFeat SLIC超像素分割(Cpp版)

    这段时间对VLFeat的C接口非常的感兴趣,以前用的都是其Matlab接口,虽然很方便,而且提供的Matlab接口要比C接口功能更强大,但Matlab终归只能用来做一下快速的方法验证,所以想比较完整的 ...

  2. 图像分割:Python的SLIC超像素分割

    图像分割:Python的SLIC超像素分割 1. 什么是超像素? 2. 为什么超像素在计算机视觉方面有重要的作用? 3. 简单线性迭代聚类(SLIC) 4. 效果图 5. 源码 参考 1. 什么是超像 ...

  3. SLIC超像素分割方法

    为了方便查找,记录SLIC超像素分割方法的介绍 简介 关键代码分析 应用

  4. SLIC超像素分割详解

    SLIC超像素分割详解(一) 超像素概念是2003年Xiaofeng Ren提出和发展起来的图像分割技术,是指具有相似纹理.颜色.亮度等特征的相邻像素构成的有一定视觉意义的不规则像素块.它利用像素之间 ...

  5. 【youcans 的 OpenCV 例程200篇】172.SLIC 超像素区域分割算法比较

    OpenCV 例程200篇 总目录-202205更新 [youcans 的 OpenCV 例程200篇]172.SLIC 超像素区域分割算法比较 5. 区域分割之聚类方法 5.3 SLIC 超像素区域 ...

  6. 超像素分割算法————综述

    参考:超像素-学习笔记 什么是超像素?评价标准?SLIC.SEED.ETPS算法 比较的指标:图像边界的粘附性.算法速度.存储效率.分割性能 超像素算法:将像素组合成感知有意义的原子区域( atomi ...

  7. SLIC 超像素分割详解(三):应用

    看过上面的介绍后,我们应该思考一下:分割好的超像素有什么用?怎么用?用到哪里? 首先,超像素可以用来做跟踪,可以参考卢湖川课题组发表在IEEE TIP上的<Robust superpixeltr ...

  8. SLIC超像素分割的算法介绍和源码分析

    前述 最近在看显著性检测,发现很多算法的基础是超像素分割,而正在看的Saliency Optimization from Robust Background Detection算法的预处理是SLIC算 ...

  9. SLIC超像素生成算法

    SLIC算法是simple linear iterative cluster的简称,该算法用来生成超像素(superpixel). 基本思想 算法大致思想是这样的,将图像从RGB颜色空间转换到CIE- ...

  10. SLIC图像超像素分割算法解析

    转载自http://blog.chinaunix.net/uid-29431466-id-4831314.html 1 概述 SLIC 即simple linear iterative cluster ...

最新文章

  1. Android开发之限制输入框长度 | 限制EditText输入长度 | 限制AppCompatEditText长度的方法
  2. 《结对-贪吃蛇-设计文档》
  3. php怎么给接口里的方法传参,PHP接口中方法的参数和实现类方法中的参数可以不一致的问题...
  4. python编写es脚本_Elasticsearch 参考指南(如何使用脚本)
  5. js中字符串类型转化toString、parseInt、parseFloat、Number
  6. Leetcode 刷题笔记(十七) —— 二叉树篇之公共祖先问题
  7. c语言中指,C语言程序设计中指教学要点分析.doc
  8. 小代码编写神器:LINQPad 使用入门
  9. 常见网络厂商Mib库文件
  10. 网络游戏制作---坦克大战(1)
  11. 【洛谷】P2298 Mzc和男家丁的游戏*
  12. 数据结构|连通图、完全图、无向图、有向图的边数计算问题
  13. 【C语言基础练习】100匹马驮100担货,大马一匹驮3担,中马一匹驮2担,小马两匹驮1担。试编写程序计算大、中、小马的数目。
  14. 【胡搞的不能AC的题解,暴力搜索一发博弈问题】1995 三子棋 - 51Nod
  15. 2019-TOG-Adobe-(3D Ken Burns)3D ken burns effect from a single image
  16. Word 2003操作技巧之改变默认字体及恢复方法
  17. SIM卡不识或者掉卡简单分析
  18. 中学教师资格考试模拟试卷
  19. android美图秀秀--基础
  20. 【python】python实现屏幕指定区域文字提取(百度API)

热门文章

  1. [Eclipse经验] 如何导入XSD文件
  2. linux下最全的快捷键大全
  3. svn如何退回软件版本_SVN版本控制工具的使用
  4. 快递100支持的物流公司
  5. 离线OCR、文字识别、ios证件扫描、ios系统OCR(ios、android)
  6. json文件格式转换为png文件格式
  7. Android APP原型设计,五款移动APP在线原型设计工具,值得收藏
  8. 常用词句或缩略语汇总
  9. 标定学习笔记(四)-- 手眼标定详解
  10. SpringMVC入门简单静态资源处理