本文主要介紹圖像分割基于閾值處理的一些基本方法。
一、基于閾值的分割方法
1.1 固定閾值法——直方圖雙峰法
該方法基于圖像直方圖上出現(xiàn)的雙峰現(xiàn)象。當一個圖像有雙峰現(xiàn)象時,其直方圖會出現(xiàn)兩個峰,分別對應圖像中兩種不同的顏色或亮度區(qū)域。這時我們可以使用直方圖雙峰法來自動確定合適的閾值。
其基本思路如下:
1. 計算圖像的灰度直方圖。
2. 根據直方圖的兩個峰的位置,計算出兩個峰之間的閾值,作為圖像的閾值。
3. 根據計算出的閾值對圖像進行二值化處理,將圖像分成目標和背景兩個部分。
注意:該方法只適用于存在雙峰現(xiàn)象的圖像,對于單峰或峰不明顯的圖像,則需要使用其他的閾值分割方法。
使用cv2.threshold(src, thresh, maxval, type)函數。
常用的閾值處理方法有:
1.THRESH_BINARY 二值閾值化該方法將像素值與設定的閾值進行比較,若像素值大于等于閾值,則將該像素值設為最大值,否則設為0。可以用于處理灰度圖像與彩色圖像。
2.THRESH_BINARY_INV 反二值閾值化該方法將像素值與設定的閾值進行比較,若像素值小于閾值,則將該像素值設為最大值,否則設為0。可以用于處理灰度圖像與彩色圖像。
3.THRESH_TRUNC 截斷閾值化該方法將像素值與設定的閾值進行比較,若像素值大于等于閾值,則將該像素值設為閾值,否則不更改。可以用于處理灰度圖像。
4.THRESH_TOZERO 零閾值化該方法將像素值與設定的閾值進行比較,若像素值大于等于閾值,則不更改,否則設為0。可以用于處理灰度圖像。
5.THRESH_TOZERO_INV 反零閾值化該方法將像素值與設定的閾值進行比較,若像素值小于等于閾值,則不更改,否則設為0。可以用于處理灰度圖像
代碼:
# 直方圖雙峰法import cv2import numpy as npfrom matplotlib import pyplot as plt
# 讀取圖像(閾值處理方法不同)img = cv2.imread('house.tif',0)x,img1 = cv2.threshold(img,150,255,cv2.THRESH_BINARY)x,img2 = cv2.threshold(img,150,255,cv2.THRESH_TRUNC)x,img3 = cv2.threshold(img,150,255,cv2.THRESH_TOZERO)
# 顯示原始圖像和恢復后的圖像plt.figure(figsize=(20, 20))plt.subplot(221), plt.imshow(img, cmap='gray')plt.title('Input Image'), plt.xticks([]), plt.yticks([])plt.subplot(222), plt.imshow(img1, cmap='gray')plt.title('seg img THRESH_BINARY'), plt.xticks([]), plt.yticks([])plt.subplot(223), plt.imshow(img2, cmap='gray')plt.title('seg img THRESH_TRUNC'), plt.xticks([]), plt.yticks([])plt.subplot(224), plt.imshow(img3, cmap='gray')plt.title('seg img THRESH_TOZERO'), plt.xticks([]), plt.yticks([])plt.show()
這種全局閾值處理方法缺點是對噪聲的處理的效果并不好,需要進行平滑處理。
import cv2import randomimport numpy as npfrom matplotlib import pyplot as pltimg = cv2.imread('house.tif',0)
# 加上高斯噪聲noise = np.random.normal(0,100,size=img.size).reshape(img.shape[0],img.shape[1])img1 = img + noiseimg1 = np.clip(img1,0,255)
# 平滑處理img2 = cv2.GaussianBlur(img1,(5,5),0)
# 直方圖雙峰法x,img3 = cv2.threshold(img,150,255,cv2.THRESH_BINARY)x,img4 = cv2.threshold(img1,150,255,cv2.THRESH_BINARY)x,img5 = cv2.threshold(img2,150,255,cv2.THRESH_BINARY)
# 顯示原始圖像和恢復后的圖像plt.figure(figsize=(20, 20))plt.subplot(221), plt.imshow(img, cmap='gray')plt.title('Input Image'), plt.xticks([]), plt.yticks([])plt.subplot(222), plt.imshow(img1, cmap='gray')plt.title('noise img'), plt.xticks([]), plt.yticks([])plt.subplot(223), plt.imshow(img4, cmap='gray')plt.title('seg noise img '), plt.xticks([]), plt.yticks([])plt.subplot(224), plt.imshow(img5, cmap='gray')plt.title('seg blur img '), plt.xticks([]), plt.yticks([])plt.show()
1.2 迭代閾值圖像分割
這一種自適應的圖像分割方法,其主要思想是通過對圖像的灰度值進行迭代,不斷調整閾值使得分割更加準確。
分割步驟如下:
1. 隨機選取一個初始閾值(可以是圖像的平均灰度值)
2. 根據該閾值將圖像分割成兩部分:小于等于閾值的部分為前景,大于閾值的部分為背景
3. 計算前景和背景的平均灰度值
4. 將前景和背景的平均灰度值的平均值作為新的閾值
5. 如果新的閾值和原來的閾值相同,則分割結束。否則,回到第2步,重復迭代直到收斂
代碼:
# ?迭代閾值圖像分割法import numpy as npfrom PIL import Image
# 讀取圖像img = Image.open('house.tif').convert('L')width, height = img.sizepixels = img.load()
# 初始化閾值threshold = 150delta = 1while delta > 0: ? ?# 根據閾值將圖像分割 ? ?foreground = [] ? ?background = [] ? ?for y in range(height): ? ? ? ?for x in range(width): ? ? ? ? ? ?if pixels[x, y] > threshold: ? ? ? ? ? ? ? ?background.append(pixels[x, y]) #大于閾值的部分為背景 ? ? ? ? ? ?else: ? ? ? ? ? ? ? ?foreground.append(pixels[x, y]) #小于等于閾值的部分為前景 ? ?avr_foreground = sum(foreground) // len(foreground) ? ?avr_background = sum(background) // len(background) ? ?# 更新閾值 ? ?new_threshold = (avr_foreground + avr_background) // 2 ? ?delta = abs(new_threshold - threshold) ? ?threshold = new_threshold
# 重新分割圖像for y in range(height): ? ?for x in range(width): ? ? ? ?if pixels[x, y] > threshold: ? ? ? ? ? ?pixels[x, y] = 255 ? ? ? ?else: ? ? ? ? ? ?pixels[x, y] = 0
# 顯示分割后的圖像img.show('result.jpg')
優(yōu)點: ?1.迭代閾值圖像分割相對于靜態(tài)閾值分割而言,具有更高的實用性和魯棒性。 ?2.該方法能夠有效地應對光照變化、背景復雜多樣等情況下的圖像分割問題。缺點: ?對于大尺寸圖像,迭代閾值圖像分割的計算速度可能成為其一個缺點。
1.3 自適應閾值圖像分割
1.3.1 常規(guī)方法
分割步驟如下:
1. 針對輸入的灰度圖像,定義一個大小為 N × N N×NN×N 的窗口,在窗口中央選擇一個像素點作為處理的核心像素點。
2. 根據窗口內的像素信息計算局部閾值,作為當前像素的閾值,這個閾值可以是均值、中值、模式等統(tǒng)計量。
3. 將處理完的像素點和當前像素點的局部閾值進行比較,大于等于閾值的像素點設置為白色,小于閾值的像素點設置為黑色,分割出新的二值化圖像。
4. 將窗口向圖像的其他區(qū)域移動,繼續(xù)對各個區(qū)域進行高斯自適應閾值分割,直到圖像的所有區(qū)域都被分割為二值化圖像。
代碼:
# 自適應閾值圖像分割import numpy as npfrom PIL import Image
# 圖像預處理img = Image.open('house.tif').convert('L')width, height = img.sizepixels = img.load()block_size = 11 ?# 設置塊大小
# 分割圖像并計算局部閾值for y in range(0, height, block_size): ? ?for x in range(0, width, block_size): ? ? ? ?# 獲取局部區(qū)域 ? ? ? ?region = [] ? ? ? ?for j in range(y, y+block_size): ? ? ? ? ? ?for i in range(x, x+block_size): ? ? ? ? ? ? ? ?if i < width and j < height: ? ? ? ? ? ? ? ? ? ?region.append(pixels[i, j]) ? ? ? ?# 計算局部區(qū)域平均值作為閾值 ? ? ? ?threshold = sum(region) // len(region) ? ? ? ?# 對區(qū)域進行二值化處理 ? ? ? ?for j in range(y, y+block_size): ? ? ? ? ? ?for i in range(x, x+block_size): ? ? ? ? ? ? ? ?if i < width and j < height: ? ? ? ? ? ? ? ? ? ?if pixels[i, j] > threshold: ? ? ? ? ? ? ? ? ? ? ? ?pixels[i, j] = 255 ? ? ? ? ? ? ? ? ? ?else: ? ? ? ? ? ? ? ? ? ? ? ?pixels[i, j] = 0
#顯示分割后的圖像img.show('result.jpg')
也可以直接調用cv2.adaptiveThreshold()函數實現(xiàn)。
import cv2# 讀取原始圖像img = cv2.imread('house.tif', 0)# 自適應閾值分割img_thresh = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)# 顯示結果cv2.imshow('Original Image', img)cv2.imshow('Adaptive Thresholding', img_thresh)cv2.waitKey(0)cv2.destroyAllWindows()
1.3.2 大津法(OTSU)
大津法基于以下觀察結果:當圖像被分為多個區(qū)域時,如果區(qū)域內的像素值差異較小,區(qū)域的均值方差就會較小。而當區(qū)域內像素值差異較大時,區(qū)域的均值方差就會較大。因此,局部圖像的全局最佳閾值是能使類間方差最大的那個灰度級。
步驟:
代碼:
import cv2
# 讀取圖片img = cv2.imread('house.tif', 0)
# 大津法閾值分割ret, img_thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 顯示結果cv2.imshow('Original Image', img)cv2.imshow('OTSU Thresholding', img_thresh)cv2.waitKey(0)cv2.destroyAllWindows()
優(yōu)點: ?多種檢測任務效果較好。缺點: ?計算量比較大,適用于數據量較小的圖像分割場合。
參考文獻:
[1] 阮秋琦,阮宇智譯;(美)拉斐爾·C.岡薩雷斯,理查德·E.伍茲.國外電子書與通信教材系列 數字圖像處理 第4版[M].北京:電子工業(yè)出版社,2020
審核編輯:黃飛
?
評論
查看更多