图像连通域分析
图像的连通域是指图像中具有相同像素值并且相邻的像素组成的区域,连通域分析是指在图像中寻找彼此独立的连通域并将其标记出来
提取图像中不同的连通域是图像处理中较为常用的方法,例如车牌识别、文字识别、目标检测等领域对感兴趣区域的分割和识别
一般情况下,一个连通域内只包含一个像素值,因此,为了防止像素值波动对提取不同连通域的影响,连通域分析常处理的是
二值化后的图像
在了解图像连通域分析方法之前,需要了解图像邻域的概念,图像中两个像素相邻有两中定义方式,分别是4-邻域和8-邻域

4-邻域定义下,两个像素相邻必须在水平和垂直方向上相邻
8-邻域定义下,两个像素相邻允许在对角线方向相邻
根据像素邻域定义方式不同,得到的连通域也不同,因此,在分析连通域时,一定要声明是哪种邻域下分析结果
常用的图像邻域分析法有两遍扫描法和种子填充法
两遍扫描法会遍历两次图像,第一次遍历图像时会给每一个非零像素赋予一个数字标签,当某个像素的上方和左侧邻域内的像素已经
有数字标签时,取两者中较小值作为当前像素的标签,否则赋予当前像素一个新的数字标签
在第一次遍历图像时候,同一个连通域可能会被赋予一个或者多个不同的标签
因此,第二次遍历需要将这些同一个连通域的不同标签合并,最后实现同一个邻域内的所有像素具有相同的标签
\
种子填充法源于计算机图像学,常用于对某些图像进行填充,该方法首先将所有非零像素放到一个集合中,之后在集合中随机选出一个
像素作为种子像素,根据邻域关系不断扩充种子像素所在的连通域,并在集合中删除扩充出的像素,直到种子所在的连通域无法扩充
之后再从集合中随机选取一个像素作为新的种子像素,重复上述过程直到集合中没有像素
connectComponents()
OpenCV4提供了用于提取图像中不同连通域的connectComponents()函数(两个原型)
connectComponents()函数原型1:
int connectComponents(InputArray image,
OutputArray labels,
int connectivity,
int ltype,
int ccltype
)
- image:待标记不同连通域的单通道图像,数据类型必须为CV_8U
- labels:标记不同连通域后的输出图像,与输入图像具有相同的尺寸
- connectivity:标记连通域时使用的邻域种类,4表示4-邻域,8表示8-邻域
- ltype:输出图像的数据类型,目前支持CV_32S和CV_16U两种数据类型
- ccltype:标记连通域时使用的算法标志
connectComponents()函数中标记连通域算法类型可选择标志
| 标志参数 | 简记 | 作用 |
|---|---|---|
| CCL_WU | 0 | 8-邻域使用SAUF算法,4-邻域使用SAUF算法 |
| CCL_DEFAULT | -1 | 8-邻域使用BBDT算法,4-邻域使用SAUF算法 |
| CCL_GRANA | 1 | 8-邻域使用BBDT算法,4-邻域使用SAUF算法 |
该函数用于计算二值图像中连通域的个数,并在图像中将不同的连通域用不同的数字标签标志,其中标签0表示图像的背景区域
同时函数具有一个int类型的返回数据用于表示图像中连通域的数目
第一个参数是待标记连通域的输入图像,函数要求输入图像必须是数据类型为CV_8U的单通道灰度图像,最好是经过二值化的二值图像
第二个参数是标记连通域后的输出图像,输出图像尺寸与输入图像尺寸相同,数据类型与第四个参数有关
第三个参数是标记连通域是选择的邻域种类,该函数支持两种邻域,用4表示4-邻域,8表示8-邻域
第四个参数为输出图像的数据类型,可以选择CV_32S和CV_16U两种
最后一个参数是标记连通域是使用算法的标志,目前只支持Grana(BBDT)和Wu(SAUF)两种算法
这个函数原型所有参数都没有默认值,在调用时需要设置全部参数,增加了使用的复杂程度
因此OpenCV4提供了connectComponents()函数的简易原型,减少了参数数量,以及为部分参数增加了默认值
connectComponents()函数原型2:
int connectComponents(InputArray image,
OutputAray labels,
int connectivity = 8,
int ltype = CV_32S
)
- image:待标记不同连通域的单通道图像,数据类型必须为CV_8U
- labels:标记不同连通域后的输出图像,与输入图像具有相同的尺寸
- connectivity:标记连通域时使用的邻域种类,4表示4-邻域,8表示8-邻域
- ltype:输出图像的数据类型,目前支持CV_32S和CV_16U两种数据类型
示例程序:首先将图像转换成灰度图像,然后将灰度图像二值化为二值图像,之后利用connectComponents()函数对图像进行连通域的统计,根据统计结果,将数字不同的标签设置成不同的颜色,以区分不同的连通域
示例:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
//对图像进行距离变换
Mat img = imread("E:\\CLion\\opencv_xin\\SG\\SG_0.jpg");
if (img.empty())
{
cout << "error" << endl;
return -1;
}
Mat riceBW,rice;
//将图像转成二值图像,同时把黑白区域颜色互换
cvtColor(img,rice,COLOR_BGR2GRAY);
threshold(rice,riceBW,50,255,THRESH_BINARY);
//生成随机颜色,用于区分不同的连通域
RNG rng(10086); //随机数种子
Mat out;
int number = connectedComponents(riceBW,out,8,CV_16U); //统计图像中连通域的个数
vector<Vec3b> colors;
for (int i = 0; i < number; i++)
{
//使用均匀分布的随机数确定颜色
Vec3b vec3 = Vec3b(rng.uniform(0,256),rng.uniform(0,256),rng.uniform(0,256));
colors.push_back(vec3);
//RNG类是opencv里C++的随机数产生器。它可产生一个64位的int随机数。目前可按均匀分布和高斯分布产生随机数
//RNG::uniform( ) :产生一个均匀分布的随机数
//RNG::gaussian( ) : 产生一个高斯分布的随机数
//RNG::uniform(a, b ) 返回一个[a,b)范围的均匀分布的随机数,a,b的数据类型要一致,而且必须是int、float、double中的一种,默认是int
}
//用不同颜色标记不同的连通域
Mat result = Mat::zeros(rice.size(),img.type());
int w = result.cols;
int h = result.rows;
for (int row = 0; row < h; row++)
{
for (int col = 0; col < w; col++)
{
int label = out.at<uint16_t>(row,col);
if(label == 0) //背景为黑色不改变
{
continue;
}
result.at<Vec3b>(row,col) = colors[label];
}
}
//显示结果
imshow("原图",img);
imshow("标记后的图像",result);
waitKey(0);
return 0;
}
注:
//RNG类是opencv里C++的随机数产生器。它可产生一个64位的int随机数。目前可按均匀分布和高斯分布产生随机数
//RNG::uniform( ) :产生一个均匀分布的随机数
//RNG::gaussian( ) : 产生一个高斯分布的随机数
//RNG::uniform(a, b ) 返回一个[a,b)范围的均匀分布的随机数,a,b的数据类型要一致
// 而且必须是int、float、double中的一种,默认是int
connectComponentsWithStats()
虽然connectComponents()函数可以实现图像中多个连通域的统计,但是只能通过标签将图像中不同连通域区分开
无法得到更多的统计信息,有时,我们希望得到每个连通域中心位置或者在图像中标记出连通域所在矩形区域
OpenCV4提供了connectComponentsWithStats()函数用于标记图像中不同连通域的同时统计连通域的位置、面积的信息
connectComponentsWithStats()函数原型1:
int connectComponentsWithStats(InputArray image,
OutputArray labels,
OutputArray stats,
OutputArray centroids,
int connectivity,
int ltype,
int ccltype
)
- image:待标记不同连通域的单通道图像,数据类型必须为CV_8U
- labels:标记不同连通域后的输出图像,与输入图像具有相同的尺寸
- stats:含有不同连通域统计信息的矩阵,矩阵的数据类型为CV_32S,矩阵第i行是标签为i的连通域的统计特性(int读取)
- centroids:每个连通域质心的坐标,数据类型为CV_64F,(double读取)
- connectivity:统计连通域时使用的邻域种类,4表示4-邻域,8表示8-邻域
- ltype:输出图像的数据类型,目前支持CV_32S和CV_16U两种数据类型
- ccltpe:标记连通域使用的算法类型标志
该函数能够在图像中不同连通域标记标签的同时统计每个连通域的中心位置,矩形区域大小,区域面积信息
第三个参数为每个连通域统计信息矩阵,如果图像有N个连通域,那么该参数输出矩阵尺寸为Nx5,每一行分别保存每个连通域的
统计特性
第四个参数为每个连通域质心的坐标,如果有图像有N个连通域,那么输出矩阵尺寸为Nx2,矩阵每一行分别保存x、y坐标
第五个参数是统计连通域时选择的邻域种类,函数支持两种邻域,4表示4-邻域,8表示8-邻域
第六个参数为输出图像的数据类型,可以选择的参数为CV_32S和CV_16U两种
最后一个参数为标记连通域使用的算法,目前只支持Grana(BBDT)和Wu(SAUF)两种算法
connectComponentsWithStats()函数中统计的连通域特性
| 标记参数 | 简记 | 作用 |
|---|---|---|
| CC_STAT_LEFT | 0 | 连通域内最左侧像素的x坐标,水平方向上的包含连通域边界框的开始 |
| CC_STAT_TOP | 1 | 连通域内最上方像素的坐标,垂直方向上的包含连通域边界框的开始 |
| CC_STAT_WIDTH | 2 | 包含连通域边界框的水平长度 |
| CC_STAT_HEIGHT | 3 | 包含连通域边界框的垂直长度 |
| CC_STAT_AREA | 4 | 连通域的面积(以像素为单位) |
| CC_STAT_MAX | 5 | 统计信息种类数目,无实际意义 |
OpenCV4提供了connectComponentsWithStats()函数的简易原型,减少了参数数目,以及增加了默认值
connectComponentsWithStats()函数原型2:
int connectComponentsWithStats(InputArray image,
OutputArray labels,
OutputArray stats,
OutputArray centroids,
int connectivity = 8,
int ltype = CV_32F,
)
该函数与原型1参数意义相同
示例程序:将图像转换成灰度图像,然后将灰度图像化为二值图像,之后利用connectComponentsWithStats()函数对图像
进行连通域的统计,根据统计结果,用不同颜色的矩形将连通域围起来并标记出每个连通域的质心,标记出连通域的标签数字
以区分不同的连通域,最后输出每个连通域的面积
示例:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
//对图像进行距离变换
Mat img = imread("E:\\CLion\\opencv_xin\\SG\\SG_connect.png");
if (img.empty())
{
cout << "error" << endl;
return -1;
}
imshow("原图",img);
Mat rice,riceBW;
//将图像转成二值图像,同时把黑白区域颜色互换
cvtColor(img,rice,COLOR_BGR2GRAY);
threshold(rice,riceBW,50,255,THRESH_BINARY);
//生成随机颜色,用于区分不同的连通域
RNG rng(10086); //随机数种子
Mat out,stats,centroids;
int number = connectedComponentsWithStats(riceBW,out,stats,centroids,8,CV_16U); //统计图像中连通域的个数
vector<Vec3b> colors;
for (int i = 0; i < number; i++)
{
//使用均匀分布的随机数确定颜色
Vec3b vec3 = Vec3b(rng.uniform(0,256),rng.uniform(0,256),rng.uniform(0,256));
colors.push_back(vec3);
//RNG类是opencv里C++的随机数产生器。它可产生一个64位的int随机数。目前可按均匀分布和高斯分布产生随机数
//RNG::uniform( ) :产生一个均匀分布的随机数
//RNG::gaussian( ) : 产生一个高斯分布的随机数
//RNG::uniform(a, b ) 返回一个[a,b)范围的均匀分布的随机数,a,b的数据类型要一致,而且必须是int、float、double中的一种,默认是int
}
//用不同颜色标记不同的连通域
Mat result = Mat::zeros(rice.size(),img.type());
int w = result.cols;
int h = result.rows;
for (int i = 1; i < number; i++)
{
//中心位置
int center_x = centroids.at<double>(i,0);
int center_y = centroids.at<double>(i,1);
//矩形边框
int x = stats.at<int>(i,CC_STAT_LEFT);
int y = stats.at<int>(i,CC_STAT_TOP);
int w = stats.at<int>(i,CC_STAT_WIDTH);
int h = stats.at<int>(i,CC_STAT_HEIGHT);
int area = stats.at<int>(i,CC_STAT_AREA);
//中心位置绘制
circle(img,Point(center_x,center_y),2,Scalar(0,255,0),2,8,0);
//外接矩形
Rect rect(x,y,w,h);
rectangle(img,rect,colors[i],1,8,0);
putText(img, format("%d",i),Point(center_x,center_y),FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,0,255),1);
cout << "number:" << i << ",area:" << area << endl; //format()函数用于格式化字符串。可以接受无限个参数,可以指定顺序。返回结果为字符串。
}
//显示结果
imshow("标记后的图像",img);
waitKey(0);
return 0;
}
注:format()函数用于格式化字符串。可以接受无限个参数,可以指定顺序。返回结果为字符串。