1. 什么是圖像的二值化
將圖像上的[像素](https://baike.baidu.com/item/%E5%83%8F%E7%B4%A0/95084)點(diǎn)的[灰度值](https://baike.baidu.com/item/%E7%81%B0%E5%BA%A6%E5%80%BC/10259111)設(shè)置為0或255,也就是將整個(gè)圖像呈現(xiàn)出明顯的黑白效果的過(guò)程。
圖像的二值化使圖像中數(shù)據(jù)量大為減少,從而能凸顯出目標(biāo)的輪廓剃浇。
2. 實(shí)現(xiàn)函數(shù)- cv2.threshold()、cv2.adaptiveThreshold()
2.1 全局閾值-cv2.threshold()
- 參數(shù)如下:cv2.threshold(src, thresh, maxval, type)
- 參數(shù)說(shuō)明: 1. src 源圖像,必須是單通道 2. thresh 分類閾值 3. maxval 高于(低于)閾值時(shí)賦予的新值 4. type 閾值處理模式
- 返回值: 1.retval 得到的閾值 2. dst 閾值化后的圖像
- 閾值處理模式:cv2.THRESH_BINARY、cv2.THRESH_BINARY_INV偿渡、cv2.THRESH_TRUNC臼寄、
cv2.THRESH_TOZERO、cv2.THRESH_TOZERO_INV,具體說(shuō)明如下:
- 1. cv2.THRESH—BINARY
如果像素值大于閾值溜宽,像素值就會(huì)被設(shè)為參數(shù)3; 小于等于閾值吉拳,設(shè)定為0
- 2. cv2.THRESH_BINARY_INV
這個(gè)是上面一種情況的反轉(zhuǎn): 如果像素值大于閾值,像素值為0; 小于等于閾值适揉,設(shè)定為參數(shù)3
- 3. cv2.THRESH_TRUNC
如果像素大于閾值留攒,設(shè)定為閾值;小于等于閾值,保持原像素值
- 4. cv2.THRESH_TOZERO
大于閾值嫉嘀,保持原像素值; 小于等于炼邀,設(shè)定為0
- 5. cv2.THRESH_TOZERO_INV
與上一種相反:大于閾值,設(shè)定為0;小于等于剪侮,保持原像素值
代碼如下:
def threshold_simple(image):
img = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
ret, thresh1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
ret, thresh2 = cv.threshold(img, 127, 255, cv.THRESH_BINARY_INV)
ret, thresh3 = cv.threshold(img, 127, 255, cv.THRESH_TRUNC)
ret, thresh4 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO)
ret, thresh5 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO_INV)
titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(6):
plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray') # 將圖像按2x3鋪開
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
2.2 自適應(yīng)閾值 -cv2.adaptiveThreshold()
自適應(yīng)閾值可以看成一種局部性的閾值拭宁,通過(guò)規(guī)定一個(gè)區(qū)域大小,比較這個(gè)點(diǎn)與區(qū)域大小里面像素點(diǎn)的平均值(或者其他特征)的大小關(guān)系確定這個(gè)像素點(diǎn)是屬于黑或者白(如果是二值情況)瓣俯。該函數(shù)需要填6個(gè)參數(shù):
- 第一個(gè)原始圖像
- 第二個(gè)像素值上限
- 第三個(gè)自適應(yīng)方法Adaptive Method:
- — cv2.ADAPTIVE_THRESH_MEAN_C :領(lǐng)域內(nèi)均值
- —cv2.ADAPTIVE_THRESH_GAUSSIAN_C :領(lǐng)域內(nèi)像素點(diǎn)加權(quán)和杰标,權(quán)重為一個(gè)高斯窗口
- 第四個(gè)值的賦值方法:只有cv2.THRESH_BINARY 和cv2.THRESH_BINARY_INV
- 第五個(gè)Block size:規(guī)定領(lǐng)域大小(一個(gè)正方形的領(lǐng)域)彩匕,計(jì)算鄰域時(shí)的領(lǐng)鄰域大小腔剂,必須為奇數(shù);當(dāng)blockSize越大,參與計(jì)算閾值的區(qū)域也越大驼仪,細(xì)節(jié)輪廓就變得越少掸犬,整體輪廓越粗越明顯
- 第六個(gè)常數(shù)C,閾值等于均值或者加權(quán)值減去這個(gè)常數(shù),得到的就是最終閾值绪爸。當(dāng)C越大湾碎,每個(gè)像素點(diǎn)的N*N鄰域計(jì)算出的閾值就越小,中心點(diǎn)大于這個(gè)閾值的可能性也就越大奠货,設(shè)置成255的概率就越大介褥,整體圖像白色像素就越多,反之亦然仇味。
這種方法理論上得到的效果更好,相當(dāng)于在動(dòng)態(tài)自適應(yīng)的調(diào)整屬于自己像素點(diǎn)的閾值雹顺,而不是整幅圖像都用一個(gè)閾值丹墨。
代碼如下:
def threshold_adaptive(image):
img = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
# 中值濾波
img = cv.medianBlur(img, 5)
ret, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
# 11 為 Block size, 2 為 C 值
th2 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 2)
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2)
titles = ['Original Image', 'Global Threshold (v = 127)', 'Adaptive Mean Threshold', 'Adaptive Gaussian Threshold']
images = [img, th1, th2, th3]
for i in range(4):
plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
import cv2
import numpy as np
blocksize = 3
C=0
def adaptive_demo(gray, blocksize, C):
binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, blocksize, C)
# binary = cv2.GaussianBlur(binary, (15,15), 0)
cv2.imshow('binary', binary)
def C_changed(value):
global gray
global blocksize
global C
C = value - 30
print('C:', C)
adaptive_demo(gray, blocksize, C)
def blocksize_changed(value):
global gray
global blocksize
global C
blocksize = 2 * value + 1
print('blocksize:', blocksize)
adaptive_demo(gray, blocksize, C)
if __name__ == "__main__":
image_path = './img/1.jpg'
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
adaptive_demo(gray, 3, 0)
cv2.createTrackbar('C', 'binary',0, 60, C_changed)
cv2.createTrackbar('blocksize', 'binary',1, 20, blocksize_changed)
cv2.waitKey(0)
在使用全局閾值時(shí),隨便給了一個(gè)數(shù)來(lái)做閾值嬉愧,那怎么知道選取的這個(gè)數(shù)的好壞呢贩挣?答案就是不停的嘗試。如果是一副雙峰圖像(簡(jiǎn)單來(lái)說(shuō)雙峰圖像是指圖像直方圖中存在兩個(gè)峰)呢?我們豈不是應(yīng)該在兩個(gè)峰 之間的峰谷選一個(gè)值作為閾值王财?這就是 Otsu 二值化要做的卵迂。簡(jiǎn)單來(lái)說(shuō)就是對(duì) 一副雙峰圖像自動(dòng)根據(jù)其直方圖計(jì)算出一個(gè)閾值。(對(duì)于非雙峰圖像绒净,這種方法得到的結(jié)果可能會(huì)不理想)见咒。
這里用到到的函數(shù)還是 cv2.threshold(),但是需要多傳入一個(gè)參數(shù) (?ag):cv2.THRESH_OTSU挂疆。這時(shí)要把閾值設(shè)為 0改览。然后算法會(huì)找到最優(yōu)閾值,這個(gè)最優(yōu)閾值就是返回值 retVal缤言。如果不使用 Otsu 二值化宝当,返回的 retVal 值與設(shè)定的閾值相等。
下面的例子中胆萧,輸入圖像是一副帶有噪聲的圖像庆揩。第一種方法,設(shè)127為全局閾值跌穗。第二種方法订晌,我們直接使用 Otsu 二值化。第三種方法瞻离,我 們首先使用一個(gè) 5x5 的高斯核除去噪音腾仅,然后再使用 Otsu 二值化√桌看看噪音去除對(duì)結(jié)果的影響有多大推励。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('F:/OTSnoise.png',0)
#全局閾值
ret1, th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
#OTS閾值
ret2, th2 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
#(5,5)為高斯核的大小,0為標(biāo)準(zhǔn)差
blur = cv2.GaussianBlur(img,(5,5),0)
#閾值一定設(shè)為0
ret3, th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_OTSU)
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','Histogtam',"Otus'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.hist是畫直方圖
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’s 二值化是如何工作的肉迫?
import cv2
import numpy as np
img = cv2.imread('noisy2.png',0)
blur = cv2.GaussianBlur(img,(5,5),0)
# find normalized_histogram, and its cumulative distribution function
# 計(jì)算歸一化直方圖
#CalcHist(image, accumulate=0, mask=NULL)
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