提取连通域实际上是标记连通域的过程,其算法如下:
初始点:Bo=某个连通分量中的某点(这里通过遍历图像,知道像素值为255,将此点作为种子点)
循环:
(用3X3d的结构元素对种子点进行膨胀,然后用原图像对膨胀结果取交集)
结束条件:知道膨胀的图像不发生变化
在这里可以用一个模板来存储连通分量,其位置对应原图像的位置。提取完后可以修改原图像的像素值,即一个标签。
实现过程如下:
#include<iostream>
#include<opencv2\opencv.hpp>
using namespace std;
using namespace cv;
/*******************************************
功能:标注连通分量
参数:src-输入图像
nConn-取值4、8,表示4连通或8连通
********************************************/
void LabelConnRgn(Mat& src, int nConn)
{
int se[3][3] = { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } };//8连通
if (nConn == 4)
{
se[0][0] = -1;
se[0][2] = -1;
se[2][0] = -1;
se[2][2] = -1;
}
int nConnRgn = 254;//连通分量的标记号
Mat dst = Mat::zeros(src.size(), src.type());
for (int i = 0; i < src.rows; i++)
{
uchar* srcData = src.ptr<uchar>(i);
uchar* dstData = dst.ptr<uchar>(i);
for (int j = 0; j < src.cols; j++)
{
if (srcData[j] == 255)
{
dstData[j] = 255;
while (true)
{
Mat temp;
dst.copyTo(temp);
dilate(dst, dst, se);
dst= dst&src;
//如果和上一次处理后的图像相同,说明连通区域已经提取完毕
if (equalImg(dst, temp))
{
break;
}
}
//标注刚刚找到的连通区域
for (int k = 0; k < src.rows; k++)
{
uchar* srcData = src.ptr<uchar>(k);
uchar* dstData = dst.ptr<uchar>(k);
for (int l = 0; l < src.cols; l++)
{
if (dstData[l] == 255)
{
//标记原图像上的连通区域
srcData[l] = nConnRgn;
}
}
}
nConnRgn-=50;//连通区域编号加1(此处为了显示的方便实际为:nConnRgn--;)
if (nConnRgn <=0)
{
cout << "连通区域大于254个连通区域" << endl;
i = src.rows;//强制跳出外循环
break;
}
}
}
}
}
int main()
{
Mat src= imread("test1.jpg", 0);
imshow("原始图像", src);
LabelConnRgn(src, 4);
imshow("连通区域",src);
waitKey(0);
return 0;
}
原始图像:
提取后的图像: