opencv中的大津算法(最大类间差法/OTSU算法)

大津二值化算法(Otsu’s Method)

要解释大津二值化算法,首先要解释一下二值化算法。

opencv通过cv2.imread(“图片路径”)读取图片后(假设是彩色BGR三通道的图片),每个通道的每个像素是从0到255的无符号整数,我们看到的颜色是通过三个通道对0到255的不同组合呈现出来的。二值化是首先把它们合并成一张灰度图,再设定一个阈值th,高于th的像素值设为255(即最白),低于th的则设为0(即最黑)。这是最简单的二值化,实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2
import numpy as np

img = cv2.imread("../imori.jpg").astype(np.float)
b = img[:, :, 0].copy()
g = img[:, :, 1].copy()
r = img[:, :, 2].copy()

out = 0.2126 * r + 0.7152 * g + 0.0722 * b
out = out.astype(np.uint8)

th = 128

out[out < th] = 0
out[out >= th] = 255

cv2.imwrite("answer_3.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
输入 输出
image image

但这存在一个问题,如何设置这个阈值th呢?上面的代码是直接取0到255的中值128(其实也不是中值,中值是127.5,好吧,我钻牛角尖了),效果也还不错,这是因为刚好这张原图的灰度图的像素分布在128的两边,但如果是一张很灰暗的或者很亮的图呢?那可能得到的是一张全黑或全白的结果了。

一个解决办法是遍历0到255所有数值,把它们都作为阈值th,看在哪一个th下二值化效果最好。这里“效果最好”指的是两部分的类间方差最大——类间方差越大,就说明两部分之间的灰度差距越大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import cv2
import numpy as np

img = cv2.imread("../imori.jpg").astype(np.float)
H, W, C = img.shape

b = img[:, :, 0].copy()
g = img[:, :, 1].copy()
r = img[:, :, 2].copy()

out = 0.2126 * r + 0.7152 * g + 0.0722 * b
out = out.astype(np.uint8)

max_sigma = 0
max_t = 0

for _t in range(1, 255):
v0 = out[np.where(out < _t)]
m0 = np.mean(v0) if len(v0) > 0 else 0.
w0 = len(v0) / (H * W)
v1 = out[np.where(out >= _t)]
m1 = np.mean(v1) if len(v1) > 0 else 0.
w1 = len(v1) / (H * W)
sigma = w0 * w1 * ((m0 - m1)**2)
if sigma > max_sigma:
max_sigma = sigma
max_t = _t

# 下面对其进行二值化
print("二值化阈值:", max_t)
th = max_t
out[out < th] = 0
out[out >= th] = 255

# 保存结果
cv2.imwrite("answer_4.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
输入 输出
image image