概述
- 圖像分割是視覺中非常重要的一個內容
- 我們人類在看周圍世界的時候, 首先會將視野中的目標分成一個一個不同的焦點, 進一步再對每一個目標進行詳細的分析
- 模擬人類視覺,計算機視覺也要對圖像中不同的目標一個一個的進行分割出來
圖像直方圖(灰度直方圖)
- 在左上角的圖像每一個像素格分別對應了該像素的對應灰度,在這裏我們只考慮灰度圖像
- 每個像素對應的灰度幅值是0~255之間,我們可以圍繞着這個圖像做它的灰度直方圖
- 灰度直方圖以灰度爲橫座標,圍繞着每個灰度值在圖像中出現的次數作爲它的縱座標
- 灰度直方圖描述了圖像中灰度的分佈情況,在灰度直方圖裏,波峯位置通常對應這個灰度出現頻次最高的位置
- 波谷位置則是出現頻次最低的位置
- 進一步,我們的縱座標可以不是出現的頻次,而是出現的頻次和所有像素個數的一個比例
- 縱座標使用百分比的好處:所有灰度統計的個數一定是這個灰度像素的個數
- 縱座標使用百分比,那麼這個灰度直方圖的積分就是1,所以這個灰度直方圖就等同於灰度出現的概率密度函數
通過灰度直方圖看圖像的照明效果
- 第一張圖左上角的圖片明顯發暗,灰度直方圖大部分的位置都聚集在了灰度值比較低的位置
- 相反,第一張圖左下角圖片整體較量,灰度直方圖大部分的位置都聚集在亮的區域
- 第二張圖,右上角對比度(圖像亮和暗的部分的比較)比較低的圖像,它的灰度直方圖大部分像素都擠在一個區域中,這說明灰度之間亮和暗沒有區分
- 相反,第二張圖右下角高對比度的圖像,它的亮和暗的圖像都分得比較開,在直方圖上幾乎是平均分佈,我們希望我們的圖像對比度高一些好
- 對比度太高也會存在一個問題:噪聲點會比較突出,我們可以根據這個圖像來提升自己的攝影水平
灰度閾值分割
- 通過灰度直方圖來完成灰度閾值分割
- 基於灰度的閾值分割,假設:圖像中的目標區和背景區之間或者不同目標區之間,存在不同的灰度或平均灰度
- 比如在一個比較亮的背景裏,有一個比較暗的目標,或者目標與背景有一個比較明顯的亮度區分,
- 灰度閾值的基本定義:凡是灰度值包含於z的像素都變成某一灰度值,其他的變成另一個灰度值,則該圖像就以z爲界被分
成兩個區域 - 我們通常把閾值 和 取成最亮的和最暗的,也就是取成0和255
- 在matlib中最亮和最暗是1和0,如果=1和=0,分割後的圖像爲二值圖像
- 對應閾值分割的問題,通常它的灰度直方圖就像在圖裏畫的,從圖裏看,它有兩個明顯的波峯,分別對應的是前景和背景
- 要是人來做,我們會選擇兩個波峯之間的波谷位置,作爲我們要選擇的閾值,來區分背景和前景
- 我們的任務是讓計算機去自動選擇最佳的閾值,我們選擇閾值的標準是讓當前目標和背景之間做到最大化
- 也就是下面要談到的大津算法
大津(Otsu)算法
- 由日本學者大津在1973年的時候提出的
- 他要解決的問題是:確定最佳閾值,使背景和目標之間的類間方差最大(因爲二者差異最大)
- 其中一類的均值是, 另一類是, 整體直方圖是均值是
- 和 對應的是每一類直方圖的面積,也就是像素的個數
- 和 加起來一定等於1(整體), 因爲我們分成了2類
- 由1,2的表達式合併得到了表達式3, 我們的任務是選擇最佳的閾值, 讓類間方差g最大
- 這個問題的難點在於:與相關, 我們的閾值, 並沒有直接出現
- 每次我們閾值取一個值, 都會隨之變化, 同樣, 也會隨着閾值的選擇而變化,這個變化關係不是一個簡單的數學表達式,而是一個函數關係
- 換句話來說,我們的和之間是一個隱函數的關係,我們要求的最大值就很麻煩
- 找一個更加方便的方法來解決問題,至關重要
- 算法實現: 遍歷灰度取值
- 在灰度直方圖上,橫軸對應的灰度值是0 ~ 255
- 我們去掉2頭,也就是1 ~ 254, 在這254個值中,把 取值從1 ~ 254遍歷所有的灰度值
- 把每個對應的的值都取出來
- 最後計算下的最大值,那對應我們的最佳閾值也就知道了
舉例
- 從圖上可出分割效果非常理想
- 在原始圖像中背景是漸變的灰色是因爲光線造成的影響
- 大津算法可以有效的把這個影響去掉
侷限性
- 噪聲的影響造成的雪花點特別多
- 灰度漸變很難確定閾值,在某一中間點上把圖給切開了
- 侷限性如何解決,那就是需要用到區域生長法
區域生長法
- 中間的這個點叫種子點
- 從種子點開始,按照一定準則(如相鄰像素灰度相似性)向周圍擴散 ,將鄰域相似像素加入區域中(也就是種子點周圍每個像素和這個種子點的灰度差異是否小於一個閾值,小於則認爲一類,就直接加入區域)
- 當週圍有多個點之後, 再把多個點作爲我們的種子點,遵循這個原則,一點點兒把這個區域向外擴散,指導最後到達區域的邊界爲止
區域生長實現步驟
- 1.對圖像順序掃描!找到第1個還沒有歸屬的像素, 設該像素爲;
- 2.以爲中心, 考慮的8鄰域像素(x, y),當然也可以考慮它的四鄰域,如果滿足生長準則, 將(x, y) 與 合併, 同時將(x, y)壓入堆棧;
- 3.從堆棧中取出一個像素, 把它當作返回到步驟2;
- 4.當堆棧爲空時,返回到步驟1;
- 5.重複步驟1-4直到圖像中的每個點都有歸屬時,生長結束
- 這是一個遞歸算法
區域生長的兩種方法
- 方法一:首先選擇一個種子點後,再比較它的四鄰域或八臨域像素,看是否和種子點是一類,如果是,那麼進一步以它周圍臨域爲種子點,慢慢向外擴散,直觀來說是一圈一圈的往外擴散,它在數據結構上對應我們樹或圖的廣度優先搜索
-
方法二:首先選擇一個種子點後,再比較它的四鄰域或八臨域像素,看是否和種子點是一類,如果是,那麼不再搜索臨域中的其他像素, 以新的像素爲種子點,進一步往外搜索,就像是錐子一樣,沿着一個方向一直錐到底,直到到達邊界,再回去再從另外一個像素開始,這種方式對應數據結構中的深度優先搜索
-
這兩種方式最終達到的目標是一致的,但是生長的過程可能不同
大津算法與區域生長方法結果比較
- 右側圖是使用區域生長算法得到的圖,兩個結果基本正確,可以避免大津算法的問題
- 從本質上來說,大津算法是一類全局閾值化算法,它的閾值選取是針對整個圖像的效果來取的,當圖像中灰度值發生漸變,或目標表面光照比較複雜,往往得不到太好的效果
- 而區域生長算法是一類局部的算法,我們的準則是基於某個像素和旁邊臨域像素差異,這意味着我們考察相似性的時候,以臨域爲我們的考察範圍,在左下角灰度漸變的圖像中,雖然,灰度值整體漸變,但我們每次比較的都是它的相鄰像素,所以就可以一點點向前推演,最終就可以得到一個相對完整的結果
OpenCV的實現
-
計算灰度直方圖
- c++版本
void calcHist( const Mat* arrays, intnarrays, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false );
- python版本
dst = cv.calcHist( images, channels, mask, histSize, ranges[, hist[, accumulate]] )
- c++版本
-
大津算法計算閾值
- c++版本
double threshold( InputArray src, OutputArray dst, double thresh, double maxval, int type ); // type : THRESH_OTSU
- python版本
retval, dst = cv.threshold( src, thresh, maxval, type[, dst] )
- c++版本
-
漫水填充(區域生長法)
- c++版本
int floodFill( InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 );
- python版本
retval, image, mask, rect = cv.floodFill( image, mask, seedPoint, newVal[, loDiff[, upDiff[, flags]]] )
- c++版本
總結
- 灰度直方圖是圖像中灰度分佈統計圖
- 大津算法是最常用的一類灰度閾值自動選取方式,目標是令類間方差最大
- 區域生長法是另一類有效的分割方法,其核心在於利用相鄰像素間的相似性