在圖像處理中閾值化操作,從一副圖像中利用閾值分割出我們需要的物體部分(當然這里的物體可以是一部分或者整體)。這樣的圖像分割方法是基於圖像中物體與背景之間的灰度差異,而且此分割屬於像素級的分割。opencv的二值化操作函數,如果你是一位經驗豐富的專業人員,可以發現閾值化操作有很多小技巧,不只是單單調用二值化操作函數,就完成閾值化操作,往往還是結合形態學處理。
閾值化操作在圖像處理中是一種常用的算法,比如圖像的二值化就是一種最常見的一種閾值化操作。opencv2和opencv3中提供了直接閾值化操作cv::threshold()和自適應閾值化操作cv::adaptiveThreshold()兩種閾值化操作接口,這里將對這兩個接口進行介紹和對比。本文將重點講解兩個函數的應用特點和應用場景,以及在使用的注意事項。
一、閾值化操作介紹
二進制閾值
閾值化類型如下式所示:
解釋:在運用該閾值類型的時候,先要選定一個特定的閾值量,比如:125,這樣,新的閾值產生規則可以解釋為大於125的像素點的灰度值設定為最大值(如8位灰度值最大為255),灰度值小於125的像素點的灰度值設定為0。

反二進制閾值化
閾值類型如下式所示:
解釋:該閾值化與二進制閾值化相似,先選定一個特定的灰度值作為閾值,不過最后的設定值相反。(在8位灰度圖中,例如大於閾值的設定為0,而小於該閾值的設定為255)。
截斷閾值化
該閾值化類型如下式所示:
解釋:同樣首先需要選定一個閾值,圖像中大於該閾值的像素點被設定為該閾值,小於該閾值的保持不變。(例如:閾值選取為125,那小於125的閾值不改變,大於125的灰度值(230)的像素點就設定為該閾值)。
閾值化為0
該閾值類型如下式所示:
解釋:先選定一個閾值,然后對圖像做如下處理:1 像素點的灰度值大於該閾值的不進行任何改變;2 像素點的灰度值小於該閾值的,其灰度值全部變為0。
反閾值化為0
該閾值類型如下式所示:
解釋:原理類似於0閾值,但是在對圖像做處理的時候相反,即:像素點的灰度值小於該閾值的不進行任何改變,而大於該閾值的部分,其灰度值全部變為0。
二、直接閾值化—THRESHOLD
2.1 代碼說明
threshold(src,thresh,maxval, type,dst=None)#輸出圖像
src:原圖像。
thresh:當前閾值。
maxVal:最大閾值,一般為255.
thresholdType:閾值類型,主要有下面幾種:
enum { THRESH_BINARY=0, THRESH_BINARY_INV=1, THRESH_TRUNC=2, THRESH_TOZERO=3, THRESH_TOZERO_INV=4};
在二值化操作中常用的閾值類型為THRESH_BINARY,THRESH_BINARY_INV,但在二值化操作中有個特殊的閾值化操作為Otsu算法。
2.2 Otsu算法
算法的主要思想是,在進行閾值化時,考慮所有可能的閾值,分別計算低於閾值和高於閾值像素的方差,使下式最小化的值作為閾值:
其中,兩類像素方差的權值由兩類像素的個數決定。這種閾值化的結果相對來說比較理想,可以避免尋找合適閾值的操作,但是這種方式運算量較大,費時。
對圖像Image,記t為前景與背景的分割閾值,前景點數占圖像比例為w0,平均灰度為u0;背景點數占圖像比例為w1,平均灰度為u1。圖像的總平均灰度為:u=w0*u0+w1*u1。從最小灰度值到最大灰度值遍歷t,當t使得值g=w0*(u0-u)2+w1*(u1-u)2 最大時t即為分割的最佳閾值。對大津法可作如下理解:該式實際上就是類間方差值,閾值t分割出的前景和背景兩部分構成了整幅圖像,而前景取值u0,概率為 w0,背景取值u1,概率為w1,總均值為u,根據方差的定義即得該式。因方差是灰度分布均勻性的一種度量,方差值越大,說明構成圖像的兩部分差別越大, 當部分目標錯分為背景或部分背景錯分為目標都會導致兩部分差別變小,因此使類間方差最大的分割意味着錯分概率最小。
img = cv2.imread('noisy2.png',0) blur = cv2.GaussianBlur(img,(5,5),0) # find normalized_histogram, and its cumulative distribution function hist = cv2.calcHist([blur],[0],None,[256],[0,256]) hist_norm = hist.ravel()/hist.max() Q = hist_norm.cumsum() bins = np.arange(256) fn_min = np.inf thresh = -1 for i in xrange(1,256): p1,p2 = np.hsplit(hist_norm,[i]) # probabilities q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes b1,b2 = np.hsplit(bins,[i]) # weights # finding means and variances m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2 v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2 # calculates the minimization function fn = v1*q1 + v2*q2 if fn < fn_min: fn_min = fn thresh = i # find otsu's threshold value with OpenCV function ret, otsu = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) print( "{} {}".format(thresh,ret) )
其中:numpy.hsplit表示為:https://docs.scipy.org/doc/numpy/reference/generated/numpy.hsplit.html
cv2.threshold函數是有兩個返回值的,前面一直用的第二個返回值,也就是閾值處理后的圖像,在簡單的閾值處理上,我們選擇的閾值都是127,那么實際情況下,怎么去選擇這個127呢?有的圖像可能閾值不是127得到的效果更好。Otsu’s就可以自己找到一個認為最好的閾值,並且Otsu’s非常適合於圖像灰度直方圖具有雙峰的情況,他會在雙峰之間找到一個值作為閾值,對於非雙峰圖像,可能並不是很好用。那么經過Otsu’s得到的那個閾值就是函數cv2.threshold的第一個參數了。因為Otsu’s方法會產生一個閾值,那么函數cv2.threshold的的第二個參數(設置閾值)就是0了,並且在cv2.threshold的方法參數中還得加上語句cv2.THRESH_OTSU。那么什么是雙峰圖像(只能是灰度圖像才有),就是圖像的灰度統計圖中可以明顯看出只有兩個波峰,比如下面一個圖的灰度直方圖就可以是雙峰圖:
一個實例如下:
#簡單濾波 ret1,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY) #Otsu 濾波 ret2,th2 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) print ret2
上面中其中thresh直接設置為0,因為otsu算法可以直接計算一個合適的閾值,當然效率會比較低。
1.3 實驗代碼
import cv2 import numpy as np from matplotlib import pyplot as plt img = cv2.imread('gradient.png',0) ret,thresh1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY) ret,thresh2 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV) ret,thresh3 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC) ret,thresh4 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO) ret,thresh5 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO_INV) titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV'] images = [img, thresh1, thresh2, thresh3, thresh4, thresh5] for i in xrange(6): plt.subplot(2,3,i+1),plt.imshow(images[i],'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show()
import cv2 import numpy as np from matplotlib import pyplot as plt img = cv2.imread('noisy2.png',0) # global thresholding ret1,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY) # Otsu's thresholding ret2,th2 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) # Otsu's thresholding after Gaussian filtering blur = cv2.GaussianBlur(img,(5,5),0) ret3,th3 = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) # plot all the images and their histograms images = [img, 0, th1, img, 0, th2, blur, 0, th3] titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)', 'Original Noisy Image','Histogram',"Otsu's Thresholding", 'Gaussian filtered Image','Histogram',"Otsu's Thresholding"] for i in xrange(3): plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray') plt.title(titles[i*3]), plt.xticks([]), plt.yticks([]) plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256) plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([]) plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray') plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([]) plt.show()
看看上面的例子。輸入圖像是一個嘈雜的圖像。在第一種情況下,我申請了一個價值127的全局閾值分割。在第二種情況下,我應用Otsu閾值直接。在第三的情況下,我與一個5x5高斯內核過濾圖像去除噪聲,然后利用Otsu閾值。看看噪聲濾波改善結果。
三、自適應閾值化——cv::adaptiveThreshold()
上述的二值化本質上還是全局二值化,重點在於如何獲取最佳閾值,這次我們講解一種局部閾值方式,即opencv集成的自適應二值化。自適應閾值化能夠根據圖像不同區域亮度分布的,改變閾值。
void cv::adaptiveThreshold( cv::InputArray src, // 輸入圖像 double maxValue, // 向上最大值 int adaptiveMethod, // 自適應方法,平均或高斯 int thresholdType // 閾值化類型 int blockSize, // 塊大小 double C // 常量 );
cv::adaptiveThreshold()支持兩種自適應方法,即cv::ADAPTIVE_THRESH_MEAN_C(平均)和cv::ADAPTIVE_THRESH_GAUSSIAN_C(高斯)。在兩種情況下,自適應閾值T(x, y)。通過計算每個像素周圍bxb大小像素塊的加權均值並減去常量C得到。其中,b由blockSize給出,大小必須為奇數;如果使用平均的方法,則所有像素周圍的權值相同;如果使用高斯的方法,則(x,y)周圍的像素的權值則根據其到中心點的距離通過高斯方程得到。
import cv2 import numpy as np from matplotlib import pyplot as plt img = cv2.imread('sudoku.png',0) img = cv2.medianBlur(img,5) ret,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY) th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\ cv2.THRESH_BINARY,11,2) th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\ cv2.THRESH_BINARY,11,2) titles = ['Original Image', 'Global Thresholding (v = 127)', 'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding'] images = [img, th1, th2, th3] for i in xrange(4): plt.subplot(2,2,i+1),plt.imshow(images[i],'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show()
四、參考文獻
OTSU算法原理說明:文中對原理進行詳細說明,並有代碼進行實現。
官方閾值化操作說明:文中詳細對各個函數進行詳細介紹,很好。
閾值化操作——cv::threshold()與cv::adaptiveThreshold()詳解:文中對這兩種方法進行介紹,並進行編碼測試。