OpenCV-Python教程:11.圖片閾值

weixin_34194087發表於2017-06-15

簡單閾值

這裡,問題很簡單,如果畫素值超過閾值,就給分配一個值(可能是白色),否則給分配另一個值(可能是黑色)。用的方法是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()函式。

結果是:

1784254-fca4be7edf898ce7.jpg

適應性閾值

在前面我們使用了全域性的值作為閾值。但是它可能不是在所有條件下都好使,有事圖片在不同區域有不同的光線條件。在這種情況下,我們使用適應性閾值。演算法計算圖片裡一個小區域的閾值。我們在同一張圖片裡的不同區域可以有不同閾值。這會給我們多種光照下更好的結果.

它有三個“特殊”的輸入引數,只有一個輸出引數。

適應性方法 - 它來決定閾值如何計算出來。

·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()

結果:

1784254-ffe1ab91e6a78169.jpg

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()

結果:

1784254-9e34e5bb3c0682e9.jpg

Otsu二值法是如何工作的

我們通過一個Otsu二值法的Python實現來展示它實際是如何工作的。

由於我們是處理雙峰圖片,Otsu演算法會通過下面的關係式嘗試找到能使帶權類內方差最小的閾值。

1784254-e21d8bff8e4ecbf7.png

其中:

1784254-cb521d060a887cd7.png

它實際上找到雙峰之間的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

相關文章