opencv小白实现基地学习任务的心路历程!
在网上找了很多资料后,我定下的方案大致如下:
1.对黄色进行二值化处理(inrange()函数),将图像快速分为黑白两色。
其中白色对应的就是最先形成的mask图像(掩膜),用于之后和最初图像的一个叠加。
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
int main(){
Mat im1,out,labels,stats,centroids,img_bool,final,gray;
Mat image = imread("C:\\Users\\huli\\Desktop\\test1\\4.png");
Mat pre=image;
if (image.empty())
{
cout << "Error: Could not load image" << endl;
return 0;
}
int height = image.rows;
int width = image.cols;
Mat hsvImage;
Mat mask;
cvtColor(image,hsvImage,COLOR_BGR2HSV);
//基本的颜色转化处理
Scalar yellow_scalarL = Scalar(11, 70, 110);
Scalar yellow_scalarH = Scalar(34, 255, 255);
//你可以上网查找黄色的对应hsv值,此上数据是我再次基础上进行了不断调整后得出的
inRange(hsvImage, yellow_scalarL, yellow_scalarH, mask);
imshow("mask",mask);//生成基础mask
至于为什么将RGB转化成HSV,这是因为RGB三个通道共同决定颜色,但HSV却仅仅是H决定了肉眼可见的颜色。S,V分别代表饱和度和明度,这就代表我们将有更多的空间进行参数的调整。
inRange(原图,下限,上限,输出图像)
当原图像素在下限和上限之间的时候,函数会把像素值设置为白色(255),反之则将输出黑色(0)。注意:此时的mask已经是一张灰度图了
这就是最开始的mask图
从上图我们不难发现仅仅是对黄色的二值化处理,会有一定的残余。为了处理这些面积较小的区域,我们将采用第二个重要函数。
connectedComponents(输入图(二值化),输出的标记图像(labels),connectivity,ltype)
这里的connectivity默认为8,当然你也可以改为较为常见的4。
至于连通域的原理,展开来讲就有一些复杂了。不过基本的方法有两遍扫描法和种子填充法,更多的大家可以上csdn上翻翻大佬们的资料。
ltype则代表输出的labels的数据类型。
void ClearSmallConnecttedComponent(Mat srcImage, Mat& dstImage,int S)//去除面积小于等于S的连通域
{
Mat temp;
Mat labels;//labels内数据由连通域索引转换到了像素值!!!!!
srcImage.copyTo(temp);
//1. 标记连通域
int n_comps = connectedComponents(temp, labels, 4, CV_16U);
vector histogram_of_labels;
for (int i = 0; i < n_comps; i++)//初始化labels的个数为0
{
histogram_of_labels.push_back(0);
}
int rows = labels.rows;
int cols = labels.cols;
for (int row = 0; row < rows; row++) //计算每个labels的面积
{
for (int col = 0; col < cols; col++)
{
histogram_of_labels.at(labels.at(row, col)) += 1;//面积(像素点数)+1
}
}
histogram_of_labels.at(0) = 0; //将背景的面积设置为0
//2. 计算连通域labels索引
int maximum = 0;
int max_idx = 0;
int tmp[1024]{};//创建全0tmp数组,1则说明小于等于S
for (int i = 0; i < n_comps; i++)
{
if (histogram_of_labels.at(i) <=S)
{
tmp[i]=1;
}
}
//3.
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
if (labels.at(row, col) == 0)//背景跳过(保持黑色)
{
continue;
}
else if (tmp[labels.at(row, col)]==1)//小连通域去除
{
continue;
}
else
{
labels.at(row, col) = 255;//其余连通域转为白色
}
}
}
//4. 将图像更改为CV_8UC1格式
labels.convertTo(dstImage, CV_8UC1);
threshold(dstImage, dstImage, 254, 255, THRESH_BINARY);//非常重要!!!!!!!!!!!!!!!!!!!!
}
这是我定义的一个删除小区域连通区域的一个函数,下面让我们分块来讲解这段函数。
一.
void ClearSmallConnecttedComponent(Mat srcImage, Mat& dstImage,int S)//去除面积小于等于S的连通域
{
Mat temp;
Mat labels;//labels内数据由连通域索引转换到了像素值!!!!!
srcImage.copyTo(temp);
//1. 标记连通域
int n_comps = connectedComponents(temp, labels, 4, CV_16U);
vector histogram_of_labels;
for (int i = 0; i < n_comps; i++)//初始化labels的个数为0
{
histogram_of_labels.push_back(0);
}
int rows = labels.rows;
int cols = labels.cols;
for (int row = 0; row < rows; row++) //计算每个labels的面积
{
for (int col = 0; col < cols; col++)
{
histogram_of_labels.at(labels.at(row, col)) += 1;//面积(像素点数)+1
}
}
histogram_of_labels.at(0) = 0; //将背景的面积设置为0
}
这里的srcImage代表我们输入的要处理的图片,dstImage是我们最后的输出图。
然后我们运用connectedComponents函数得到了初始mask的连通域数量(n_comps),
并由此设定了n_comps个不同连通域的初始面积。
然后我们根据不同像素点对应的label值进行了一个累加,从而得出了不同连通域(label)的面积
(背景面积记得设为0)
二.
int max_idx = 0;
int tmp[1024]{};//创建全0tmp数组,1则说明小于等于S
for (int i = 0; i < n_comps; i++)
{
if (histogram_of_labels.at(i) <=S)
{
tmp[i]=1;
}
}
这个部分的代码比较简单,对面积的比较的判断。当面积小于S时,我们将tmp这个标志变量设为1,代表我们会删除这个部分。
三.
//3.
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
if (labels.at(row, col) == 0)//背景跳过(保持黑色)
{
continue;
}
else if (tmp[labels.at(row, col)]==1)//小连通域去除
{
continue;
}
else
{
labels.at(row, col) = 255;//其余连通域转为白色
}
}
}
//4. 将图像更改为CV_8UC1格式
labels.convertTo(dstImage, CV_8UC1);
threshold(dstImage, dstImage, 254, 255, THRESH_BINARY);//非常重要!!!!!!!!!!!!!!!!!!!!
在以上代码中我们遍历了所有的像素点并对其进行了判断,只有当对应像素的label不代表背景同时它的面积大于S,我们才会将其转化成白色的像素点。
然后看图!!!
经过上述操作,我们就成功实现了对多余区域的去除!!!
bitwise_and(输入图,输入图,输出图,掩膜(mask))
Mat mix1,mix2;
bitwise_and(image,image,mix1,final);
imshow("mix1",mix1);
通过该程序,我们就成功将两张图片的像素相加。
那么第一题的大体流程就结束了!!!!!!!
先放效果图:
在得到了第一题的图像后,我们对第二题的处理将会变得简单的多。
下面让我们由图像“final”入手,实现对图像的轮廓提取。
findContours
(输入(多为边缘图),输出(contours),对应关系(hierarchy),mode,method,offset)
blur(final,im1,Size(3,3));
Canny(im1,canny,100,120,3);
imshow("canny",canny);
vector> contours;
vector hierarchy;
findContours(canny, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE, Point());
Mat imageContour= Mat::zeros(im1.size(),CV_8UC1);
Mat Contours = Mat::zeros(image.size(), CV_8UC1);
输入是经由Canny函数处理后得到的边界函数。
contours,则储存了每一层轮廓的坐标。
(大体就是vector向量中构建了多个由point类构建的vector向量,所以它可以同时储存不同轮廓的多个坐标)
hierarchy,输出的轮廓关系的存储。
(生成hierarchy[i][0] ~hierarchy[i][3],分别表示i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号)
cvtColor(imageContour,im1,COLOR_GRAY2BGR);
//drawContours(im1, contours, 1, Scalar(0,0,255), 4, 7, hierarchy);
drawContours(im1, contours, 2, Scalar(0,0,255), 4, 7, hierarchy);
im1.copyTo(pre,im1);
imshow("final2",pre);
waitKey(0);
destroyAllWindows();
在得到函数相对轮廓的坐标后,我们就可以直接使用drawContours将我们想要的图像画出来。
那么到这,基地的基础任务1大体就完成了!!!!