簡單閾值
這里,問題很簡單,如果像素值超過閾值,就給分配一個值(可能是白色),否則給分配另一個值(可能是黑色)。用的方法是cv2.threshold。第一個參數是源圖片,應該是個灰度圖片,第二個參數是閾值,用來分類像素值的。第三個參數是在像素值大于閾值時的最大值。OpenCV提供了不同風格的閾值,由第四個參數決定。不同類型由:
·cv2.THRESH_BINARY
·cv2.THRESH_BINARY_INV
·cv2.THRESH_TRUNC
·cv2.THRESH_TOZERO
·cv2.THRESH_TOZERO_INV
文檔里有解釋每個類型的意義。
會得到兩個輸出。第一個是retval,第二個是閾值過濾過的圖像。
代碼:
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()
注意:
要畫多個圖片,我們可以使用plt.subplot()函數。
結果是:
適應性閾值
在前面我們使用了全局的值作為閾值。但是它可能不是在所有條件下都好使,有事圖片在不同區域有不同的光線條件。在這種情況下,我們使用適應性閾值。算法計算圖片里一個小區域的閾值。我們在同一張圖片里的不同區域可以有不同閾值。這會給我們多種光照下更好的結果.
它有三個“特殊”的輸入參數,只有一個輸出參數。
適應性方法 - 它來決定閾值如何計算出來。
·cv2.ADAPTIVE_THRESH_MEAN_C:閾值是周圍區域的差
·cv2.ADAPTIVE_THRESH_GAUSSIAN_C:閾值是周圍權重符合高斯分布的區域的權重之和。
塊大小 - 它決定了周圍區域的大小
C - 它是一個常量,從差值里減出來的或者計算出來的權重差。
下面的代碼比較了全局閾值和適應性閾值在處理一個變化光線的圖片的差別。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('dave.jpg',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二值法
在前面說過第二個參數retVal。在我們用到大津二值法的時候會用到。
在全局閾值里,我們使用一個固定值作為閾值,那么我們怎么知道這個值是好還是不好呢?答案是,反復試驗,不斷探索。但是要注意雙峰圖片(簡單說,雙峰圖片是頻率分布峰值有兩個的圖片),對于這種圖片,我們近似的可以用兩個峰的中間的值作為閾值,這就是Otsu二值法的做法。所以簡單來說,它自動計算雙峰圖片的頻率分布的閾值。(對于非雙峰圖片,二值法不準確)
這里用到cv2.threashold()函數,但是傳入一個額外的標志位,cv2.THRESH_OTSU。對于閾值,簡單傳入0.然后算法會算出優化的閾值并作為第二個輸出返回。retVal。如果Otsu閾值沒用到,retVal和你用的閾值一樣。
看下面的例子。輸入圖片是一個充滿噪點的圖片,在第一種情況下,我使用全局閾值127,在第二個情況,我使用Otsu閾值,第三個情況,我用5x5的告訴核來國旅圖片來除去噪點,然后使用Otsu閾值,看看過濾噪點對結果的提升。
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()
結果:
Otsu二值法是如何工作的
我們通過一個Otsu二值法的Python實現來展示它實際是如何工作的。
由于我們是處理雙峰圖片,Otsu算法會通過下面的關系式嘗試找到能使帶權類內方差最小的閾值。
其中:
它實際上找到雙峰之間的t值使得兩個類都取得最小值。可以像下面這樣用Python實現:
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 thresh, ret