【机器视觉学习笔记】二值图像连通区域提取算法(C++)
目錄
- 原理
- 二值圖像
- 連通區域(Connected Component)
- 連通區域分析(Connected Component Analysis,Connected Component Labeling)
- 算法:Two-Pass(兩遍掃描法)
- 思路:
- Two-Pass算法的簡單步驟:
- 源碼
- 效果
- 示例一
- 示例二
平臺:Windows 10 20H2
Visual Studio 2015
OpenCV 4.5.3
本文摘自OpenCV-二值圖像連通域分析 —— 青雲-吾道樂途
原理
二值圖像
顧名思義就是圖像的亮度值只有兩個狀態:黑(0)和白(255)。二值圖像在圖像分析與識別中有著舉足輕重的地位,因為其模式簡單,對像素在空間上的關系有著極強的表現力。在實際應用中,很多圖像的分析最終都轉換為二值圖像的分析,比如:醫學圖像分析、前景檢測、字符識別,形狀識別。二值化+數學形態學能解決很多計算機識別工程中目標提取的問題。
二值圖像分析最重要的方法就是連通區域標記,它是所有二值圖像分析的基礎,它通過對二值圖像中白色像素(目標)的標記,讓每個單獨的連通區域形成一個被標識的塊,進一步的我們就可以獲取這些塊的輪廓、外接矩形、質心、不變矩等幾何參數。
連通區域(Connected Component)
一般是指圖像中具有相同像素值且位置相鄰的前景像素點組成的圖像區域(Region,Blob)。
在圖像中,最小的單位是像素,每個像素周圍有8個鄰接像素,常見的鄰接關系有2種:4鄰接與8鄰接。4鄰接一共4個點,即上下左右,如下左圖所示。8鄰接的點一共有8個,包括了對角線位置的點,如下右圖所示。
如果像素點A與B鄰接,我們稱A與B連通,于是我們不加證明的有如下的結論:
如果A與B連通,B與C連通,則A與C連通。
在視覺上看來,彼此連通的點形成了一個區域,而不連通的點形成了不同的區域。這樣的一個所有的點彼此連通點構成的集合,我們稱為一個連通區域。
下面這符圖中,如果考慮4鄰接,則有3個連通區域;如果考慮8鄰接,則有2個連通區域。(注:圖像是被放大的效果,圖像正方形實際只有4個像素)
連通區域分析(Connected Component Analysis,Connected Component Labeling)
是指將圖像中的各個連通區域找出并標記。
連通區域分析是一種在CVPR和圖像分析處理的眾多應用領域中較為常用和基本的方法。例如:
OCR識別中字符分割提取(車牌識別、文本識別、字幕識別等)、
視覺跟蹤中的運動前景目標分割與提取(行人入侵檢測、遺留物體檢測、基于視覺的車輛檢測與跟蹤等)、
醫學圖像處理(感興趣目標區域提取)等等。
也就是說,在需要將前景目標提取出來以便后續進行處理的應用場景中都能夠用到連通區域分析方法,通常連通區域分析處理的對象是一張二值化后的圖像。
算法:Two-Pass(兩遍掃描法)
從連通區域的定義可以知道,一個連通區域是由具有相同像素值的相鄰像素組成像素集合,因此,我們就可以通過這兩個條件在圖像中尋找連通區域,對于找到的每個連通區域,我們賦予其一個唯一的標識(Label),以區別其他連通區域。
兩遍掃描法指的就是通過掃描兩遍圖像,就可以將圖像中存在的所有連通區域找出并標記。
思路:
第一遍掃描時賦予每個像素位置一個label,掃描過程中同一個連通區域內的像素集合中可能會被賦予一個或多個不同label,因此需要將這些屬于同一個連通區域但具有不同值的label合并,也就是記錄它們之間的相等關系;
第二遍掃描就是將具有相等關系的equal_labels所標記的像素歸為一個連通區域并賦予一個相同的label(通常這個label是equal_labels中的最小值)。
Two-Pass算法的簡單步驟:
1)第一次掃描:
訪問當前像素B(x,y),如果B(x,y) == 1:
a、如果B(x,y)的領域中像素值都為0,則賦予B(x,y)一個新的label:
label += 1, B(x,y) = label;
b、如果B(x,y)的領域中有像素值 > 1的像素Neighbors:
(1)將Neighbors中的最小值賦予給B(x,y): B(x,y) = min{Neighbors}
(2)記錄Neighbors中各個值(label)之間的相等關系,即這些值(label)同屬同一個連通區域;
labelSet[i] = { label_m, …, label_n },labelSet[i]中的所有label都屬于同一個連通區域(注:這里可以有多種實現方式,只要能夠記錄這些具有相等關系的label之間的關系即可)
2)第二次掃描:
訪問當前像素B(x,y),如果B(x,y) > 1:
a、找到與label = B(x,y)同屬相等關系的一個最小label值,賦予給B(x,y);
完成掃描后,圖像中具有相同label值的像素就組成了同一個連通區域。
下面這張圖動態地演示了Two-pass算法:
源碼
#include <iostream> #include "opencv2/opencv.hpp"using namespace std; using namespace cv;//------------------------------【兩步法新改進版】---------------------------------------------- // 對二值圖像進行連通區域標記,從1開始標號 void Two_PassNew(const Mat &bwImg, Mat &labImg) {assert(bwImg.type() == CV_8UC1);labImg.create(bwImg.size(), CV_32SC1); //bwImg.convertTo( labImg, CV_32SC1 );labImg = Scalar(0);labImg.setTo(Scalar(1), bwImg);assert(labImg.isContinuous());const int Rows = bwImg.rows - 1, Cols = bwImg.cols - 1;int label = 1;vector<int> labelSet;labelSet.push_back(0);labelSet.push_back(1);//the first passint *data_prev = (int*)labImg.data; //0-th row : int* data_prev = labImg.ptr<int>(i-1);int *data_cur = (int*)(labImg.data + labImg.step); //1-st row : int* data_cur = labImg.ptr<int>(i);for (int i = 1; i < Rows; i++){data_cur++;data_prev++;for (int j = 1; j<Cols; j++, data_cur++, data_prev++){if (*data_cur != 1)continue;int left = *(data_cur - 1);int up = *data_prev;int neighborLabels[2];int cnt = 0;if (left>1)neighborLabels[cnt++] = left;if (up > 1)neighborLabels[cnt++] = up;if (!cnt){labelSet.push_back(++label);labelSet[label] = label;*data_cur = label;continue;}int smallestLabel = neighborLabels[0];if (cnt == 2 && neighborLabels[1]<smallestLabel)smallestLabel = neighborLabels[1];*data_cur = smallestLabel;// 保存最小等價表for (int k = 0; k<cnt; k++){int tempLabel = neighborLabels[k];int& oldSmallestLabel = labelSet[tempLabel]; //這里的&不是取地址符號,而是引用符號if (oldSmallestLabel > smallestLabel){labelSet[oldSmallestLabel] = smallestLabel;oldSmallestLabel = smallestLabel;}else if (oldSmallestLabel<smallestLabel)labelSet[smallestLabel] = oldSmallestLabel;}}data_cur++;data_prev++;}//更新等價隊列表,將最小標號給重復區域for (size_t i = 2; i < labelSet.size(); i++){int curLabel = labelSet[i];int prelabel = labelSet[curLabel];while (prelabel != curLabel){curLabel = prelabel;prelabel = labelSet[prelabel];}labelSet[i] = curLabel;}//second passdata_cur = (int*)labImg.data;for (int i = 0; i < Rows; i++){for (int j = 0; j < bwImg.cols - 1; j++, data_cur++)*data_cur = labelSet[*data_cur];data_cur++;} }//---------------------------------【顏色標記程序】----------------------------------- //彩色顯示 cv::Scalar GetRandomColor() {uchar r = 255 * (rand() / (1.0 + RAND_MAX));uchar g = 255 * (rand() / (1.0 + RAND_MAX));uchar b = 255 * (rand() / (1.0 + RAND_MAX));return cv::Scalar(b, g, r); }void LabelColor(const cv::Mat& labelImg, cv::Mat& colorLabelImg) {int num = 0;if (labelImg.empty() ||labelImg.type() != CV_32SC1){return;}std::map<int, cv::Scalar> colors;int rows = labelImg.rows;int cols = labelImg.cols;colorLabelImg.release();colorLabelImg.create(rows, cols, CV_8UC3);colorLabelImg = cv::Scalar::all(0);for (int i = 0; i < rows; i++){const int* data_src = (int*)labelImg.ptr<int>(i);uchar* data_dst = colorLabelImg.ptr<uchar>(i);for (int j = 0; j < cols; j++){int pixelValue = data_src[j];if (pixelValue > 1){if (colors.count(pixelValue) <= 0){colors[pixelValue] = GetRandomColor();num++;}cv::Scalar color = colors[pixelValue];*data_dst++ = color[0];*data_dst++ = color[1];*data_dst++ = color[2];}else{data_dst++;data_dst++;data_dst++;}}}printf("color num : %d \n", num); }//------------------------------------------【測試主程序】------------------------------------- int main() {cv::Mat binImage = cv::imread("D:\\Work\\OpenCV\\Workplace\\Test_1\\Test.jpg", 0);cv::threshold(binImage, binImage, 127, 255, THRESH_BINARY);cv::imshow("原圖", binImage);cv::Mat labelImg;double time;time = getTickCount();Two_PassNew(binImage, labelImg);time = 1000 * ((double)getTickCount() - time) / getTickFrequency();cout << std::fixed << time << "ms" << endl;//彩色顯示cv::Mat colorLabelImg;LabelColor(labelImg, colorLabelImg);cv::imshow("colorImg", colorLabelImg);double minval, maxval;minMaxLoc(labelImg, &minval, &maxval);cout << "minval" << minval << endl;cout << "maxval" << maxval << endl;cv::waitKey(0);return 0; }效果
示例一
示例二
總結
以上是生活随笔為你收集整理的【机器视觉学习笔记】二值图像连通区域提取算法(C++)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 虚拟现实大潮渐近:Oculus VR、E
- 下一篇: s3c2440移植MQTT