OpenCV连通性原理及应用详解
在图像处理和计算机视觉中,连通性分析是一项基础且重要的技术,用于检测图像中相互连接的像素区域,这些区域通常代表着图像中的独立对象或目标。本文将介绍连通性(Connectivity)的基本原理、常见的连通方式(如 4 连通和 8 连通)、OpenCV 中的相关实现方法以及实际应用场景。
1. 连通性基本原理
在二值图像中,连通性描述的是像素之间是否存在“连接”关系。两种常用的连通性定义如下:
- 4 连通(4-connected):仅考虑水平和垂直方向上相邻的像素。如果一个像素与其上、下、左、右的像素具有相同的值,则认为它们连通。
- 8 连通(8-connected):不仅考虑水平和垂直方向,还考虑对角线方向上的邻域。如果一个像素与其周围 8 个像素中的任意一个具有相同的值,则认为它们连通。
连通性分析的核心任务是将图像中所有相互连接的像素归为同一组,通常称为“连通分量”。每个连通分量可以被赋予一个唯一的标签,以便后续处理和分析。
2. OpenCV 中的连通性分析方法
OpenCV 提供了多种函数来实现连通性分析,其中最常用的函数包括:
2.1 connectedComponents
该函数用于对二值图像进行连通分量标记,并输出一个标签图像。函数原型如下:
int connectedComponents(InputArray image, OutputArray labels, int connectivity = 8, int ltype = CV_32S);
- image:输入二值图像(非零表示前景)。
- labels:输出标签图像,每个连通分量的像素赋予相同的标签。
- connectivity:连通性类型,可以选择 4 或 8(默认 8)。
- ltype:输出标签图像的数据类型,通常为
CV_32S
。
该函数返回连通分量的数目(包括背景)。
2.1.1 参考代码:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 读取灰度图像
Mat gray = imread("E:/image/rice1.jpg", IMREAD_GRAYSCALE);
if (gray.empty()) {
cerr << "图像加载失败!" << endl;
return -1;
}
GaussianBlur(gray, gray, Size(5, 5), 0);
Mat binary;
double otsu_thresh_val = threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
cout << "Otsu 自动选择的阈值为:" << otsu_thresh_val << endl;
Mat outlabs=Mat::zeros(binary.size(),CV_32S);
//包括背景
int number= connectedComponents(binary, outlabs, 8, CV_32S);
cout << "number of rice: " << number-1 << endl;
imshow("原始灰度图像", gray);
imshow("Triangle 二值化", binary);
//显示标签图像
vector<Vec3b> colors(number);
//bk color
colors[0] = Vec3b(0, 0, 0);
for (int i = 1; i < number; i++) {
colors[i] = Vec3b((rand() & 255), (rand() & 255), (rand() & 255));
}
Mat dst = Mat::zeros(binary.size(), CV_8UC3);
int w = binary.cols;
int h = binary.rows;
for (int row = 0; row < h; row++) {
for (int col = 0; col < w; col++) {
int label = outlabs.at<int>(row, col);
dst.at<Vec3b>(row, col) = colors[label];
}
}
imshow("Connected Components", dst);
waitKey(0);
return 0;
}
2.2 connectedComponentsWithStats
在 connectedComponents 的基础上,该函数除了返回连通分量标签外,还计算每个连通分量的统计信息,如区域面积、边界框等。函数原型如下:
int connectedComponentsWithStats(InputArray image, OutputArray labels, OutputArray stats, OutputArray centroids, int connectivity = 8, int ltype = CV_32S);
image
:输入的二值图像(CV_8U 类型),像素值必须是 0 或 255。labels
:输出的标签图,每个连通区域的像素值相同,不同区域的像素值不同(类型由ltype
指定)。stats
:输出的统计信息矩阵(返回值也就是联通个数x5大小),包含每个连通区域的属性(CV_32S
类型)。每行表示一个连通组件,包括以下列:stats[i, 0]
:连通组件的左边界 x 坐标。stats[i, 1]
:连通组件的上边界 y 坐标。stats[i, 2]
:连通组件的宽度。stats[i, 3]
:连通组件的高度。stats[i, 4]
:连通组件的像素数量(面积)。
centroids
:输出的质心坐标矩阵(CV_64F
类型),每个连通区域的中心点坐标(cx, cy)
。connectivity
:连通性类型,可选值:4
:使用 4 连通性。8
:使用 8 连通性(默认)。
ltype
:标签图的数据类型,默认为CV_32S
。
2.2.1 参考代码:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 读取灰度图像
Mat gray = imread("E:/image/rice1.jpg", IMREAD_GRAYSCALE);
if (gray.empty()) {
cerr << "图像加载失败!" << endl;
return -1;
}
GaussianBlur(gray, gray, Size(5, 5), 0);
Mat binary;
double otsu_thresh_val = threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
cout << "Otsu 自动选择的阈值为:" << otsu_thresh_val << endl;
Mat outlabs = Mat::zeros(binary.size(), CV_32S);
//包括背景
Mat labels, stats, centroids;
int nComponents = connectedComponentsWithStats(binary, labels, stats, centroids, 8, CV_32S);
cout << "连通分量数量(包括背景): " << nComponents << endl;
imshow("原始灰度图像", gray);
imshow("Triangle 二值化", binary);
// 将标签图像转换为彩色图像,方便可视化
Mat labelImage = Mat::zeros(labels.size(), CV_8UC3);
RNG rng(12345);
vector<Vec3b> colors(nComponents);
colors[0] = Vec3b(0, 0, 0); // 背景为黑色
for (int i = 1; i < nComponents; i++) {
colors[i] = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
}
for (int r = 0; r < labels.rows; r++) {
for (int c = 0; c < labels.cols; c++) {
int label = labels.at<int>(r, c);
labelImage.at<Vec3b>(r, c) = colors[label];
}
}
// 显示每个连通分量的统计信息(忽略背景)
for (int i = 1; i < nComponents; i++) {
int x = stats.at<int>(i, CC_STAT_LEFT);
int y = stats.at<int>(i, CC_STAT_TOP);
int width = stats.at<int>(i, CC_STAT_WIDTH);
int height = stats.at<int>(i, CC_STAT_HEIGHT);
int area = stats.at<int>(i, CC_STAT_AREA);
int cx = centroids.at<double>(i, 0);
int cy = centroids.at<double>(i, 1);
cout << "组件 " << i << ": 区域大小 = " << area
<< ", 边界框 = [" << x << ", " << y << ", " << width << ", " << height << "]" <<
",中心位置:"<<cx<<","<<cy << endl;
//绘制中心位置
circle(labelImage, Point(cx, cy), 5, Scalar(255, 0, 0), -1);
// 绘制边界框
rectangle(labelImage, Point(x, y), Point(x + width, y + height), Scalar(0, 255, 0), 1);
}
imshow("Connected Components", labelImage);
waitKey(0);
return 0;
}
代码先进行滤波然后二值化,在通过联通性检测确定目标的信息。
3. 连通性分析的主要应用
- 对象计数:通过连通性分析,可以统计图像中独立对象的数量,如细胞计数、车牌检测等。
- 目标分割:在物体检测和形态学处理中,连通性分析可以用于分割独立的目标区域。例如,在医学图像处理(如细胞检测)、目标跟踪等领域均有应用。
- 区域分析:通过提取连通分量的统计信息,可以进一步分析目标的形状、大小、位置和质心等特征。
- 轮廓分析:连通性分析可以结合
findContours
进行轮廓提取,以便进行目标识别、计数等任务。 - **图像降噪:**在图像预处理中,使用连通性分析可以过滤掉过小的连通区域(如噪声点),提高图像的质量。