代码需实现步骤:
1.完成框架:包含头文件、定义变量、配置opencv、配置vscode
2.函数实现:mread
读取图片——》resize图片并滤波——》cvtColor
完成RGB到LAB空间的转换——》定义计算距离的参数N、K、S——》像素初始化init_clusters
——》在定义的超像素框框内循环完成数据初始化update_pixel
+更新超像素位置updaye_clusters
+标识超像素draw_clusters
+绘制超像素的图final_draw
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
#define sqrtK 128 // 超像素个数128*128 (zx:自定义,也可以是32
#define sqrtN 512 // 图像格式512*512 (zx:根据具体图片来
int label[sqrtN][sqrtN]; // 图像各像素点归属
int dis[sqrtN][sqrtN]; // 图像各像素点距离
struct cluster{
int row, col, l, a, b;
};
cluster clusters[sqrtK*sqrtK]; // 存储超像素的像素坐标
int main(){
// 注意修改文件位置
Mat src = imread("E:\\TruthCocapop\\SLIC\\1.jpg"), lab;
// resize图片尺寸并滤波(zx:选择合适的滤波,比如高斯滤波啊之类的
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); //zx:在SLIC中对于颜色的空间转换(RGB->LAB)会消耗不少时间,这里直接使用opencv现有的转化算法进行颜色空间的转换
int N = sqrtN * sqrtN; // N:像素点总数 512*512
int K = sqrtK * sqrtK; // K:超像素个数 128*128
int S = sqrt(N / K); // S:相邻种子点距离(相邻两超像素间距)4,即超像素边长
// 1.初始化像素
init_clusters(lab,S);
cout << "1-初始化像素-完成\n";
// 2.微调种子的位置 (没改进太多,可用可不用
move_clusters(lab);
cout << "2-微调种子的位置-完成\n";
for (int i = 0; i < 5; i++) { //s=4
// 3.初始化数据
update_pixel(lab, 2*S);
cout << "3-初始化数据-完成\n";
// 4.让超像素位于正中间
updaye_clusters(lab);
cout << "4-让超像素位于正中间-完成\n";
// -------------------这两个函数主要是帮助显示结果的
// 5.标识超像素
draw_clusters(src.clone());
cout << "5-标识超像素-完成\n";
// 6.绘制超像素结果图
final_draw(lab, lab.clone());
cout << "6-绘制超像素结果图-完成\n";
// opencv的函数,每1000ms更新一下,动态显示图片
waitKey(1000);
// -----------------------------------------------
}
imshow("原图", src);
waitKey(0);
}
函数实现:
一、init_clusters
1.像素初始化函数init_clusters
,传入的参数为lab彩色空间和S超像素间距
注意:opencv中Mat类的赋值不是拷贝赋值,而是像C++中的引用赋值。(就是把原来的值也给改了,就是这个意思。比如:Mat a,b; b=a; 改变a也会改变b)
· 所以如果想得到一个全新的图像矩阵,可以使用b=a.clone(); 这样改变a,b的值不会被改
· fill函数用于对一段空间赋值,这里即将矩阵dis赋-1。(在非opencv程序也可以使用)
void init_clusters(const Mat lab,int S) {
// 初始化每一个超像素的坐标
for (int i = 0; i < sqrtK; i++) { //超像素个数sqrtK=128
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++) { //图片像素sqrtN=512
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;
}
}
// 像素与超像素的距离先假设为-1
fill(dis[0], dis[0] + (sqrtN * sqrtN), -1);
}
二、update_pixel函数
· 首先定义距离计算函数get_distance
传入的参数为lab、超像素索引clusters_index、像素的横纵坐标i和j
·lab.at(row,col)属于opencv里面的写法,用于访问矩阵lab在坐标(row,col)的值。Vec3b表示3通道,每个通道为uchar类型(0-255)。
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;
}
}
}
}
}
三、updaye_clusters函数
根据当前超像素的所有归属像素来更新位置。需要注意的是C++用new申请空间时后面加上()会自动初始化申请的空间。且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;
}
四、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);
}
六、draw_edge
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);
}
vscode显示未定义标识符CV_Lab2BGR
问题原因:新版本中色彩空间转换的标识符已经改了,但是文档似乎还没更新。
解决方法:把 CV_BGR2Lab 改成 COLOR_BGR2Lab 即可。
结果:不报错。