图像阈值处理
Python
简单阈值处理
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 决定如何计算阈值:
第四个参数则是阈值处理的类型;第五个参数 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?1?pi?=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?1?ipi?
令
w
0
(
T
)
=
∑
i
=
0
T
p
i
w_{0}(T)=\sum_{i=0}^{T} p_{i}
w0?(T)=i=0∑T?pi?
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?1?pi?=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∑T?w0?(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?1?w1?(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)-\mu\right)^{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∑T?w0?(T)pi?i2??μ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?1?w1?(T)pi?i2??μ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?1?pi?i2?μ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}-\mu\right)^{2}+w_{1}\left(\mu_{1}-\mu\right)^{2} \\ &=\mu^{2}\left(w_{0}+w_{1}\right)+w_0\mu_0^2-2w_0\mu_0\mu+w_1\mu_1^2-2w_1\mu_1\mu \\&=\mu^{2}+w_0\mu_0^2+w_1\mu_1^2-2w_0\mu_0\mu-2w_1\mu_1\mu \leftarrow (由 w_0+w_1=1) \\&=\mu^{2}+w_0\mu_0^2+w_1\mu_1^2-2\mu(w_0\mu_0+w_1\mu_1) \\&=w_0\mu_0^2+w_1\mu_1^2-(w_0\mu_0+w_1\mu_1) ^2\leftarrow (由 \mu=w_0\mu_0+w_1\mu_1) \\&=w_0\mu_0^2+w_1\mu_1^2-w^2_0\mu^2_0-w^2_1\mu^2_1 -2w_0w_1\mu_0\mu_1 \\&=w_0\mu_0^2(1-w_0)+w_1\mu_1^2(1-w_1)-2w_0w_1\mu_0\mu_1 \\&=w_0w_1\mu_0^2+w_0w_1\mu_1^2-2w_0w_1\mu_0\mu_1\leftarrow (由 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??2w0?w1?μ0?μ1?=w0?μ02?(1?w0?)+w1?μ12?(1?w1?)?2w0?w1?μ0?μ1?=w0?w1?μ02?+w0?w1?μ12??2w0?w1?μ0?μ1?←(由w0?+w1?=1)=w0?w1?(μ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)+w0?w1?(u0??u1?)2=w0?(i=0∑T?w0?pi?i2??μ02?)+w1?(i=T+1∑L?1?w1?pi?i2??μ12?)+w0?w1?(u0??u1?)2=i=0∑T?pi?i2?w0?μ02?+i=T+1∑L?1?pi?i2?w1?μ12?+w0?w1?(μ0??μ1?)2=i=0∑L?1?pi?i2?w02?μ02??w12?μ12??2w0?w1?μ0?μ1?=i=0∑L?1?pi?i2?(w0?u0?+w1?u1?)2=i=0∑L?1?pi?i2?μ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)
ret2, th2 = cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
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 <opencv2\opencv.hpp>
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 <opencv2\opencv.hpp>
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;
}
大津(Otsu)法
#include <opencv2\opencv.hpp>
using 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版),张德丰, 人民邮电出版社
|