影象分析:二值影象連通域標記-基於行程的標記方法
一、前言
二值影象,顧名思義就是影象的亮度值只有兩個狀態:黑(0)和白(255)。二值影象在影象分析與識別中有著舉足輕重的地位,因為其模式簡單,對畫素在空間上的關係有著極強的表現力。在實際應用中,很多影象的分析最終都轉換為二值影象的分析,比如:醫學影象分析、前景檢測、字元識別,形狀識別。二值化+數學形態學能解決很多計算機識別工程中目標提取的問題。
二值影象分析最重要的方法就是連通區域標記,它是所有二值影象分析的基礎,它通過對二值影象中白色畫素(目標)的標記,讓每個單獨的連通區域形成一個被標識的塊,進一步的我們就可以獲取這些塊的輪廓、外接矩形、質心、不變矩等幾何引數。
下面是一個二值影象被標記後,比較形象的顯示效果,這就是我們這篇文章的目標。
二、連通域
在我們討論連通區域標記的演算法之前,我們先要明確什麼是連通區域,怎樣的畫素鄰接關係構成連通。在影象中,最小的單位是畫素,每個畫素周圍有8個鄰接畫素,常見的鄰接關係有2種:4鄰接與8鄰接。4鄰接一共4個點,即上下左右,如下左圖所示。8鄰接的點一共有8個,包括了對角線位置的點,如下右圖所示。
如果畫素點A與B鄰接,我們稱A與B連通,於是我們不加證明的有如下的結論:
如果A與B連通,B與C連通,則A與C連通。
在視覺上看來,彼此連通的點形成了一個區域,而不連通的點形成了不同的區域。這樣的一個所有的點彼此連通點構成的集合,我們稱為一個連通區域。
下面這符圖中,如果考慮4鄰接,則有3個連通區域;如果考慮8鄰接,則有2個連通區域。(注:影象是被放大的效果,影象正方形實際只有4個畫素)。
三、連通區域的標記
連通區域標記演算法有很多種,有的演算法可以一次遍歷影象完成標記,有的則需要2次或更多次遍歷影象。這也就造成了不同的演算法時間效率的差別,在這裡我們介紹2種演算法。
第一種演算法是現在matlab中連通區域標記函式bwlabel中使的演算法,它一次遍歷影象,並記下每一行(或列)中連續的團(run)和標記的等價對,然後通過等價對對原來的影象進行重新標記,這個演算法是目前我嘗試的幾個中效率最高的一個,但是演算法裡用到了稀疏矩陣與Dulmage-Mendelsohn分解演算法用來消除等價對,這部分原理比較麻煩,所以本文裡將不介紹這個分解演算法,取而代這的用圖的深度優先遍歷來替換等價對。
第二種演算法是現在開源庫cvBlob中使用的標記演算法,它通過定位連通區域的內外輪廓來標記整個影象,這個演算法的核心是輪廓的搜尋演算法,這個我們將在文章中詳細介紹。這個演算法相比與第一種方法效率上要低一些,但是在連通區域個數在100以內時,兩者幾乎無差別,當連通區域個數到了103103數量級時,上面的演算法會比該演算法快10倍以上。
四、基於行程的標記
我們首先給出演算法的描述,然後再結合實際影象來說明演算法的步驟。
1,逐行掃描影象,我們把每一行中連續的白色畫素組成一個序列稱為一個團(run),並記下它的起點start、它的終點end以及它所在的行號。
2,對於除了第一行外的所有行裡的團,如果它與前一行中的所有團都沒有重合區域,則給它一個新的標號;如果它僅與上一行中一個團有重合區域,則將上一行的那個團的標號賦給它;如果它與上一行的2個以上的團有重疊區域,則給當前團賦一個相連團的最小標號,並將上一行的這幾個團的標記寫入等價對,說明它們屬於一類。
3,將等價對轉換為等價序列,每一個序列需要給一相同的標號,因為它們都是等價的。從1開始,給每個等價序列一個標號。
4,遍歷開始團的標記,查詢等價序列,給予它們新的標記。
5,將每個團的標號填入標記影象中。
6,結束。
我們來結合一個三行的影象說明,上面的這些操作。
第一行,我們得到兩個團:[2,6]和[10,13],同時給它們標記1和2。
第二行,我們又得到兩個團:[6,7]和[9,10],但是它們都和上一行的團有重疊區域,所以用上一行的團標記,即1和2。
第三行,兩個:[2,4]和[7,8]。[2,4]這個團與上一行沒有重疊的團,所以給它一個新的記號為3;而[2,4]這個團與上一行的兩個團都有重疊,所以給它一個兩者中最小的標號,即1,然後將(1,2)寫入等價對。
全部影象遍歷結束,我們得到了很多個團的起始座標,終止座標,它們所在的行以及它們的標號。同時我們還得到了一個等價對的列表。
按照此演算法寫程式碼如下:
void bwLabelFunc(Mat bwFrame)
{
int width = bwFrame.cols;
int height= bwFrame.rows;
vector<int> stRun, endRun, rowRun, runLabelInit;
int totalRuns = 0;
int lastLineStIdx = 0, lastLineEndIdx = 0;
vector<pair<int,int>> equivalences;
lastLineStIdx = 0;
lastLineEndIdx = 0;
for (int i=0; i<height; i++)
{
for (int j=0; j<width; j++)
{
uchar pelVal = bwFrame.at<uchar>(i,j);
uchar pelValPre, pelValNext; // Get the Pre and Next Pixel Value.
if (j==0) pelValPre = 255;
else pelValPre = bwFrame.at<uchar>(i,j-1);
if (j<width-1) pelValNext = bwFrame.at<uchar>(i,j+1);
else pelValNext = 255;
if (pelValPre==255 && pelVal==0) // start Valid.
{
stRun.push_back(j);
rowRun.push_back(i);
}
if (pelVal==0 && pelValNext==255) // End Valid.
{
endRun.push_back(j);
// Get Label.
int curLabel = -1;
for (int m=lastLineStIdx; m<lastLineEndIdx; m++) // Last Line Valid.
{
int startRunLastLine = stRun[m];
int endRunLastLine = endRun[m];
int startRunCur = stRun[stRun.size()-1];
int endRunCur = endRun[stRun.size()-1];
if (startRunLastLine<=endRunCur+1 && endRunLastLine+1>=startRunCur) // Connection Label.
{
if (curLabel==-1)
{
curLabel = runLabelInit[m];
}
else // Set Connection Equal Label.
{
equivalences.push_back(make_pair(curLabel, runLabelInit[m]));
}
}
}
if (curLabel==-1)
{
curLabel = totalRuns;
totalRuns++;
}
runLabelInit.push_back(curLabel);
}
}
lastLineStIdx = lastLineEndIdx; // Update Last Line's Start/End Run.
lastLineEndIdx = endRun.size();
}
// DAG; Find the Same Connection Label.
int maxLabel = *max_element(runLabelInit.begin(), runLabelInit.end());
vector<vector<bool>> eqTab(totalRuns, vector<bool>(totalRuns, false)); // graph Init.
// Construct Graph.
vector<pair<int, int>>::iterator vecPairIt = equivalences.begin();
while (vecPairIt != equivalences.end())
{
eqTab[vecPairIt->first][vecPairIt->second] = true;
eqTab[vecPairIt->second][vecPairIt->first] = true;
vecPairIt++;
}
vector<int> labelFlag(totalRuns, 0);
vector<vector<int>> equaList;
vector<int> tempList;
// cout << maxLabel << endl;
for (int i = 0; i <= maxLabel; i++)
{
if (labelFlag[i])
{
continue;
}
labelFlag[i] = equaList.size() + 1;
tempList.push_back(i);
// BFS Search Algorithm.
for (vector<int>::size_type j = 0; j < tempList.size(); j++)
{
for (vector<bool>::size_type k = 0; k != eqTab[tempList[j]].size(); k++)
{
if (eqTab[tempList[j]][k] && !labelFlag[k])
{
tempList.push_back(k);
labelFlag[k] = equaList.size() + 1;
}
}
}
equaList.push_back(tempList);
tempList.clear();
}
/*cout << equaList.size() << endl;
for (vector<int>::size_type i = 0; i != runLabels.size(); i++)
{
runLabels[i] = labelFlag[runLabels[i] - 1];
}*/
}
相關推薦
影象分析:二值影象連通域標記
一、前言 二值影象,顧名思義就是影象的亮度值只有兩個狀態:黑(0)和白(255)。二值影象在影象分析與識別中有著舉足輕重的地位,因為其模式簡單,對畫素在空間上的關係有著極強的表現力。在實際應用中,很多影象的分析最終都轉換為二值影象的分析,比如:醫學影象分析、前景檢測、字元識
影象分析:二值影象連通域標記-基於行程的標記方法
一、前言 二值影象,顧名思義就是影象的亮度值只有兩個狀態:黑(0)和白(255)。二值影象在影象分析與識別中有著舉足輕重的地位,因為其模式簡單,對畫素在空間上的關係有著極強的表現力。在實際應用中,很多影象的分析最終都轉換為二值影象的分析,比如:醫學影象分析、前景檢測、字
影象分析:二值影象連通域標記2-基於輪廓的標記
一、前言 二值影象,顧名思義就是影象的亮度值只有兩個狀態:黑(0)和白(255)。二值影象在影象分析與識別中有著舉足輕重的地位,因為其模式簡單,對畫素在空間上的關係有著極強的表現力。在實際應用中,很多影象的分析最終都轉換為二值影象的分析,比如:醫學影象分析、前景檢測、字
OpenCV-二值影象連通域分析
通域分析對於影象處理後面涉及到模式識別的內容來說是基礎 連通區域(Connected Component)一般是指影象中具有相同畫素值且位置相鄰的前景畫素點組成的影象區域(Region,Blob)。連通區域分析(Connected Component Analysis,C
實現基於C語言的二值影象連通域標記演算法
實現基於C語言的二值影象連通域標記演算法 1 #include <stdio.h> 2 #include <stdarg.h> 3 #include <stddef.h> 4 #include <stdlib.h> 5 #includ
【影象處理】利用種子填充法對二值影象進行連通域標記-計算目標中心位置方法2
種子填充法原理 大致演算法如下: 設二值化影象A中,畫素值為255的點是前景,為0的點是背景。A(x, y)為座標(x, y)處的畫素值,遍歷影象的每個畫素: 1、 如果畫素值不等於255,則繼續訪問下一個元素。 2、 如果畫素值為A(x, y) = 255,則新建一
opencv 刪除二值化影象中面積較小的連通域
對於上圖的二值化影象,要去除左下角和右上角的噪點,方法:使用opencv去掉黑色面積較小的連通域。程式碼 CvSeq* contour = NULL; double minarea = 100.0; double tmparea = 0.0;
二值影象連通域標記演算法優化
文章概要 非常感謝☆Ronny丶博主在其博文《影象分析:二值影象連通域標記》中對二值影象連通域的介紹和演算法闡述,讓我這個毫無資料結構演算法底子的小白能夠理解和復現程式碼。本文的目的是基於我自己的理解,對該博文中Two-Pass演算法的一些優化和補充,同時也希望幫助更多像我一樣的人較快地掌握
二值影象:B&W(黑白影象)、 Gray (灰度影象) 、單色影象//Color(彩色影象)
二值影象(binary image),即影象上的每一個畫素只有兩種可能的取值或灰度等級狀態,人們經常用黑白、B&W、單色影象表示二值影象。 B&W黑白影象: 只有黑色和白色,不存在過渡性的灰色,它一個畫素只需要一個二進位制位就能表示出來,即0表示
SSE影象演算法優化系列二十五:二值影象的Euclidean distance map(EDM)特徵圖計算及其優化。 SSE影象演算法優化系列九:靈活運用SIMD指令16倍提升Sobel邊緣檢測的速度(4000*3000的24點陣圖像時間由480ms降低到30ms)
Euclidean distance map(EDM)這個概念可能聽過的人也很少,其主要是用在二值影象中,作為一個很有效的中間處理手段存在。一般的處理都是將灰度圖處理成二值圖或者一個二值圖處理成另外一個二值圖,而EDM演算法確是由一幅二值圖生成一幅灰度圖。其核心定義如下: The definitio
C程式碼二值影象連通區域標記
之前寫過一個C++版本的二值影象連通區域標記函式,當時的直觀結果沒有問題,我也使用了很久,後來才發現其結果是錯的,I'm so sorry! 這裡貼出的是一個經過改進的二值影象連通區域標記函式,目前只支援4連通區域標記,要想做到8連通標記的話,最簡單的方法是先用[1 1 1
實現二值影象連通區標記之區域生長法
連通區標記是最基本的影象處理演算法之一。該演算法中,按從左至右、從上至下的順序,對整幅影象進行掃描,通過比較每個前景畫素的鄰域進行連通區標記,並建立等效標記列表。最後,合併等效標記列表,並再次掃描影象以更新標記。演算法的優點的是通俗易懂,缺點是需要兩次掃描影象,效率不高。區域
基於卷積神經網路特徵圖的二值影象分割
目標檢測是當前大火的一個研究方向,FasterRCNN、Yolov3等一系列結構也都在多目標檢測的各種應用場景或者競賽中取得了很不錯的成績。但是想象一下,假設我們需要通過影象檢測某個產品上是否存在缺陷,或者通過衛星圖判斷某片海域是否有某公司的船隻
利用PIL.ImageOps.invert實現二值影象黑白反轉
利用PIL.ImageOps.invert實現二值影象黑白反轉 import PIL.ImageOps from PIL import Image img = Image.open('D:\\Desktop\\計算機視覺\\image\\0.png') img =
二值影象的腐蝕膨脹原理(附程式碼)
程式碼: #include <iostream> #include<vector> #include<iomanip> using namespace std; #define picX 6 #define picY 6 type
MATLA影象處理之二值化以及灰度處理
首先先來明白幾個概念: 1、彩色影象(RGB):影象的每個畫素點都是由紅(R)、綠(G)、藍(B)三個分量來表示的,每一個分量一般分別介於0-255之間,當然如果每一個顏色分量用更多的位數去表示的話,那
二值影象、灰度影象、彩色影象
二值影象 二值影象(Binary Image),按名字來理解只有兩個值,0和1,0代表黑,1代表白,或者說0表示背景,而1表示前景。其儲存也相對簡單,每個畫素只需要1Bit就可以完整儲存資訊。如果把每個畫素看成隨機變數,一共有N個畫素,那麼二值圖有2的N次方種變化,而8位灰度
C++——bmp二值影象的腐蝕、膨脹、開運算、閉運算
本文實現二值bmp影象的腐蝕、膨脹及開運算、閉運算操作。本文使用白色為前景、黑色為背景的二值圖進行操作:1、腐蝕腐蝕操作是結構原中心在被腐蝕影象中平移填充的過程。影象A被結構元B腐蝕,用集合論來表示如下式:
MATLAB 求兩張大小完全相同二值影象影象的白色區域重疊面積
如下圖,兩副大小完全相同的二值影象,現求取白色區域的重疊面積左側影象為test01.jpg,右側為test02.jpg。思路為1.使用imsubtract將兩副影象進行相減操作,假設為test01-test02,則imsubtract(test01,test02),相減之後如
二值影象處理開運算
應用背景:在前兩篇博文中我們看到腐蝕操作會縮小影象前景、膨脹操作會增大影象前景,經過這兩種變換後圖像的細節也發生了一些變換,如果腐蝕和膨脹同時處理影象會產生什麼效果呢,這個問題就是本文要講的開操作。 基本原理:在二值形態學影象處理中,除了腐蝕和膨脹這兩種一次運算外,還有二次