环境:VS2017+OpenCV3.3+C++
什么是图像切割?在一幅图像中,如果我们只对其中的部分目标感兴趣,这些目标通常占据一定的区域,并且在某些特性(如灰度、轮廓、颜色和纹理等)上和临近的图像有差别。这些特性差别可能非常明显,也可能很细微,以至肉眼察觉不出来。随着计算机图像处理技术的发展,我们可以通过计算机来获取和处理图像信息。图像识别的基础是图像分割,其作用是把反映物体真实情况的、占据不同区域的、具有不同特性的目标区分开来,并形成数字特征。图像分割是图像识别和图像理解的基本前提步骤,图像分割质量的好坏直接影响后续图像处理的效果,甚至决定其成败,因此,图像分割的作用是至关重要的。
今天我来说下,如何使用FCN实现图像切割,模型下载,我们一步一步来实现这个功能。
1、头文件引入
#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/dnn.hpp>
using namespace std;
using namespace cv;
using namespace cv::dnn;
2、模型文件位置
String fcn_label_txt = "D:/new_cv/opencv/sources/samples/data/dnn/pascal-classes.txt";
String fcn_txt = "D:/new_cv/opencv/sources/samples/data/dnn/fcn8s-heavy-pascal.prototxt";
String fcn_model = "D:/new_cv/opencv/sources/samples/data/dnn/fcn8s-heavy-pascal.caffemodel";
3、读入label值
对于图像分割和常规的检测是不一样的,他是对输入图像全局的每一个像素进行的识别,也就是说每一个像素点都应该有一个对应的label和value值,如果你能明白这个思想下面就好办了,我们读取label值内容如下:
background 0 0 0
aeroplane 128 0 0
bicycle 0 128 0
bird 128 128 0
boat 0 0 128
bottle 128 0 128
bus 0 128 128
car 128 128 128
cat 64 0 0
chair 192 0 0
cow 64 128 0
diningtable 192 128 0
dog 64 0 128
horse 192 0 128
motorbike 64 128 128
person 192 128 128
pottedplant 0 64 0
sheep 128 64 0
sofa 0 192 0
train 128 192 0
tvmonitor 0 64 128
我们可以看到每个值都是三通道的,如果是之前的检测结果,应该用vector<String>存储,换成通道value,我们用vector<Vec3b>来存储,方法如下
vector<Vec3b> readColors()
{
vector<Vec3b> colors;
ifstream fp(fcn_label_txt);//创建流
if (!fp.is_open())
{
cout << "can not open label file" << endl;
exit(-1);
}
string line;
while (!fp.eof())//如果流没有结束
{
getline(fp, line);
if (line.length())//判断是否可以打开
{
stringstream ss(line);//创建字符流
string name;
ss >> name;
// cout << name << endl;;
int temp;
Vec3b color;
ss >> temp;
cout << temp;
color[0] = (uchar)temp;
ss >> temp;
color[1] = (uchar)temp;
ss >> temp;
color[2] = (uchar)temp;
// cout << temp << endl;
colors.push_back(color);
}
}
return colors;
}
4、图片处理与模型初始化
Mat src = imread("D:/test/test.jpg");
if (src.empty())
{
cout << "imput img is empty" << endl;
return -1;
}
//resize
resize(src, src, Size(500, 500));
imshow("src", src);
vector<Vec3b> labels = readColors();
Mat blobimg = blobFromImage(src);
Net net = readNetFromCaffe(fcn_txt,fcn_model);
if (net.empty())
{
cout << "init net error";
return -1;
}
float time = getTickCount(); //计时器
cout << time;
net.setInput(blobimg, "data");
Mat score = net.forward("score");
float tt = getTickCount() - time;
cout << "time consume: " << (tt / getTickFrequency() * 1000) << endl;
5、定义查找表
为什么要生成查找表的?从结果角度来说,我们要知道输出图像,每一个像素属于哪个通道(21个label的坐标)以及对应的value值是多少,所以我们要声明一个对应的查找表,后续来做map
cout << score.size <<" "<< score.size[1]<<" " << score.size[2]<<" " << score.size[3] << endl;
// 21*500*500
const int rows = score.size[2];//height
const int cols = score.size[3];//width
const int chls = score.size[1];//channels
Mat maxC1(rows, cols, CV_8UC1); //存储最终选定的通道(21分之一)
Mat MaxVal(rows, cols, CV_32FC1);//存储label的key
接下来是一个分割算法当中比较绕的地方了那就是查找表的生成
//我们要遍历的score 是数据结构为 21个通道,每个通道是500*500像素的结果
//set 查找表
for (int c = 0; c < chls; c++) //一共21个通道代表21个通道在每个下像素点的置信度
{
for (int row = 0; row < rows; row++)//500行
{
const float *ptrScore = score.ptr<float>(0, c, row);//指向每个通道的第1个位置,存储的是21个label的置信度
// cout << *ptrScore << endl;
uchar *ptrMaxC1 = maxC1.ptr<uchar>(row); //为了
float *ptrMaxVal = MaxVal.ptr<float>(row);
for(int col = 0; col < cols; col++) //500列
{
if (ptrScore[col] > ptrMaxVal[col]) //21个通道的同一个位置取最大值
{
ptrMaxVal[col] = ptrScore[col]; //存储最终的值也就是color帐的结果
ptrMaxC1[col] = (uchar)c;//存储最终选择的通道
}
}
}
}
6、输出结果
最后我们就可以声明一个500*500的zero Mat对象,依次map查找表中的位置,输出结果:
Mat result = Mat::zeros(rows, cols, CV_8UC3);//初始化全0的图像矩阵
for (int row = 0; row < rows; row++)
{
const uchar *ptrMaxC1 = maxC1.ptr<uchar>(row); //指向对应的查找表中的行
Vec3b *ptrColor = result.ptr<Vec3b>(row);//指向查找表中的行
for (int col = 0; col < cols; col++)
{
ptrColor[col] = labels[ptrMaxC1[col]];//对应位置
}
}
imshow("result", result);
结果如下: 哈哈哈哈哈哈哈^_^,猜猜原图是什么?
坚持一件事情或许很难,但坚持下来一定很酷!^_^
源代码:https://github.com/haiqiang2017/open-dnn/blob/master/DNN/fcn.cpp