two Pass方法连通域检测
原理:
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)不同连通区域不同颜色显示
封装后的代码见我的码云code:https://gitee.com/rxdj/twoPassMethod.git
---------------------------------
业精于勤而荒于嬉 行成于思而毁于随
---------------------------------