基于轮廓的连通域标记及应用
基于轮廓的连通域标记
本文在参考了https://www.cnblogs.com/ronny/p/img_aly_01.html
基于轮廓的连通域标记基础上,对其进行了一些应用并对其中内容进行了简单解释
相关函数及参数
连通域标记函数
int bwLabel(const Mat& imgBw, Mat & imgLabeled, vector<vector> &contours);//连通域标记
参数1:输入图像
参数2:输出一个imgLabeled图像,此图像为连通域标记图像
参数3:输出contours轮廓点集
返回值:返回连通域的总数(这里包含了背景,真实计算还需要-1)
显示不同连通域函数
void showColorLabels(Mat& image, int num_labels, Mat & labels);//以不同的颜色显示各个连通域
参数1:输入原始图像
参数2:输入连通域个数
参数3:输入标记后的图像
绘制轮廓函数
void MdrawContours(Mat & image, vector<vector> &contours);//绘制轮廓
参数1:输入原始图像
参数2:输入轮廓点集
具体实现效果
原始的图像这是个3通道的png图像:
经过连通域标记后,再用不同颜色表示出来的图像:
轮廓在连通域标记的时候已经得到了,这里只是将其绘制出来,contours的点集如下所示,使用drawContours函数绘制出的轮廓和下方图像也是一样的。
具体代码实现(C++)
#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
using namespace cv;
using namespace std;
int bwLabel(const Mat& imgBw, Mat & imgLabeled, vector<vector<Point>> &contours);//连通域标记
void showColorLabels(Mat& image, int num_labels, Mat & labels);//以不同的颜色显示各个连通域
void MdrawContours(Mat & image, vector<vector<Point>> &contours);//绘制轮廓
int main(){
Mat image = imread("FigFindCountours2.bmp");//FigFindCountours2.bmp,3.PNG
Mat gray, binary;
//进行灰度化
cvtColor(image, gray, COLOR_BGR2GRAY);
//进行二值化
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat labels;
vector<vector<Point>> contours;
int num_labels = bwLabel(binary, labels, contours);
cout << "连通域数:" << (num_labels - 1) << endl;
cout << "轮廓数:" << contours.size() << endl;
showColorLabels(image, num_labels, labels);
MdrawContours(image, contours);
waitKey(0);
return 0;
}
//基于轮廓的连通域标记
int bwLabel(const Mat& imgBw, Mat & labels, vector<vector<Point>> &contours)
{
// 对图像周围扩充一格
Mat imgClone = Mat(imgBw.rows + 1, imgBw.cols + 1, imgBw.type(), Scalar(0));
imgBw.copyTo(imgClone(Rect(1, 1, imgBw.cols, imgBw.rows)));
labels.create(imgClone.size(), imgClone.type());
labels.setTo(Scalar::all(0));
//vector<vector<Point>> contours
vector<Vec4i> hierarchy;
findContours(imgClone, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);
// RETR_CCOMP模式是找到所有轮廓,层级只有两层(外轮廓和洞,洞里面再有轮廓按照外轮廓算,表现在层级关系上为:父轮廓无父轮廓,子轮廓无子轮廓),注意这里父轮廓只能指定一个子轮廓,但是多个子轮廓可以指定同一个父轮廓
// PS:RETR_CCOMP模式可以对打了lable的图片(比如通过connectcomponent函数或者watershed函数获得的结果)进行轮廓识别
//CHAIN_APPROX_NONE是不做处理(得到的是原始点集)
vector<int> contoursLabel(contours.size(), 0);
int numlab;
numlab = 1;
// 标记外围轮廓
for (vector<vector<Point>>::size_type i = 0; i < contours.size(); i++)
{
if (hierarchy[i][3] >= 0) // 有父轮廓
{
continue;
}
for (vector<Point>::size_type k = 0; k != contours[i].size(); k++)
{
labels.at<uchar>(contours[i][k].y, contours[i][k].x) = numlab;
}
contoursLabel[i] = numlab++;
}
// 标记内轮廓
for (vector<vector<Point>>::size_type i = 0; i < contours.size(); i++)
{
if (hierarchy[i][3] < 0)
{
continue;
}
for (vector<Point>::size_type k = 0; k != contours[i].size(); k++)
{
labels.at<uchar>(contours[i][k].y, contours[i][k].x) = contoursLabel[hierarchy[i][3]];
}
}
// 非轮廓像素的标记
for (int i = 0; i < labels.rows; i++)
{
for (int j = 0; j < labels.cols; j++)
{
if (imgClone.at<uchar>(i, j) != 0 && labels.at<uchar>(i, j) == 0)
{
labels.at<uchar>(i, j) = labels.at<uchar>(i, j - 1);
}
}
}
labels = labels(Rect(1, 1, imgBw.cols, imgBw.rows)).clone(); // 将边界裁剪掉1像素
return numlab;//返回连通域个数
}
//以不同的颜色显示各个连通域
void showColorLabels(Mat& image, int num_labels, Mat & labels){
//以不同的颜色显示各个连通域
cv::RNG rng(12345);
vector<Vec3b> colors(num_labels);
// background color
colors[0] = Vec3b(0, 0, 0);
// object color
for (int i = 1; i < num_labels; i++) {
colors[i] = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
}
// render result
Mat dst = Mat::zeros(image.size(), image.type());
int w = image.cols;
int h = image.rows;
for (int row = 0; row < h; row++) {
for (int col = 0; col < w; col++) {
int label = labels.at<uchar>(row, col);
if (label == 0) continue;
dst.at<Vec3b>(row, col) = colors[label];
}
}
imshow("ccla-demo", dst);
//imwrite("ccla_dst.png", dst);
}
//绘制轮廓模块
void MdrawContours( Mat & image, vector<vector<Point>> &contours){
//绘制轮廓
cv::Mat imageContours = Mat::zeros(image.size(), CV_8UC1);
cv::Mat Contours = Mat::zeros(image.size(), CV_8UC1);
//绘制,contours.size()代表轮廓个数。
for (int i = 0; i<contours.size(); i++)
{
//contours[i]代表的是第i个轮廓,
//contours[i].size()代表的是第i个轮廓上所有的像素点数。
for (int j = 0; j<contours[i].size(); j++)
{
//绘制出contours向量内所有的像素点
Point P = Point(contours[i][j].x, contours[i][j].y);
Contours.at<uchar>(P) = 255;
}
//输出hierarchy向量内容
//cout << "向量hierarchy第" << i << " 个元素内容:"
// << hierarchy[i] << endl;
//绘制轮廓
drawContours(imageContours, contours, i, Scalar(255), 1, 8);
}
imshow("Point of Contours", Contours); //显示所有轮廓点集
imshow("Contour Image", imageContours); //显示轮廓
}