数字图像处理——第十章 图像分割
數字圖像處理——第十章 圖像分割
文章目錄
- 數字圖像處理——第十章 圖像分割
- 寫在前面
- 1 點、線和邊緣檢測
- 1.1 孤立點的檢測
- 1.2 線檢測
- 1.3 邊緣檢測
- 2 閾值處理
- 2.1 單一全局閾值
- 2.2 自適應閾值
- 3 區域分割
- 3.1 區域生長
- 3.2 區域分裂與聚合
- 4 分水嶺算法
寫在前面
圖像分割——以一幅圖像作為輸入而返回一個或多個區域或亞像素輪廓作為輸出。也就是說為得到圖像中的物體信息,我們必須進行圖像分割,即提取圖像中的感興趣區域。數字圖像處理中圖像分割的四種方法:
- 邊緣檢測:檢測出邊緣,再將邊緣像素連接,構成邊界形成分割,找出目標物體的輪廓,進行目標的分析、識別、測量等。
- 閾值分割:最常用法。有直方圖門限選擇,半閾值選擇圖像分割,迭代閾值
- 邊界方法:直接確定區域邊界,實現分割;有邊界跟蹤法,輪廓提取法。
- 區域法:將各像素劃歸到相應物體或區域的像素聚類方法;有區域增長法等。
1 點、線和邊緣檢測
1.1 孤立點的檢測
孤立點的檢測依賴于二階導數,因此使用拉普拉斯模板。拉普拉斯模板可以將孤立的點檢測出來:這個模板的作用就是當模板中心是孤立點時,模板的響應最強,而在非模板中心時,響應為零。也就是如下圖所示,孤立點的灰度和周圍的像素的灰度很大程度的不同,因此使用這類模板,很容易檢測出這個孤立點。對于一個導數模版,系數之和為零表明在恒定灰度區模版響應將是零。
將上述模板寫成
kernel = np.array([[-1, -1, -1],[-1, 8, -1],[-1, -1, -1]])
result = cv2.filter2D(img, -1, kernel)
1.2 線檢測
線檢測同樣可使用拉普拉斯模板。但拉普拉斯檢測子是各向同性的,因此其響應與方向無關,因此設置四個不同方向的模板。
不同的模板對特定方向上的線感興趣,也就是在不同的情況下使用不同的模板,并對其輸出進行閾值處理。如果對檢測圖像中由給定模板定義的方向上的所有線感興趣,則只需簡單地對該圖像運行這個模板,并對結果的絕對值進行閾值處理,留下的點是有最強響應的點,對于1個像素寬度的線來說,相應的點最接近于模板定義的方向。
1.3 邊緣檢測
邊緣檢測是基于灰度突變來進行圖像分割的最常用的方法。前幾章屢次提到的Canny算子就是邊緣檢測的常用算法。我感覺所有的邊緣檢測算法本質上就是一種濾波算法,區別在于濾波器的選擇,因為濾波的規則是完全一致的。而邊緣檢測概念離不開梯度。圖像梯度即當前所在像素點對于X軸、Y軸的偏導數,所以梯度在圖像處理領域我們可以也理解為像素灰度值變化的速度。
圖中我們可以看到,100與90之間相差的灰度值為10,即當前像素點在X軸方向上的梯度為10,而其它點均為90,則求導后發現梯度全為0,因此我們可以發現在數字圖像處理,因其像素性質的特殊性,微積分在圖像處理表現的形式為計算當前像素點沿偏微分方向的差值,所以實際的應用是不需要用到求導的,只需進行簡單的加減運算。
以Sobel算子為例,下圖SxS_{x}Sx?、SyS_{y}Sy?分別表示對于X軸、Y軸的邊緣檢測算子,從SxS_{x}Sx?算子結構可以很清楚發現,這個濾波器是計算當前像素點右邊與左邊8連通像素灰度值的差值。
sx=[?101?202?101],sy=[121000?1?2?1]s_{x}=\left[\begin{array}{rrr} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{array}\right], s_{y}=\left[\begin{array}{ccc} 1 & 2 & 1 \\ 0 & 0 & 0 \\ -1 & -2 & -1 \end{array}\right] sx?=????1?2?1?000?121????,sy?=???10?1?20?2?10?1????
舉一個很好理解的一維例子:有一個一維數組長度為10:[ 8, 6, 2, 4, 9, 1, 3, 5, 10, 6 ],此時定義一維邊緣檢測算子為[ -1, 0, 1 ],現在我們把邊緣檢測算子放在數組上面進行點積(即對應點相乘之后的和),得到結果為:[ 6, -6, -2, 7, -3, -6, 4, 7, 1, -10],出現負數不要緊,取絕對值得[ 6, 6, 2, 7, 3, 6, 4, 7, 1, 10]。其中數字的大小則表示了當前像素點梯度的模大小,即灰度變化的速度有多大,值越大,我們一定程度上就可以確信當前點為我們所要找的邊緣點,通過一維的例子我們可以更好理解二維的邊緣檢測思想,即沿著X軸、Y軸進行兩次濾波操作。Sobel算子效果如下:
代碼如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
import numba as nb
img = cv2.imread(r' ', 0)gx = np.array([[-1,0,1],[-2,0,2],[-1,0,1]])
gy = np.array([[1,2,1],[0,0,0],[-1,-2,-1]])@nb.jit
def sobel(img):height = img.shape[0]width = img.shape[1]tmp_img = img.copy()for i in range(1,height-1):for j in range(1, width-1):tmpx = np.sum(np.sum(gx * img[i-1:i+2,j-1:j+2]))tmpy = np.sum(np.sum(gy * img[i-1:i+2,j-1:j+2]))tmp_img[i,j] = np.sqrt(tmpx**2 + tmpy **2)return tmp_imgsobel_img_my = sobel(img.copy())
x = cv2.Sobel(img,cv2.CV_16S,1,0)
y = cv2.Sobel(img,cv2.CV_16S,0,1)
absX = cv2.convertScaleAbs(x) # 轉回uint8
absY = cv2.convertScaleAbs(y)
dst = cv2.addWeighted(absX,0.5,absY,0.5,0)plt.figure(dpi = 180)
plt.subplot(131)
plt.title('Origin Image')
plt.imshow(img, cmap = 'gray')
plt.subplot(132)
plt.title('Sobel(Ours)')
plt.imshow(sobel_img_my, cmap = 'gray')
plt.subplot(133)
plt.title('Sobel(cv2.Sobel)')
plt.imshow(dst, cmap = 'gray')
plt.tight_layout()
plt.show()
2 閾值處理
所謂閾值處理,就是對事物進行簡單的劃分。例如60分就算是個閾值,低于60分就是不及格,高于60則是及格。同理可得,一張圖片我們也可以設置閾值分為前景和背景。我們感興趣的一般的是前景部分,所以我們一般使用閾值將前景和背景分割開來,使我們感興趣的圖像的像素值為1,不感興趣的我0,有時一張圖我們會有幾個不同的感興趣區域(不在同一個灰度區域),這時我們可以用多個閾值進行分割,這就是閾值處理。
2.1 單一全局閾值
使用OpenCV庫cv2.threshold( ),這個函數有四個參數,第一個是原圖像矩陣,第二個是進行分類的閾值,第三個是高于(低于)閾值時賦予的新值,第四個是一個方法選擇參數,參數常用的有:
-
cv2.THRESH_BINARY(黑白二值)
-
cv2.THRESH_BINARY_INV(黑白二值翻轉)
-
cv2.THRESH_TRUNC(得到額圖像為多像素值)
-
cv2.THRESH_TOZERO(當像素高于閾值時像素設置為自己提供的像素值,低于閾值時不作處理)
-
cv2.THRESH_TOZERO_INV(當像素低于閾值時設置為自己提供的像素值,高于閾值時不作處理)
代碼如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
import numba as nb
src = cv2.imread(r' ', 0)
# 設置閾值
ThreshValue = 165
# 設置最大像素值
MaxVal = 230
# cv.THRESH_BINARY
returned_thresh_value, dst = cv2.threshold(src, ThreshValue, MaxVal, cv2.THRESH_BINARY)
# cv.THRESH_BINARY_INV
returned_thresh_value1, dst1 = cv2.threshold(src, ThreshValue, MaxVal, cv2.THRESH_BINARY_INV)
# cv.THRESH_TRUNC
returned_thresh_value2, dst2 = cv2.threshold(src, ThreshValue, MaxVal, cv2.THRESH_TRUNC)
# cv.THRESH_TOZERO
returned_thresh_value3, dst3 = cv2.threshold(src, ThreshValue, MaxVal, cv2.THRESH_TOZERO)
# cv.THRESH_TOZERO_INV
returned_thresh_value4, dst4 = cv2.threshold(src, ThreshValue, MaxVal, cv2.THRESH_TOZERO_INV)
plt.figure(dpi = 180)
plt.subplot(231)
plt.title('Origin Image')
plt.imshow(src, cmap = 'gray')
plt.subplot(232)
plt.title('cv2.THRESH_BINARY')
plt.imshow(dst, cmap = 'gray')
plt.subplot(233)
plt.title('cv2.THRESH_BINARY_INV')
plt.imshow(dst1, cmap = 'gray')
plt.subplot(234)
plt.title('cv2.THRESH_TRUNC')
plt.imshow(dst2, cmap = 'gray')
plt.subplot(235)
plt.title('cv2.THRESH_TOZERO')
plt.imshow(dst3, cmap = 'gray')
plt.subplot(236)
plt.title('cv2.THRESH_TOZERO_INV')
plt.imshow(dst4, cmap = 'gray')
plt.tight_layout()
plt.show()
實驗結果分析:單一閾值采用全局閾值,只需要設定一個閾值,整個圖像都和這個閾值比較,方法過于粗暴。當一張圖片存在明顯明暗不同的區域,將會導致二值化后丟失所有細節,因此需要對圖片進行不同區域的二值化才能得到更好的結果。
2.2 自適應閾值
自適應閾值可以看成一種局部性的閾值,通過設定一個區域大小,比較這個點與區域大小里面像素點 的平均值(或者其他特征)的大小關系確定這個像素點的情況。這種方法理論上得到的效果更好,相當于在動態自適應的調整屬于自己像素點的閾值,而不是整幅圖都用一個閾值。
自適應閾值算法的核心是將圖像分割位不同的區域,每個區域都計算閾值。這樣可以更好的處理復雜的圖像。自適應閾值函數定義如下:
cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst=None),參數含義如下:
- src:灰度化的圖片
- maxValue:滿足條件的像素點需要設置的灰度值
- adaptiveMethod:自適應方法。有2種:ADAPTIVE_THRESH_MEAN_C 或 ADAPTIVE_THRESH_GAUSSIAN_C
- thresholdType:二值化方法,可以設置為THRESH_BINARY或者THRESH_BINARY_INV
- blockSize:分割計算的區域大小,取奇數
- C:常數,每個區域計算出的閾值的基礎上在減去這個常數作為這個區域的最終閾值,可以為負數
- dst:輸出圖像,可選
其中,adaptiveMethod的選擇非常關鍵。一種是使用均值的方法,而另外一種是使用高斯加權和的方法。效果如下:
代碼如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
import numba as nb
src = cv2.imread(r' ', 0)
blocksize = 25
C=10
ADAPTIVE_THRESH_MEAN_C = cv2.adaptiveThreshold(src, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, blocksize, C)
ADAPTIVE_THRESH_GAUSSIAN_C = cv2.adaptiveThreshold(src, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, blocksize, C)
plt.figure(dpi = 180)
plt.subplot(131)
plt.title('Origin Image')
plt.imshow(src, cmap = 'gray')
plt.subplot(132)
plt.title('MEAN_C')
plt.imshow(ADAPTIVE_THRESH_MEAN_C, cmap = 'gray')
plt.subplot(133)
plt.title('GAUSSIAN_C')
plt.imshow(ADAPTIVE_THRESH_GAUSSIAN_C, cmap = 'gray')plt.tight_layout()
plt.show()
實驗結果:所謂均值的方法就是以計算區域像素點灰度值的平均值作為該區域所有像素的灰度值,這其實就是一種平滑或濾波作用。而高斯加權和算法是將區域中點(x,y)周圍的像素根據高斯函數加權計算他們離中心點的距離。
3 區域分割
區域分割法彌補閾值分割法的不足,利用空間性質,認為屬于同一區域的像素應具有相似性?;趨^域的分割:區域生長算法和區域分裂與聚合都是屬于基于區域的分割算法。
3.1 區域生長
區域生長算法是根據預先定義的生長準則將像素或子區域組合為更大的區域的過程?;痉椒ㄊ菑囊唤M“種子”點開始,將與種子預先定義的性質相似的那些鄰域像素添加到每個種子上來形成這些生長區域(如特定范圍的灰度或顏色)。
區域生長實現的步驟如下:
-
對圖像順序掃描!找到第1個還沒有歸屬的像素, 設該像素為(x0, y0);
-
以(x0, y0)為中心, 考慮(x0, y0)的4鄰域像素(x, y)如果(x0, y0)滿足生長準則, 將(x, y)與(x0, y0)合并(在同一區域內), 同時將(x, y)壓入堆棧;
-
從堆棧中取出一個像素, 把它當作(x0, y0)返回到步驟2;
-
當堆棧為空時!返回到步驟1;
-
重復步驟1 - 4直到圖像中的每個點都有歸屬時。生長結束。
3.2 區域分裂與聚合
區域分裂合并算法的基本思想是先確定一個分裂合并的準則,即區域特征一致性的測度,當圖像中某個區域的特征不一致時就將該區域分裂成4 個相等的子區域,當相鄰的子區域滿足一致性特征時則將它們合成一個大區域,直至所有區域不再滿足分裂合并的條件為止。當分裂到不能再分的情況時,分裂結束,然后它將查找相鄰區域有沒有相似的特征,如果有就將相似區域進行合并,最后達到分割的作。
4 分水嶺算法
在地理學中,分水嶺是一個山脊,該山脊通過不同的水系來區分排水區域。集水盆地是把水排入河流或水庫的地理區域。分水嶺變換把這些概念應用到灰度圖像處理中,從而解決許多圖像分割問題。
分水嶺分割方法,是一種基于拓撲理論的數學形態學的分割方法,其基本思想是把圖像看作是測地學上的拓撲地貌,圖像中每一點像素的灰度值表示該點的海拔高度,每一個局部極小值及其影響區域稱為集水盆,而集水盆的邊界則形成分水嶺。分水嶺的概念和形成可以通過模擬浸入過程來說明。在每一個局部極小值表面,刺穿一個小孔,然后把整個模型慢慢浸入水中,隨著浸入的加深,每一個局部極小值的影響域慢慢向外擴展,在兩個集水盆匯合處構筑大壩,即形成分水嶺。
分水嶺函數cv2.watershed(img, markers)
代碼如下:
import numpy as np
import cv2
from matplotlib import pyplot as pltsrc = cv2.imread(r' ')
img = src.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)# 消除噪聲
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)# 膨脹
sure_bg = cv2.dilate(opening, kernel, iterations=3)# 距離變換
dist_transform = cv2.distanceTransform(opening, 1, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)# 獲得未知區域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)# 標記
ret, markers1 = cv2.connectedComponents(sure_fg)# 確保背景是1不是0
markers = markers1 + 1# 未知區域標記為0
markers[unknown == 255] = 0markers3 = cv2.watershed(img, markers)
img[markers3 == -1] = [0, 0, 255]plt.figure(dpi = 180)
plt.subplot(241), plt.imshow(cv2.cvtColor(src, cv2.COLOR_BGR2RGB)),
plt.title('Original'), plt.axis('off')
plt.subplot(242), plt.imshow(thresh, cmap='gray'),
plt.title('Threshold'), plt.axis('off')
plt.subplot(243), plt.imshow(sure_bg, cmap='gray'),
plt.title('Dilate'), plt.axis('off')
plt.subplot(244), plt.imshow(dist_transform, cmap='gray'),
plt.title('Dist Transform'), plt.axis('off')
plt.subplot(245), plt.imshow(sure_fg, cmap='gray'),
plt.title('Threshold'), plt.axis('off')
plt.subplot(246), plt.imshow(unknown, cmap='gray'),
plt.title('Unknow'), plt.axis('off')
plt.subplot(247), plt.imshow(np.abs(markers), cmap='jet'),
plt.title('Markers'), plt.axis('off')
plt.subplot(248), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)),
plt.title('Result'), plt.axis('off')
plt.show()
總結
以上是生活随笔為你收集整理的数字图像处理——第十章 图像分割的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数字图像处理——第九章 形态学图像处理
- 下一篇: Python计算机视觉——SIFT特征