在进行图像二值化的操作中,我们往往要去去定一个阈值,像素值小于阈值的为背景,像素值大于背景的为前景。不同的阈值选择会产生出截然不同的二值化图像。因此,如何选取一个合适的阈值,是一个关键的问题。
大津算法,作为一个自动确定二值化阈值的算法,是从类内方差和类间方差的比值计算得来,各种方差的定义如下:
类内方差:
S
w
2
=
w
0
S
0
2
+
w
1
S
1
2
{S_w}^2=w_0\ {S_0}^2+w_1\ {S_1}^2
Sw2=w0 S02+w1 S12
类间方差:
S
b
2
=
w
0
(
M
0
−
M
t
)
2
+
w
1
(
M
1
−
M
t
)
2
=
w
0
w
1
(
M
0
−
M
1
)
2
{S_b}^2 = w_0 \ (M_0 - M_t)^2 + w_1\ (M_1 - M_t)^2 = w_0\ w_1\ (M_0 - M_1) ^2
Sb2=w0 (M0−Mt)2+w1 (M1−Mt)2=w0 w1 (M0−M1)2
图像所有像素的方差:
S
t
2
=
S
w
2
+
S
b
2
=
常数
{S_t}^2 = {S_w}^2 + {S_b}^2 = \text{常数}
St2=Sw2+Sb2=常数
在上式中,我们令小于阈值
t
t
t的类记为
0
0
0,大于阈值
t
t
t的类记为
1
1
1;
w
0
w_0
w0和
w
1
w_1
w1是阈值
t
t
t分开的两个类中的像素数占总像素数的比值(满足
w
0
+
w
1
=
1
w_0+w_1=1
w0+w1=1);
S
0
2
{S_0}^2
S02,
S
1
2
{S_1}^2
S12是这两个类中像素值的方差;
M
0
M_0
M0,
M
1
M_1
M1是这两个类的像素值的平均值;
根据上面的式子,我们定义分离度 X X X:
X = S b 2 S w 2 = S b 2 S t 2 − S b 2 X = \frac{{S_b}^2}{{S_w}^2} = \frac{{S_b}^2}{{S_t}^2 - {S_b}^2} X=Sw2Sb2=St2−Sb2Sb2
在上式中,我们希望最大化分离度 X X X,即最大化类间方差 S b 2 {S_b}^2 Sb2,同时最小化类内方差 S w 2 {S_w}^2 Sw2。但由于类间方差与类内方差之和为常数,因此问题转化为最大化类间方差,也就是说:
arg max t X = arg max t S b 2 \arg\max\limits_{t}\ X=\arg\max\limits_{t}\ {S_b}^2 argtmax X=argtmax Sb2
因此,如果使 S b 2 = w 0 w 1 ( M 0 − M 1 ) 2 {S_b}^2={w_0}\ {w_1}\ (M_0 - M_1)^2 Sb2=w0 w1 (M0−M1)2最大,就可以得到最好的二值化阈值 t t t。
代码实现
import cv2
import numpy as np
#将BGR图像转化为灰度图(opencv默认颜色通道为BGR)
def RGB2GRAY(img):
b = img[:, :, 0]
g = img[:, :, 1]
r = img[:, :, 2]
out = 0.2126 * r + 0.7152 * g + 0.0722 * b
out = out.astype(np.uint8)
return out
#大津算法得到二值化图像
def otsu(img):
max_sigma = 0
max_t = 0
for t in range(1, 255):
v0 = img[img < t]
m0 = np.mean(v0) if len(v0) else 0.
w0 = len(v0) / (H*W)
v1 = img[img >= t]
m1 = np.mean(v1) if len(v1) else 0.
w1 = len(v1) / (H * W)
sigma = w0 * w1 * ((m0 - m1) ** 2)
if sigma > max_sigma:
max_sigma = sigma
max_t = t
print('t = ', max_t)
img[img < max_t] = 0
img[img >= max_t] = 255
return img
img = cv2.imread('../imori.jpg').astype(np.float)
gray = RGB2GRAY(img)
H, W = gray.shape
out = otsu(gray)
cv2.imwrite('4.jpg', out)
cv2.imshow('otsu', out)
cv2.waitKey()
cv2.destroyAllWindows()