图像的连通域是指图像中具有相同像素值并且位置相邻的像素组成的区域,连通域分析是指在图像中寻找出彼此互相独立的连通域并将其标记出来。
一般情况下,一个连通域内只包含一个像素值,因此为了防止像素值波动对提取不同连通域的影响,连通域分析常处理的是二值化后的图像。
4-邻域和8-邻域:
常用的图像邻域分析法有两遍扫描法和种子填充法。两遍扫描法会遍历两次图像,第一次遍历图像时会给每一个非0像素赋予一个数字标签,当某个像素的上方和左侧邻域内的像素已经有数字标签时,取两者中的最小值作为当前像素的标签,否则赋予当前像素一个新的数字标签。第一次遍历图像的时候同一个连通域可能会被赋予一个或者多个不同的标签。
种子填充法源于计算机图像学,常用于对某些图形进行填充。该方法首先将所有非0像素放到一个集合中,之后在集合中随机选出一个像素作为种子像素,根据邻域关系不断扩充种子像素所在的连通域,并在集合中删除掉扩充出的像素,直到种子像素所在的连通域无法扩充,之后再从集合中随机选取一个像素作为新的种子像素,重复上述过程直到集合中没有像素。
CV_EXPORTS_AS(connectedComponentsWithAlgorithm) int connectedComponents(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:标记连通域时使用的算法类型标志,可以选择的参数及含义在表中给出
该函数用于计算二值图像中连通域的个数,并在图像中将不同的连通域用不同的数字标签标记出,其中标签0表示图像中的背景区域,同时函数具有一个int类型的返回数据,用于表示图像中连通域的数目。函数的第一个参数是待标记连通域的输入图像,函数要求输入图像必须是数据类型为CV_8U的单通道灰度图像,而且最好是经过二值化的二值图像。函数第二个参数是标记连通域后的输出图像,图像尺寸与第一个参数的输入图像尺寸相同,图像的数据类型与函数的第四个参数相关。函数第三个参数是统计连通域时选择的邻域种类,函数支持两种邻域,分别用4表示4-邻域,8表示8-邻域。函数第四个参数为输出图像的数据类型,可以选择的参数为CV_32S和CV_16U两种。函数的最后一个参数是标记连通域时使用算法的标志,可以选择的参数及含义在表给出,目前只支持Grana(BBDT)和Wu(SAUF)两种算法。
OpenCV 还给出了简单的函数形式
int connectedComponents(InputArray image, OutputArray labels,
int connectivity = 8, int ltype = CV_32S);
- image:待标记不同连通域的图像单通道,数据类型必须为CV_8U。
- labels:标记不同连通域后的输出图像,与输入图像具有相同的尺寸。
- connectivity:标记连通域时使用的邻域种类,4表示4-邻域,8表示8-邻域,默认参数为8。
- ltype:输出图像的数据类型,目前支持CV_32S和CV_16U两种数据类型,默认参数为CV_32S。
该函数原型只有四个参数,前两个参数分别表示输入图像和输出图像,第三个参数表示统计连通域时选择的邻域种类,分别用4表示4-邻域,8表示8-邻域,参数的默认值为8。最后一个参数表示输出图像的数据类型,可以选择的参数为CV_32S和CV_16U两种,参数的默认值为CV_32S。该函数原型有两个参数具有默认值,在使用时最少只需要两个参数,极大的方便了函数的调用。
进一步统计每个连通域的中心位置、矩形区域大小、区域面积等信息
复杂的
CV_EXPORTS_AS(connectedComponentsWithStatsWithAlgorithm) int connectedComponentsWithStats(InputArray image, OutputArray labels,
OutputArray stats, OutputArray centroids,
int connectivity, int ltype, int ccltype);
- image:待标记不同连通域的单通道图像,数据类型必须为CV_8U。
- labels:标记不同连通域后的输出图像,与输入图像具有相同的尺寸。
- stats:含有不同连通域统计信息的矩阵,矩阵的数据类型为CV_32S。矩阵中第i行是标签为i的连通域的统计特性,存储的统计信息种类在表6-4中给出。
- centroids:每个连通域的质心坐标,数据类型为CV_64F。
- connectivity:标记连通域时使用的邻域种类,4表示4-邻域,8表示8-邻域。
- ltype:输出图像的数据类型,目前支持CV_32S和CV_16U两种数据类型。
- ccltype:标记连通域使用的算法类型标志,可以选择的参数及含义在表中给出。
该函数能够在图像中不同连通域标记标签的同时统计每个连通域的中心位置、矩形区域大小、区域面积等信息。函数的前两个参数含义与connectedComponents()函数的前两个参数含义一致,都是输入图像和输出图像。函数的第三个参数为每个连通域统计信息矩阵,如果图像中有N个连通域,那么该参数输出的矩阵尺寸为N×5,矩阵中每一行分别保存每个连通域的统计特性,详细的统计特性在表中给出,如果想读取包含第i个连通域的边界框的水平长度,可以通过stats.at(i, CC_STAT_WIDTH)或者stats.at(i, 0)进行读取。函数的第四个参数为每个连通域质心的坐标,如果图像中有N个连通域,那么该参数输出的矩阵尺寸为N×2,矩阵中每一行分别保存每个连通域质心的x坐标和y坐标,可以通过centroids.at(i, 0)和 centroids.at(i, 1) 分别读取第i个连通域质心的x坐标和y坐标。函数第五个参数是统计连通域时选择的邻域种类,函数支持两种邻域,分别用4表示4-邻域,8表示8-邻域。函数第六个参数为输出图像的数据类型,可以选择的参数为CV_32S和CV_16U两种。函数的最后一个参数是标记连通域使用的算法,可以选择的参数在上表给出,目前只支持Grana(BBDT)和Wu(SAUF)两种算法。
简单的
int connectedComponentsWithStats(InputArray image, OutputArray labels,
OutputArray stats, OutputArray centroids,
int connectivity = 8, int ltype = CV_32S);
- image:待标记不同连通域的单通道图像,数据类型必须为CV_8U。
- labels:标记不同连通域后的输出图像,与输入图像具有相同的尺寸。
- stats:不同连通域的统计信息矩阵,矩阵的数据类型为CV_32S。矩阵中第i行是标签为i的连通域的统计特性,存储的统计信息种类在表6-4中给出。
- centroids:每个连通域的质心坐标,数据类型为CV_64F。
- connectivity:标记连通域时使用的邻域种类,4表示4-邻域,8表示8-邻域,默认参数值为8。
- ltype:输出图像的数据类型,目前只支持CV_32S和CV_16U这两种数据类型,默认参数值为CV_32S。
该函数原型只有六个参数,前两个参数分别表示输入图像和输出图像,第三个参数表示每个连通域的统计信息,第四个参数表示每个连通域的质心位置。后两个参数分别表示统计连通域时选择的邻域种类,分别用4表示4-邻域,8表示8-邻域,参数的默认值为8。最后一个参数表示输出图像的数据类型,可以选择的参数为CV_32S和CV_16U两种,参数的默认值为CV_32S。该函数原型有两个参数具有默认值,在使用时最少只需要四个参数,极大的方便了函数的调用。
简单示例
//
// Created by smallflyfly on 2021/6/16.
//
#include "opencv2/opencv.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main() {
Mat im = imread("rice.jfif");
Mat gray;
cvtColor(im, gray, CV_BGR2GRAY);
// resize(im, im, Size(0, 0), 0.5, 0.5);
imshow("im", im);
Mat im1;
threshold(gray, im1, 125, 255, THRESH_BINARY);
imshow("im1", im1);
// waitKey(0);
RNG rng(10010);
Mat out;
int num = connectedComponents(im1, out, 8, CV_16U);
vector<Vec3b> colors;
for (int i=0; i<num; i++) {
// 使用均匀分布的随机确定颜色
Vec3b vec = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
colors.push_back(vec);
}
Mat result = Mat::zeros(im.size(), im.type());
for (int i = 0; i < im.rows; ++i) {
for (int j = 0; j < im.cols; ++j) {
int label = out.at<uint16_t>(i, j);
if (label == 0) {
continue;
}
result.at<Vec3b>(i, j) = colors[label];
}
}
imshow("result", result);
Mat labels, stats, centroids;
int count = connectedComponentsWithStats(im1, labels, stats, centroids, 8);
cout << count << endl;
for (int i = 1; i < count; ++i) {
int x = centroids.at<double>(i, 0);
int y = centroids.at<double>(i, 1);
cout << x << " " << y << endl;
circle(im, Point(x, y), 2, Scalar(0, 0, 255), -1);
int xmin = stats.at<int>(i, CC_STAT_LEFT);
int ymin = 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);
Rect rect(xmin, ymin, w, h);
rectangle(im, rect, Scalar(0, 255, 255), 2);
putText(im, to_string(i), Point(x+5, y), FONT_HERSHEY_SCRIPT_SIMPLEX, 1, Scalar(0, 0, 255), 2);
}
imshow("im", im);
waitKey(0);
destroyAllWindows();
return 0;
}
简单双色图效果比较明显 百度图片搜的