在matlab中有对图像的连通区域进行求解的函数,即bwlabel。但是opencv里好像没有,所以这里自己实现一下,方便以后使用。
首先,我回顾一下bwlabel的参数和用法:
L =bwlabel(BW,n)
返回一个和BW大小相同的L矩阵,包含了标记了BW中每个连通区域的类别标签,这些标签的值为1、2、num(连通区域的个数)。n的值为4或8,表示是按4连通寻找区域,还是8连通寻找,默认为8。
[L,num] = bwlabel(BW,n)这里num返回的就是BW中连通区域的个数。
实现步骤:
(1)将输入图像阈值化,若输入图像为彩色图像,则先灰度化后再阈值化为二值图像。
(2)给二值图像添加一个是否已访问的属性,类型为Bool(避免死循环)
(3)找到第一个非零的像素点,将其入栈并将其是否已访问的属性置为真。
(4)以栈的大小是否为0作为结束条件,寻找栈顶元素相邻的八邻域非零像素点,并将它们入栈,结束后将栈顶元素删除。
(5)当栈为空时,表明一个连通区域已经遍历完成,需继续找到下一个非空且未访问过的像素点作为起点,重复4步骤,直到所有的非零像素点都被访问完成。
(6)当所有的连通区域求解完成之后,将像素点个数最大的连通区域标记出来即可。
用到的数据结构:
typedef struct{
boolvSign;//像素点是否被访问过的标记,ture已访问,false表示未访问,给图片添加的一个属性
intpixelValue;//像素值
}isVisit;
typedef struct{
CvPointregionPoint;//该连通区域起点的坐标
intregionId;//第i个连通区域的标号
intpointNum;//第i个连通区域的像素点的总个数
}connectRegionNumSet;
输入图像:
求解的连通区域:
类似于bwlabel的标记
提取出最大的连通区域:
--------------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------
别急,代码在最下哦。
只有一个文件main.cpp。
#include<cv.h>
#include<highgui.h>
#include<iostream>
usingnamespace std;
typedefstruct{
bool vSign;//像素点是否被访问过的标记,ture已访问,false表示未访问,给图片添加的一个属性
int pixelValue;//像素值
}isVisit;
typedefstruct{
CvPoint regionPoint;//该连通区域起点的坐标
int regionId;//第i个连通区域的标号
int pointNum;//第i个连通区域的像素点的总个数
}connectRegionNumSet;
intcalConnectRegionNums(IplImage *srcGray,vector<vector<isVisit>>& validPicture,vector<connectRegionNumSet> ®ionSet);
intmain(){
IplImage * src =cvLoadImage("useConnectImage.jpg");//F:/MasterHomeWork/ImageProcess/ConnectNumProgramMy/
IplImage * srcGray = NULL;
if(src->nChannels==1)
goto next;
srcGray = cvCreateImage(cvSize(src->width,src->height),8,1);
cvCvtColor(src,srcGray,CV_RGB2GRAY);
next:
if(!srcGray)
cvThreshold(src,srcGray,66,255,CV_THRESH_BINARY);
else
cvThreshold(srcGray,srcGray,66,255,CV_THRESH_BINARY);
cvNamedWindow("srcBinaryGray");
cvShowImage("srcBinaryGray",srcGray);
//按照以下方法,申请的是堆heap内存空间,相对而言较大,足够一般用户使用,而且可以动态分配。
vector<vector<isVisit> >validPoint;//此种申请发放只要电脑内存足够大,图片的尺寸也可足够大
validPoint.resize(srcGray->height);
for(int i =0;i<validPoint.size();i++)
validPoint[i].resize(srcGray->width);
vector<connectRegionNumSet>regionSet;//存放找到的各个连通区域
//regionSet.size()为连通区域的个数。
cout<<"连通区域的数目:"<<calConnectRegionNums(srcGray,validPoint,regionSet)<<endl<<endl;//计算连通区域数目
char text[3];//设置连通区域的编号,最小标号为0,最大编号为99
CvFont font;//设置字体
//参数从左到右:字体初始化,字体格式,字体宽度,字体高度,字体倾斜度,字体粗细,字体笔画类型
cvInitFont(&font,CV_FONT_HERSHEY_COMPLEX, 0.6, 0.6, 0, 1, 8);
for(int i=0;i<regionSet.size();i++){
cout<<"第"<<i<<"个连通区域的起点坐标 =("<<regionSet[i].regionPoint.x<<","<<regionSet[i].regionPoint.y<<")"<<",像素总点数="<<regionSet[i].pointNum<<endl;
if(i < 10){//连通区域的个数为个位数
text[0] = '0';
text[1] = '0'+i;
}
else{//连通区域的个数为十位数
text[0] ='0'+(i)/10;
text[1] ='0'+(i)%10;
}
text[2] = '\0';
cvPutText(src,text,regionSet[i].regionPoint,&font,cvScalar(0,0,255));
}
cvNamedWindow("src");
cvShowImage("src",src);
cvShowImage("srcGray",srcGray);
cvWaitKey(0);
cvReleaseImage(&src);
cvReleaseImage(&srcGray);
cvDestroyAllWindows();
}
intcalConnectRegionNums(IplImage *srcGray, vector<vector<isVisit>>& validPicture,vector<connectRegionNumSet> ®ionSet)
{
int regionId = 1;//管理连通区域标号的便令
connectRegionNumSet regionSetTemp;//临时用到的regionSetTemp类型中间变量
uchar * ptr = (uchar*)(srcGray->imageData);
for(int y=0; y<srcGray->height;y++){//给图片加上一个是否已访问的属性
ptr = (uchar*)(srcGray->imageData+y*srcGray->widthStep);
for(int x=0;x<srcGray->width; x++){
validPicture[y][x].pixelValue= (int)ptr[x];
validPicture[y][x].vSign= false;//开始时默认都未访问
}
}
vector<CvPoint> stack;//stack(栈),heap(堆)
CvPoint foundValidPoint;
for(int y=0; y<srcGray->height;y++){//给图片加上一个是否已访问的属性
for(int x=0;x<srcGray->width; x++){
if(validPicture[y][x].pixelValue&& !validPicture[y][x].vSign){//找到下一个连通区域的起点,即像素值非零且未被访问过的点
inteachRegionAcc = 1;//表示即将要寻找的连通区域的总像素点个数;
//将validPicture[y][x]点默认为即将生成的连通区域的起点
regionSetTemp.regionPoint= cvPoint(x,y);//x表示列,y表示行
regionSetTemp.regionId= regionId++;
regionSetTemp.pointNum= 1;
regionSet.push_back(regionSetTemp);
//将该点设置为已访问,并对其执行入栈操作
validPicture[y][x].vSign= true;
stack.push_back(cvPoint(x,y));
while(stack.size()){//当栈内为元素时,表示该连通区域的点已经全部访问
foundValidPoint= stack.back();//从栈尾开始寻找八连通的相邻点
stack.pop_back();//上一句已得到栈尾像素点,该点可以出栈了
inti = foundValidPoint.x;//t
intj = foundValidPoint.y;//k
intminY = (j-1<0?0:j-1);
intmaxY = ((j+1>srcGray->height-1?srcGray->height-1:j+1));
intminX = (i-1<0?0:i-1);
intmaxX = (i+1>srcGray->width-1?srcGray->width-1:i+1);
for(intk=minY; k<=maxY; k++){//在八连通范围内(两点之间距离小于根号2的点),表示其相邻点,入栈c
for(intt=minX; t<=maxX; t++){
if(validPicture[k][t].pixelValue&& !validPicture[k][t].vSign){//validPicture[k][t]如果没有访问过
validPicture[k][t].vSign= true;//标志为已访问,防止死循环
stack.push_back(cvPoint(t,k));//入栈,以便再次迭代
eachRegionAcc++;//相邻点的数目加1
}
}
}
}
if(eachRegionAcc> 1){//要求:连通区域的点数至少要有两个
regionSet[regionSet.size()-1].pointNum= eachRegionAcc;
}
else{//单个像素点不算,如果单个像素点也算,去掉该else语句即可
regionSet.pop_back();//上述默认的即将生成的连通区域不符合要求,出栈
regionId--;
}
}
}
}
//找到最大连通区域,并标记-----------------------------------------------
int max_pointNum = 0; //最大连通区域的像素点个数
int max_regionId = 0; //最大连通区域的标号
for(int i=0;i<regionSet.size();i++)
{
if(max_pointNum <regionSet[i].pointNum)
{ max_pointNum = regionSet[i].pointNum;
max_regionId =i;
}
}
for(int i=0;i<regionSet.size();i++)
if(i!=max_regionId)
{
//标记
int x = regionSet[i].regionPoint.x;
int y =regionSet[i].regionPoint.y;
//重置每个像素点为未访问
for(int y=0;y<srcGray->height; y++){//给图片加上一个是否已访问的属性
for(int x=0;x<srcGray->width; x++){
validPicture[y][x].vSign= false;//开始时默认都未访问
}
}
//将该点设置为已访问,并对其执行入栈操作
validPicture[y][x].vSign =true;
cvSet2D(srcGray,y,x,CV_RGB(0,0,0));
stack.push_back(cvPoint(x,y));
while(stack.size()){//当栈内为元素时,表示该连通区域的点已经全部访问
foundValidPoint =stack.back();//从栈尾开始寻找八连通的相邻点
stack.pop_back();//上一句已得到栈尾像素点,该点可以出栈了
int i = foundValidPoint.x;//t
int j =foundValidPoint.y;//k
int minY =(j-1<0?0:j-1);
int maxY =((j+1>srcGray->height-1?srcGray->height-1:j+1));
int minX =(i-1<0?0:i-1);
int maxX =(i+1>srcGray->width-1?srcGray->width-1:i+1);
for(int k=minY;k<=maxY; k++){//在八连通范围内(两点之间距离小于根号2的点),表示其相邻点,入栈c
for(intt=minX; t<=maxX; t++){
if(validPicture[k][t].pixelValue&& !validPicture[k][t].vSign){//validPicture[k][t]如果没有访问过
validPicture[k][t].vSign= true;//标志为已访问,防止死循环
stack.push_back(cvPoint(t,k));//入栈,以便再次迭代
cvSet2D(srcGray,k,t,CV_RGB(0,0,0));
}
}
}
}
}
//找到最大连通区域,并标记-----------------------------------------------
return regionSet.size();
}