Two-Pass方法检测连通域的原理可参见这篇博客:http://blog.csdn.net/lichengyu/article/details/13986521。
参考下面动图,一目了然。
代码中标记图的数据类型要注意,如果first pass中标记数多于255,就不要用uchar类型,我直接设置为int类型。
1 #include "opencv2/imgproc/imgproc.hpp"
2 #include "opencv2/highgui/highgui.hpp"
3 #include <map>
4 #include <cassert>
5 #include <iostream>
6
7 using namespace std;
8
9 const int max_size = 1000;
10 int parent[max_size] = { 0 };
11
12 // 找到label x的根节点
13 int Find(int x, int parent[])
14 {
15 assert(x < max_size);
16 int i = x;
17 while (0 != parent[i])
18 i = parent[i];
19 return i;
20 }
21
22 // 将label x 和 label y合并到同一个连通域
23 void Union(int x, int y, int parent[])
24 {
25 assert(x < max_size && y < max_size);
26 int i = x;
27 int j = y;
28 while (0 != parent[i])
29 i = parent[i];
30 while (0 != parent[j])
31 j = parent[j];
32 if (i != j)
33 parent[i] = j;
34 }
35
36 cv::Mat twoPassConnectComponent(cv::Mat &binaryImg)
37 {
38 int imgW = binaryImg.cols;
39 int imgH = binaryImg.rows;
40 int channel = binaryImg.channels();
41 int type = binaryImg.type();
42 // first pass
43 int label = 0;
44
45 cv::Mat dst = cv::Mat::zeros(cv::Size(imgW, imgH), CV_32SC1);
46 for (int y = 0; y < imgH; y++)
47 {
48 for (int x = 0; x < imgW; x++)
49 {
50 if (binaryImg.at<uchar>(y, x) != 0)
51 {
52 int left = (x - 1 < 0) ? 0 : dst.at<int>(y, x - 1);
53 int up = (y - 1 < 0) ? 0 : dst.at<int>(y - 1, x);
54
55 if (left != 0 || up != 0)
56 {
57 if (left != 0 && up != 0)
58 {
59 dst.at<int>(y, x) = min(left, up);
60 if (left <= up)
61 Union(up, left, parent);
62 else if (up<left)
63 Union(left, up, parent);
64 }
65 else
66 dst.at<int>(y, x) = max(left, up);
67 }
68 else
69 {
70 dst.at<int>(y, x) = ++label;
71 }
72 }
73 }
74 }
75
76 //second pass
77 for (int y = 0; y < imgH; y++)
78 {
79 for (int x = 0; x < imgW; x++)
80 {
81 if (binaryImg.at<uchar>(y, x) != 0)
82 dst.at<int>(y, x) = Find(dst.at<int>(y, x), parent);
83 }
84 }
85
86 return dst;
87 }
88
89 cv::Scalar getRandomColor()
90 {
91 uchar r = 255 * (rand() / (1.0 + RAND_MAX));
92 uchar g = 255 * (rand() / (1.0 + RAND_MAX));
93 uchar b = 255 * (rand() / (1.0 + RAND_MAX));
94 return cv::Scalar(b, g, r);
95 }
96
97 cv::Mat showColorLabel(cv::Mat label)
98 {
99 int imgW = label.cols;
100 int imgH = label.rows;
101 std::map<int, cv::Scalar> colors;
102
103 cv::Mat colorLabel = cv::Mat::zeros(imgH, imgW, CV_8UC3);
104 int *pLabel = (int*)label.data;
105 uchar *pColorLabel = colorLabel.data;
106 for (int i = 0; i < imgH; i++)
107 {
108 for (int j = 0; j < imgW; j++)
109 {
110 int idx = (i*imgW + j) * 3;
111 int pixelValue = pLabel[i*imgW + j];
112 if (pixelValue > 0)
113 {
114 if (colors.count(pixelValue) <= 0)
115 {
116 colors[pixelValue] = getRandomColor();
117 }
118 cv::Scalar color = colors[pixelValue];
119 pColorLabel[idx + 0] = color[0];
120 pColorLabel[idx + 1] = color[1];
121 pColorLabel[idx + 2] = color[2];
122 }
123 }
124 }
125
126 return colorLabel;
127 }
128
129 int main()
130 {
131 // 加载图像
132 string imageName = "data/source_images/logo.png";
133 cv::Mat image = cv::imread(imageName, 1);
134 if (!image.data)
135 {
136 cout << "No image data" << endl;
137 getchar();
138 return -1;
139 }
140 //转为灰度图
141 cv::cvtColor(image, image, CV_RGB2GRAY);
142 //阈值化,情景为255,背景为0
143 cv::Mat threshImg;
144 cv::threshold(image, threshImg, 200, 255, cv::THRESH_BINARY);
145 cv::bitwise_not(threshImg, threshImg);
146
147 //连通域检测 two Pass方法标记图像
148 cv::Mat labelImg = twoPassConnectComponent(threshImg);
149
150 //不同连通区域用不同颜色表示
151 cv::Mat colorLabelImg=showColorLabel(labelImg);
152
153 //显示
154 cv::imshow("thresh", threshImg);
155 cv::imshow("label", labelImg*20);
156 cv::imshow("colorLabel", colorLabelImg);
157 cv::waitKey(0);
158 }
使用OpenCV的logo为素材图,如下:
(1)转为灰度图然后阈值化
(2)寻找连通域
(3)不同连通区域不同颜色显示