学习目标
- 本教程,将学习自适应阈值(Adaptive Thresholding),Otsu‘s阈值等
- 学习函数:
cv2.threshold
,cv2.addaptiveThreshold
简单阈值(Simple Thresholding)
阈值的方法是非常直接的,如果一个像素大于阈值,分配该像素一个固定值(通常是白色,1或255),否则值为黑色(值为0)。使用函数如下:
cv2.threshold(img, thresh, maxval,function_type)
- img:输入图像,必须是灰度图像。
- thresh:阈值,用于分类像素。
- maxval :大于阈值的像素,分配该值。
- function_type:阈值函数的类型。
OpenCV提供了不同类型的阈值函数,通常由第四个参数指定:
cv2.THRESH_BINARY
d s t ( x , y ) = { m a x v a l i f s r c ( x , y ) > t h r e s h 0 o t h e r w i s e dst(x,y)=\begin{cases} maxval&&if src(x,y)>thresh\\ 0 &&otherwise \end{cases} dst(x,y)={maxval0ifsrc(x,y)>threshotherwisecv2.THRESH_BINARY_INV
d s t ( x , y ) = { 0 i f s r c ( x , y ) > t h r e s h m a x v a l o t h e r w i s e dst(x,y)=\begin{cases} 0 &&if src(x,y)>thresh\\ maxval&&otherwise \end{cases} dst(x,y)={0maxvalifsrc(x,y)>threshotherwisecv2.THRESH_TRUNC
d s t ( x , y ) = { t h r e s h i f s r c ( x , y ) > t h r e s h s r c ( x , y ) o t h e r w i s e dst(x,y)=\begin{cases} thresh &&if src(x,y)>thresh\\ src(x,y)&&otherwise \end{cases} dst(x,y)={threshsrc(x,y)ifsrc(x,y)>threshotherwisecv2.THRESH_TOZERO
d s t ( x , y ) = { s r c ( x , y ) i f s r c ( x , y ) > t h r e s h 0 o t h e r w i s e dst(x,y)=\begin{cases} src(x,y) &&if src(x,y)>thresh\\ 0&&otherwise \end{cases} dst(x,y)={src(x,y)0ifsrc(x,y)>threshotherwisecv2.THRESH_TOZERO_INV
d s t ( x , y ) = { 0 i f s r c ( x , y ) > t h r e s h s r c ( x , y ) o t h e r w i s e dst(x,y)=\begin{cases} 0 &&if src(x,y)>thresh\\ src(x,y)&&otherwise \end{cases} dst(x,y)={0src(x,y)ifsrc(x,y)>threshotherwise
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 range(6):
plt.subplot(2, 3, i+1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
结果如下:
自适应阈值(Adaptive Thresholding)
上一节中,我们使用的是单个全局阈值。但是,实际情况中,图像不同的区域的光照条件可能不太一样。这种情况下,我们需要考虑自适应阈值。针对不同的图像区域,设置不同的阈值。
adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst=None)
参数如下:
src
:8位单通道图像。
maxValue
:像素大于阈值,就赋值maxValue,通常是255。
adaptiveMethod
:自适应阈值的计算方式,
cv2.ADAPTIVE_THRESH_MEAN_C
:计算blockSize邻域内的均值。cv2.ADAPTIVE_THRESH_GAUSSIAN_C
:邻域内计算高斯加权和。
thresholdType
:阈值赋值方式,只有#THRESH_BINARY or
#THRESH_BINARY_INV
两种方式。
blockSize
:计算自适应阈值的窗口大小,比如3x3,5x5等。
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,5)
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,11,5)
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’s Binarization)
第一部分,我们提到第二个参数,retVal
. Otsu’s Binarization 算法中会使用该参数。
全局阈值分割算法中,我们选择任意一个值作为阈值,但是这样的分割效果不会太理想。那有没有更好的方法用于选择阈值呢,答案是试错法(trial and error method)。考虑双峰图像(简单说就是图像的直方图具有两个峰),对于这种图像,我们可以选择两峰之间的值作为阈值,这也正是Otsu
算法要做的。也就是说,Otsu
算法可以自动计算双峰图像的阈值,但是对于非双峰图像,效果并不是那么好。
使用函数为cv2.threshold()
,但是需要传递额外的标志,cv2.THRESH_OTSU. 阈值可以简单设置为0. 然后,该算法会计算最优的阈值,返回值为retVal
. 如果Otsu阈值没有使用,retVal
与设置的阈值一致。
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)
print(ret1) # 输出:127.0
# Otsu's thresholding
ret2, th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(ret2) # 输出:111.0
# 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)
print(ret3) # 输出:110.0
# 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 range(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()
How Otsu’s Binarization Works?
大津算法,也被称作最大类间方差法,是一种自动确定二值化阈值的算法。由于该算法研究的对象是双峰图像(bimodal images)。该算法是自动计算阈值,使得类内方差最小化(within-class variance),计算过程如下:
注意:上述绿色框应该改为,
q
2
(
t
)
q_2(t)
q2(t),红色框应该为
μ
2
(
t
)
\mu_2(t)
μ2(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])
# np.ravel():数组降维
hist_norm = hist.ravel()/hist.max()
# 每个元素逐渐累加,返回中间元素的累加结果
# [1, 2, 3, 4, 5].cumsum()=[1, 3, 6, 10, 15]
Q = hist_norm.cumsum()
bins = np.arange(256)
# np.inf:无穷大的值
fn_min = np.inf
thresh = -1
for i in range(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)
# 输出:110 109.0
算法评价:
就最大类间方差算法而言,优点是算法简单,当目标与背景的面积相差不大时,能够有效地对图像进行分割。但是,当图像中的目标与背景的面积相差很大时,表现为直方图没有明显的双峰,或者两个峰的大小相差很大,分割效果不佳,或者目标与背景的灰度有较大的重叠时也不能准确的将目标与背景分开。导致这种现象出现的原因是该方法忽略了图像的空间信息,同时该方法将图像的灰度分布作为分割图像的依据,因而对噪声也相当敏感。所以,在实际应用中,总是将其与其他方法结合起来使用。
参考链接: