- 图像阈值处理
- Python
- 简单阈值处理
- 自适应阈值处理
- 大津(Otsu)法
- C++
- 简单阈值处理
- 自适应阈值处理
- 大津(Otsu)法
OpenCV的cv.threshold用于简单阈值处理,它的第一个参数是灰度源图像src;第二个参数是阈值thresh;第三个参数是赋值给超过阈值的像素的最大值maxval;第四个参数则是阈值处理的类型:
cv.threshold返回两个输出。第一个是使用的阈值,第二个输出是阈值图像dst。下面比较不同类型的阈值处理方法:
import cv2 as cv
from matplotlib import pyplot as plt
import numpy as np
img = cv.imread('threshold.jpg', 0)
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')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
自适应阈值处理
简单阈值处理在图像全局都使用同一个阈值,如果图像在不同区域有不同的照明条件,这可能就不适用了。在这种情况下,自适应阈值处理更适合。自适应阈值处理根据像素周围的一个小区域来确定阈值。可以使用OpenCV的cv.adaptiveThreshold实现这个功能。
cv.adaptiveThreshold的第一个参数是灰度源图像src;第二个参数是赋值给超过阈值的像素的最大值maxval;
第三个参数adaptiveMethod决定如何计算阈值:
-
cv.ADAPTIVE_THRESH_MEAN_C:阈值是邻域内像素的平均值减去常数C。
-
cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域内像素的高斯加权和减去常数C。
第四个参数则是阈值处理的类型;第五个参数 blockSize决定邻域区域的大小;第六个参数是常数C。
下面对比一下简单阈值处理和自适应阈值处理:
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('adaptiveThreshold.jpg', 0)
ret, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
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', 'BINARY(v=127)',
'ADAPTIVE_THRESH_MEAN_C', 'ADAPTIVE_THRESH_GAUSSIAN_C']
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()
大津(Otsu)法
在普通阈值处理中,我们需要指定阈值。大津法则能够从图像直方图中自动确定一个最优的全局阈值,即使用大津法不用人工挑选阈值。
大津法确定阈值的方法是找到一个阈值 T T T,使类间方差最大或类内方差最小。设图像像素数为 N N N,灰度范围为 [ 0 , L ] [0,L] [0,L]。对应灰度级 i i i的像素数为 n i n_i ni,灰度级 i i i的频率为:
p i = n i N , i = 0 , 1 , 2 , ⋯ , L − 1 p_{i}=frac{n_{i}}{N}, i=0,1,2, cdots, L-1 pi=Nni,i=0,1,2,⋯,L−1
总和为1:
∑
i
=
0
L
−
1
p
i
=
1
sum_{i=0}^{L-1} p_{i}=1
i=0∑L−1pi=1
把图像中像素按灰度值用阈值
T
T
T分成两类
C
0
C_0
C0和
C
1
C_1
C1,
C
0
C_0
C0由灰度值在
[
0
,
T
]
[0,T]
[0,T]之间的像素组成,
C
1
C_1
C1由灰度值在
[
T
+
1
,
L
−
1
]
[T+1,L-1]
[T+1,L−1]之间的像素组成。整幅图像的均值为:
μ
=
∑
i
=
0
L
−
1
i
p
i
mu=sum_{i=0}^{L-1} i p_{i}
μ=i=0∑L−1ipi
令
w
0
(
T
)
=
∑
i
=
0
T
p
i
w_{0}(T)=sum_{i=0}^{T} p_{i}
w0(T)=i=0∑Tpi
w 1 ( T ) = ∑ i = T + 1 L − 1 p i = 1 − w 0 w_{1}(T)=sum_{i=T+1}^{L-1} p_{i}=1-w_{0} w1(T)=i=T+1∑L−1pi=1−w0
则 C 0 C_0 C0和 C 1 C_1 C1的均值为:
μ 0 ( T ) = ∑ i = 0 T i p i w 0 ( T ) mu_{0}(T)=sum_{i=0}^{T} frac{i p_{i}}{w_{0}(T)} μ0(T)=i=0∑Tw0(T)ipi
μ 1 ( T ) = ∑ i = T + 1 L − 1 i p i w 1 ( T ) mu_{1}(T)=sum_{i=T+1}^{L-1} frac{i p_{i}}{w_{1}(T)} μ1(T)=i=T+1∑L−1w1(T)ipi
由上面式子可得:
μ = w 0 ( T ) μ 0 ( T ) + w 1 ( T ) μ 1 ( T ) mu=w_{0}(T) mu_{0}(T)+w_{1}(T) mu_{1}(T) μ=w0(T)μ0(T)+w1(T)μ1(T)
类内方差(within-class variance) 定义为:
σ w 2 ( T ) = w 0 ( T ) σ 0 2 ( T ) + w 1 ( T ) σ 1 2 ( T ) sigma_{w}^{2}(T) =w_{0}(T) sigma_{0}^{2}(T) +w_{1}(T) sigma_{1}^{2}(T) σw2(T)=w0(T)σ02(T)+w1(T)σ12(T)
反映每一类样本方差的大小。(值越小,各类的样本越集中,越好分割)
类间方差(between-class variance) 定义为:
σ B 2 ( T ) = w 0 ( T ) ( μ 0 ( T ) − μ ) 2 + w 1 ( T ) ( μ 1 ( T ) − μ ) 2 sigma_{B}^{2}(T)=w_{0}(T)left(mu_{0}(T)-muright)^{2}+w_{1}(T)left(mu_{1}(T)-mu right)^{2} σB2(T)=w0(T)(μ0(T)−μ)2+w1(T)(μ1(T)−μ)2
相当于以每一类的均值作为该类的代表值,以总体均值为均值计算方差。类间方差反映阈值两侧数据之间的差异程度。(值越大,类别越明显,越好分割)
由 D ( X ) = E ( X 2 ) − E 2 ( X ) D(X)=E(X^2)-E^2(X) D(X)=E(X2)−E2(X),得到:
σ 0 2 ( T ) = ∑ i = 0 T p i i 2 w 0 ( T ) − μ 0 2 ( T ) sigma_0^2(T) =sum_{i=0}^{T} frac{p_{i}i^2}{w_0(T) }-mu_0^2(T) σ02(T)=i=0∑Tw0(T)pii2−μ02(T)
σ 1 2 ( T ) = ∑ i = T + 1 L − 1 p i i 2 w 1 ( T ) − μ 1 2 ( T ) sigma_1^2(T) =sum_{i=T+1}^{L-1} frac{p_{i}i^2}{{w_1(T) }}-mu_1^2(T) σ12(T)=i=T+1∑L−1w1(T)pii2−μ12(T)
σ 2 = ∑ i = 0 L − 1 p i i 2 − μ 2 , 为 常 数 sigma^2=sum_{i=0}^{L-1} p_{i}i^2-mu^2,为常数 σ2=i=0∑L−1pii2−μ2,为常数
再由:
σ B 2 ( T ) = w 0 ( μ 0 − μ ) 2 + w 1 ( μ 1 − μ ) 2 = μ 2 ( w 0 + w 1 ) + w 0 μ 0 2 − 2 w 0 μ 0 μ + w 1 μ 1 2 − 2 w 1 μ 1 μ = μ 2 + w 0 μ 0 2 + w 1 μ 1 2 − 2 w 0 μ 0 μ − 2 w 1 μ 1 μ ← ( 由 w 0 + w 1 = 1 ) = μ 2 + w 0 μ 0 2 + w 1 μ 1 2 − 2 μ ( w 0 μ 0 + w 1 μ 1 ) = w 0 μ 0 2 + w 1 μ 1 2 − ( w 0 μ 0 + w 1 μ 1 ) 2 ← ( 由 μ = w 0 μ 0 + w 1 μ 1 ) = w 0 μ 0 2 + w 1 μ 1 2 − w 0 2 μ 0 2 − w 1 2 μ 1 2 − 2 w 0 w 1 μ 0 μ 1 = w 0 μ 0 2 ( 1 − w 0 ) + w 1 μ 1 2 ( 1 − w 1 ) − 2 w 0 w 1 μ 0 μ 1 = w 0 w 1 μ 0 2 + w 0 w 1 μ 1 2 − 2 w 0 w 1 μ 0 μ 1 ← ( 由 w 0 + w 1 = 1 ) = w 0 w 1 ( μ 0 − μ 1 ) 2 begin{aligned} sigma_{B}^{2}(T) &=w_{0}left(mu_{0}-muright)^{2}+w_{1}left(mu_{1}-muright)^{2} \ &=mu^{2}left(w_{0}+w_{1}right)+w_0mu_0^2-2w_0mu_0mu+w_1mu_1^2-2w_1mu_1mu \&=mu^{2}+w_0mu_0^2+w_1mu_1^2-2w_0mu_0mu-2w_1mu_1mu leftarrow (由 w_0+w_1=1) \&=mu^{2}+w_0mu_0^2+w_1mu_1^2-2mu(w_0mu_0+w_1mu_1) \&=w_0mu_0^2+w_1mu_1^2-(w_0mu_0+w_1mu_1) ^2leftarrow (由 mu=w_0mu_0+w_1mu_1) \&=w_0mu_0^2+w_1mu_1^2-w^2_0mu^2_0-w^2_1mu^2_1 -2w_0w_1mu_0mu_1 \&=w_0mu_0^2(1-w_0)+w_1mu_1^2(1-w_1)-2w_0w_1mu_0mu_1 \&=w_0w_1mu_0^2+w_0w_1mu_1^2-2w_0w_1mu_0mu_1leftarrow (由 w_0+w_1=1) \&= w_{0}w_{1} left(mu_{0}-mu_{1}right)^{2} end{aligned} σB2(T)=w0(μ0−μ)2+w1(μ1−μ)2=μ2(w0+w1)+w0μ02−2w0μ0μ+w1μ12−2w1μ1μ=μ2+w0μ02+w1μ12−2w0μ0μ−2w1μ1μ←(由w0+w1=1)=μ2+w0μ02+w1μ12−2μ(w0μ0+w1μ1)=w0μ02+w1μ12−(w0μ0+w1μ1)2←(由μ=w0μ0+w1μ1)=w0μ02+w1μ12−w02μ02−w12μ12−2w0w1μ0μ1=w0μ02(1−w0)+w1μ12(1−w1)−2w0w1μ0μ1=w0w1μ02+w0w1μ12−2w0w1μ0μ1←(由w0+w1=1)=w0w1(μ0−μ1)2
得到:
σ w 2 ( T ) + σ b 2 ( T ) = w 0 σ 0 2 ( T ) + w 1 σ 1 2 ( T ) + w 0 w 1 ( u 0 − u 1 ) 2 = w 0 ( ∑ i = 0 T p i i 2 w 0 − μ 0 2 ) + w 1 ( ∑ i = T + 1 L − 1 p i i 2 w 1 − μ 1 2 ) + w 0 w 1 ( u 0 − u 1 ) 2 = ∑ i = 0 T p i i 2 − w 0 μ 0 2 + ∑ i = T + 1 L − 1 p i i 2 − w 1 μ 1 2 + w 0 w 1 ( μ 0 − μ 1 ) 2 = ∑ i = 0 L − 1 p i i 2 − w 0 2 μ 0 2 − w 1 2 μ 1 2 − 2 w 0 w 1 μ 0 μ 1 = ∑ i = 0 L − 1 p i i 2 − ( w 0 u 0 + w 1 u 1 ) 2 = ∑ i = 0 L − 1 p i i 2 − μ 2 = σ 2 begin{aligned} sigma_{w}^{2}(T)+sigma_{b}^{2}(T)&=w_{0}sigma_{0}^{2}(T)+w_{1}sigma_{1}^{2}(T)+w_{0}w_{1} left(u_{0}-u_{1}right)^{2} \&=w_0(sum_{i=0}^{T} frac{p_{i}i^2}{w_0}-mu_0^2)+w_1(sum_{i=T+1}^{L-1} frac{p_{i}i^2}{w_1}-mu_1^2)+w_{0}w_{1} left(u_{0}-u_{1}right)^{2} \&=sum_{i=0}^{T} p_{i} i^{2}-w_{0} mu_{0}^{2}+sum_{i=T+1}^{L-1} p_{i} i^{2}-w_{1} mu_{1}^{2}+w_{0} w_{1}left(mu_{0}-mu_{1}right)^{2} \& =sum_{i=0}^{L-1} p_{i} i^{2}-w_{0}^{2} mu_{0}^{2}-w_{1}^{2} mu_{1}^{2}-2 w_{0} w_{1} mu_{0} mu_{1}\&=sum_{i=0}^{L-1} p_{i} i^{2}-(w_{0} u_{0}+w_{1} u_{1})^2 \&=sum_{i=0}^{L-1} p_{i}i^2-mu^2 \&=sigma^{2} end{aligned} σw2(T)+σb2(T)=w0σ02(T)+w1σ12(T)+w0w1(u0−u1)2=w0(i=0∑Tw0pii2−μ02)+w1(i=T+1∑L−1w1pii2−μ12)+w0w1(u0−u1)2=i=0∑Tpii2−w0μ02+i=T+1∑L−1pii2−w1μ12+w0w1(μ0−μ1)2=i=0∑L−1pii2−w02μ02−w12μ12−2w0w1μ0μ1=i=0∑L−1pii2−(w0u0+w1u1)2=i=0∑L−1pii2−μ2=σ2
即类内方差与类间方差之和为定值,最大化类间方差与最小化类内方差等价。
使用cv.threshold()函数,阈值处理的类型可以任意选择,THRESH_OTSU作为一个额外的flag即可使用大津法。下面以一个直方图包含两个峰的图像(双峰图像)为例:
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('coins.png', 0)
# 普通二值化
ret1, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
# Otsu法二值化
ret2, th2 = cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU) # 随便给个0为阈值
images = [img, [], th1,
img, [], th2]
titles = ['Original Image', 'Histogram', 'BINARY (v=127)',
'Original Image', 'Histogram', 'OTSU(v=0)']
for i in range(2):
plt.subplot(2, 3, i * 3 + 1), plt.imshow(images[i * 3], 'gray')
plt.title(titles[i * 3]), plt.xticks([]), plt.yticks([])
plt.subplot(2, 3, i * 3 + 2), plt.hist(images[i * 3].ravel(), 256)
plt.title(titles[i * 3 + 1]), plt.xticks([]), plt.yticks([])
plt.subplot(2, 3, i * 3 + 3), plt.imshow(images[i * 3 + 2], 'gray')
plt.title(titles[i * 3 + 2]), plt.xticks([]), plt.yticks([])
plt.show()
大津法无需人工指定阈值即可达到较为理想的效果。
C++ 简单阈值处理#include自适应阈值处理using namespace cv; int main() { Mat img = imread("threshold.jpg",0); Mat thresh1, thresh2, thresh3, thresh4, thresh5; threshold(img, thresh1, 127, 255, THRESH_BINARY); threshold(img, thresh2, 127, 255, THRESH_BINARY_INV); threshold(img, thresh3, 127, 255, THRESH_TRUNC); threshold(img, thresh4, 127, 255, THRESH_TOZERO); threshold(img, thresh5, 127, 255, THRESH_TOZERO_INV); imshow("Original Image", img); imshow("BINARY", thresh1); imshow("BINARY_INV", thresh2); imshow("TRUNC", thresh3); imshow("TOZERO", thresh4); imshow("TOZERO_INV", thresh5); waitKey(0); return 0; }
#include大津(Otsu)法using namespace cv; int main() { Mat img = imread("adaptiveThreshold.jpg",0); Mat thresh1, thresh2, thresh3; threshold(img, thresh1, 127, 255, THRESH_BINARY); adaptiveThreshold(img, thresh2,255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, 2); adaptiveThreshold(img, thresh3,255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 11, 2); imshow("Original Image", img); imshow("BINARY", thresh1); imshow("ADAPTIVE_THRESH_MEAN_C", thresh2); imshow("ADAPTIVE_THRESH_GAUSSIAN_C", thresh3); waitKey(0); return 0; }
#includeusing namespace cv; int main() { Mat img = imread("coins.png",0); Mat thresh1, thresh2; threshold(img, thresh1, 127, 255, THRESH_BINARY); threshold(img, thresh2, 0, 255, THRESH_BINARY + THRESH_OTSU); imshow("Original Image", img); imshow("BINARY", thresh1); imshow("OTSU", thresh2); waitKey(0); return 0; }
代码:
https://gitee.com/BinaryAI/open-cv-c–and-python
参考:
[1]https://docs.opencv.org/4.6.0/
[2]https://zhuanlan.zhihu.com/p/384457101
[3]数字图像处理(MATLAB版)(第2版),张德丰, 人民邮电出版社



