OpenCV学习(二十一) :计算图像连通分量:connectedComponents(),connectedComponentsWithStats()
1、connectedComponents()函数
Connected Components即连通体算法用id标注图中每个连通体,将连通体中序号最小的顶点的id作为连通体的id。如果在图G中,任意2个顶点之间都存在路径,那么称G为连通图,否则称该图为非连通图,则其中的极大连通子图称为连通体,如下图所示,该图中有两个连通体:
计算二值图像中为图像的连通分量标注(标记)
int cv::connectedComponents(
cv::InputArrayn image, // 输入8位单通道(二进制)
cv::OutputArray labels, // 输出标签图
int connectivity = 8, // 4-或8-连接组件
int ltype = CV_32S // 输出标签类型 (CV_32S or CV_16U)
);
2、connectedComponentsWithStats()函数
这是一个重载的成员函数,它与上述函数的不同之处在于它只接受什么参数。
注意0的区域标识的是background,而centroids则对应的是中心点,而label则对应于表示是当前像素是第几个轮廓
int cv::connectedComponentsWithStats (
cv::InputArrayn image, // 输入8位单通道(二进制)
cv::OutputArray labels, // 输出标签地图
cv::OutputArray stats, // 统计量的Nx5矩阵(CV_32S):分别对应各个轮廓的包围框的起始点坐标x,y,
// 各个轮廓的包围框的 width,height和面积:
// [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中心矩阵:
// [ cx0, cy0; ... ; cx(N-1), cy(N-1)]
int connectivity = 8, // 4-或8-连接组件
int ltype = CV_32S // 输出标签类型 (CV_32S or CV_16U)
);
3、示例:
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat src =imread("F:/C++/2. OPENCV 3.1.0/TEST/test5.PNG",1);
if(!src.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; }
imshow( "image", src );
// 转灰度
Mat src_gray;
cvtColor( src, src_gray, CV_BGR2GRAY );
cv::Mat img_edge, labels, img_color, stats,centroids;
// 二值化
cv::threshold(src_gray, img_edge, 125, 255, cv::THRESH_BINARY);
cv::imshow("Image before threshold", img_edge);
// 白色代表有数据,黑色代表没有数据,所以图像输入之前要转换成”黑底白图“
bitwise_not(img_edge,img_edge); // 该函数计算输入数组的逐元素逐位反转:
cv::imshow("Image after threshold", img_edge);
// 计算连通分量
// labels 输出标签图
// stats Nx5矩阵(CV_32S): 分别对应各个轮廓的x,y,width,height和面积
int nccomps = cv::connectedComponentsWithStats ( img_edge, 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) < 200 ) // 面积小于200 的
colors[i] = Vec3b(0,0,0); // 小区域也被涂成黑色
}
//
img_color = Mat::zeros(src.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];
}
cv::imshow("Labeled map", img_color);
waitKey(0);
return 0;
}
结果:
分析:
1、看输出参数stats:Nx5矩阵(CV_32S),其中第1 2 6 个的面积小于200:
2、labels 标签图中:
4、通过findContours(),drawContours()函数计算连通量
在以前,常用的方法是”是先调用 cv::findContours() 函数(传入cv::RETR_CCOMP 标志),随后在得到的连通区域上循环调用 cv::drawContours() “
示例:
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
// 寻找最大的轮廓、按面积排序 函数
vector<vector<Point>> connection_sort(Mat src)
{
vector<vector<Point>>contours;
findContours(src.clone(),contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);
//由于给大的区域着色会覆盖小的区域,所以首先进行排序操作
//冒泡排序算法,由小到大排序
vector<Point> vptmp;
for(size_t i=1;i<contours.size();i++)
{
for(size_t j=contours.size()-1;j>=i;j--)
{
if (contourArea(contours[j]) < contourArea(contours[j-1]))
{
vptmp = contours[j-1];
contours[j-1] = contours[j];
contours[j] = vptmp;
}
}
}
// 绘制 轮廓
Mat draw = Mat::zeros(src.size(),CV_8UC3);
for( int index = 0; index < contours.size(); index ++ ) // 绘制所有内外轮廓
{
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( draw, contours, index, color, 1, LINE_8 ); //CV_FILLED -1
// 添加标记
char text[100];
sprintf(text, "%d", index+1);
RotatedRect box = minAreaRect(contours.at(index)); // 获取中心
putText(draw, text, box.center, FONT_HERSHEY_SIMPLEX, 0.5, cvScalar(0,0,255),1.5);
}
imshow("contours_sort_drawing",draw);
return contours; // 返回排序后的 轮廓集
}
// 寻找并绘制出彩色联通区域
vector<Point> FindBigestContour(Mat src,int& imax, int& imaxcontour)
{
// imax = 0; //代表最大轮廓的序号
// imaxcontour = -1; //代表最大轮廓的大小
vector<vector<Point>>contours,contour_max;
findContours(src.clone(),contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);
for (int i=0;i<contours.size();i++) // 遍历每个轮廓点集
{
int itmp = contourArea(contours[i]);// 计算每个轮廓点集面积
if (imaxcontour < itmp )
{
imax = i;
imaxcontour = itmp;
}
}
// 显示 最大轮廓
Mat draw = Mat::zeros(src.size(),CV_8UC3);
drawContours( draw, contours, imax, Scalar(0,255,0), 1, LINE_8 );
imshow( "Contours_Max", draw );
return contours[imax];
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Mat src =imread("F:/C++/2. OPENCV 3.1.0/TEST/test3.PNG",1);
if(!src.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; }
imshow( "image", src );
// 转灰度
Mat src_gray;
cvtColor( src, src_gray, CV_BGR2GRAY );
src_gray = src_gray >1;
imshow( "src_gray", src_gray );
// 获得 新排序后轮廓、显示
vector<vector<Point>> contours_sort;
contours_sort = connection_sort(src_gray); // 绘制最大轮廓
// 显示 最大轮廓
int imax=0,imax_area=0;
vector<vector<Point>>contour_max;
contour_max.push_back( FindBigestContour(src_gray,imax,imax_area)); // 获得最大轮廓点集
printf(" %d 轮廓为面积最大轮廓=%d (面积)",imax,imax_area);
waitKey(0);
return a.exec();
}
结果: