连通域是指图像中具有相同像素值并且位置相邻的像素组成的区域。连通域分析是指在图像中寻找彼此互相独立的连通域并将其标记出来。
4邻域与8邻域的概念:点 P 0 ( x , y ) P_0(x,y) P0(x,y) 的4邻域为其上下左右4个像素点,其8邻域为上下左右再加上对角线方向的4个点。
根据两个像素相邻定义方式不同,得到的连通区域也不相同,因此,在分析连通域的同时,一定要声明是在哪种邻域条件下分析得到的结果。
OpenCV提供了用于提取图像中不同连通域的 connectedComponent()
函数:
int connectedComponents(
InputArray image,
OutputArray labels, // 标记不同连通域后的输出图像
int connectivity, // 标记连通域时使用的邻域种类,4表示4邻域
int ltype, // 输出图像数据类型:CV_32S、CV_16U
int ccltype // 标记连通域时使用的算法类型
);
int connectedComponents(
InputArray image,
OutputArray labels,
int connectivity = 8,
int ltype = CV_32S
);
enum ConnectedComponentsAlgorithmsTypes {
CCL_DEFAULT = -1, // 8邻域使用SAUF、4邻域使用SAUF
CCL_WU = 0, // 8邻域使用BBDT、4邻域使用SAUF
CCL_GRANA = 1, // 8邻域使用BBDT、4邻域使用SAUF
CCL_BOLELLI = 2,
CCL_SAUF = 3,
CCL_BBDT = 4,
CCL_SPAGHETTI = 5,
};
该函数用于计算二值图像中连通域的个数,并在图像中不同的连通域用不同的数字标签标记,其中标签0表示图像中的背景色。函数返回图像中连通域的数目。
示例代码:
虽然 connectedComponents()
函数可以实现图像中多个连通域的统计,但是只能通过标签将图像中的不同连通域分开,无法得到更多的统计信息。有时,我们希望得到每个连通域中心位置或者在图像中标记出连通域所在的矩形区域, connectedComponents
便无法完成这个任务。
OpenCV中提供了 connectedComponentsWithStatus()
函数用于在标记出图像中不同连通域的同时统计连通域的位置、面积的信息:
int connectedComponentsWithStats(
InputArray image,
OutputArray labels, // 标记不同连通域后的输出图像
OutputArray stats, // 含有不同连通域统计信息的矩阵,CV_32S。矩阵第i行是标签为i的连通域的统计信息。
OutputArray centroids, // 每个连通域质心坐标,CV_64F
int connectivity, // 邻域种类
int ltype, // 输出图像数据类型
int ccltype // 标记连通域使用算法标志,同connectedComponents函数参数
);
int connectedComponentsWithStats(
InputArray image,
OutputArray labels,
OutputArray stats,
OutputArray centroids,
int connectivity = 8,
int ltype = CV_32S
);
第三个参数为每个连通域统计信息矩阵:
enum ConnectedComponentsTypes {
CC_STAT_LEFT = 0, // 连通域内最左侧像素的x坐标,它是水平方向上的包含连通域边界框的开始
CC_STAT_TOP = 1, // 连通域内最上方像素的y坐标,它是垂直方向上的包含连通域边界框的开始
CC_STAT_WIDTH = 2, // 宽
CC_STAT_HEIGHT = 3, // 高
CC_STAT_AREA = 4, // 连通域面积
#ifndef CV_DOXYGEN
CC_STAT_MAX = 5 // 统计信息种类数目,无实际含义
#endif
};
使用:stats.at
。
第四个参数为每个连通域的质心坐标,使用:centroids.at
取得x坐标,centroids.at
取得y坐标。
示例代码:
#include
#include // debug no log
using namespace cv;
using namespace std;
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
//对图像进行距离变换
Mat img = imread("rice.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << 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);
}
//以不同颜色标记出不同的连通域
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 << "\tarea: " << area << endl;
}
//显示结果
imshow("标记后的图像", img);
waitKey(0);
return 0;
}