基于opencv的cv::connectedComponentsWithStats()的连通域分析标记算法:
一、函数介绍:
在OpenCV3中有了新的专门的函数 cv::connectedComponents() 和函数 cv::connectedComponentsWithStats(); 来做连通域分析,如果需要获取连通域的具体状态信息,则用第二种。
函数原型:
int cv::connectedComponents (
cv::InputArray image, // input 8-bit single-channel (binary)
cv::OutputArray labels, // output label map
int connectivity = 8, // 4- or 8-connected components
int ltype = CV_32S // Output label type (CV_32S or CV_16U)
);
int cv::connectedComponentsWithStats (
cv::InputArray image, // input 8-bit single-channel (binary)
cv::OutputArray labels, // output label map
cv::OutputArray stats, // Nx5 matrix (CV_32S) of statistics:
// [x0, y0, width0, height0, area0;
// ... ; x(N-1), y(N-1), width(N-1),
// height(N-1), area(N-1)]
cv::OutputArray centroids, // Nx2 CV_64F matrix of centroids:
// [ cx0, cy0; ... ; cx(N-1), cy(N-1)]
int connectivity = 8, // 4- or 8-connected components
int ltype = CV_32S // Output label type (CV_32S or CV_16U)
);
参数解析:
(1) image一般需要是一个二值灰度图像(可以用阈值分割来得到)
(2) labels是一张0到total-1的灰度值的图像,比如背景的灰度值为0,第一块的区域的灰度值为1,第total-1块的灰度值为total-1,具体长这样:
(3) stats是一个存储连通域的状态信息的二维矩阵[total,5],第一行存储的是背景,后面的每一行代表着一个连通域的信息:[x,y,width,height,area],x,y是连通域的位置,width、height是连通域外接矩形的宽和高,area是连通域的具体的像素个数,也就是连通域的面积。具体长这样:
(4) centroids返回的也是一个二维数组[total,2],存储着连通域的质点的位置[ cx0, cy0; … ; cx(N-1), cy(N-1)];
(5) connectivity默认为8领域,可以选择为4邻域;
二、实战效果:
原图和阈值分割后:
连通域分析后:
三、C++代码:
//输入一张二值灰度图像ostuImage,和连通域最小面积area_min ,当背景为白色时,需要key=1,“黑白颠倒”才能进行连通域分析
void connectedAnalysis(Mat ostuImage,int area_min ,bool key){
if (key) {
bitwise_not(ostuImage, ostuImage);//黑白颠倒
}
//imshow("ostuImage_not", ostuImage);
Mat labels, stats, centroids, img_color;
int nccomps = connectedComponentsWithStats(ostuImage, labels, stats, centroids);
cout << "连通区域总数: " << nccomps << endl;
vector<cv::Vec3b> colors(nccomps + 1);
colors[0] = Vec3b(0, 0, 0); // 背景为黑色
for (int i = 1; i < nccomps; i++) {
colors[i] = Vec3b(rand() % 256, rand() % 256, rand() % 256);//连通域颜色随机
if (stats.at<int>(i, cv::CC_STAT_AREA) < area_min) //如果小于最小面积,也认为不是连通域,归类到背景为黑色
colors[i] = Vec3b(0, 0, 0);
}
img_color = Mat::zeros(ostuImage.size(), CV_8UC3);
for (int y = 0; y < img_color.rows; y++)
for (int x = 0; x < img_color.cols; x++)
{
int label = labels.at<int>(y, x);
CV_Assert(0 <= label && label <= nccomps);
img_color.at<cv::Vec3b>(y, x) = colors[label];
}
imshow("Labeled map", img_color);
}
main()函数:
#include <opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <ctime>
using namespace cv;
using namespace std;
int ostu(Mat src);//阈值分割函数,上一篇博客有介绍
Mat cutImage(Mat src, int x, int y);//分块的阈值分割函数,上一篇博客有介绍,这里不使用
void connectedAnalysis(Mat ostuImage,int area_min,bool key=1);//连通域分析
int main()
{
Mat src = imread("C:/Users/***/Desktop/blobs.tif");//读取图像
if (src.empty()) {
cout << "error!" << endl;
return 0;
}
imshow("src", src);
if (src.channels() > 1) {
cvtColor(src, src, COLOR_RGB2GRAY);
}
int k;//阈值
k = ostu(src);
Mat ostuImage;//分割图像
threshold(src, ostuImage, k, 255, THRESH_BINARY);
imshow("ostuImage", ostuImage); //背景为白色
connectedAnalysis(ostuImage, 50 ,1);
waitKey(0);
return 0;
}