聊聊大津法

最近一直在做一个和OpenCV有关的项目,在这个项目上使用到了大津法,今天就来谈谈这个方法。

之前因为要提取图片中的一个标志物的轮廓,在网上一番搜索找到了这个方法,它的作用是可以帮你找到二值化图片的一个阈值。这样能够比较好的分离出前后背景或者轮廓。但是也有缺点,如果前后背景颜色相似这个方法就不太适用了。

大津法的使用

使用大津法需要先将图片转为灰度图片,然后直接使用大津法求出阈值,最后二值化时带入这个阈值就可以求出比较好的二值化图像,此时再执行提取轮廓或者其他操作即可。

下面给出大津法的代码,这是网上的大神给出的代码。如果需要了解原理,可以看看阮一峰的文章 二、内容特征法

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
def OTSU_enhance(img_gray, th_begin=0, th_end=256, th_step=1) -> int:
"""
作用: 找到二值化最佳阈值, 可以实现前后背景分离
"""
assert img_gray.ndim == 2, "must input a gray_img"
max_g = 0
suitable_th = 0
for threshold in range(th_begin, th_end, th_step):
bin_img = img_gray > threshold
bin_img_inv = img_gray <= threshold
fore_pix = np.sum(bin_img)
back_pix = np.sum(bin_img_inv)
if 0 == fore_pix:
break
if 0 == back_pix:
continue

w0 = float(fore_pix) / img_gray.size
u0 = float(np.sum(img_gray * bin_img)) / fore_pix
w1 = float(back_pix) / img_gray.size
u1 = float(np.sum(img_gray * bin_img_inv)) / back_pix
# intra-class variance
g = w0 * w1 * (u0 - u1) * (u0 - u1)
if g > max_g:
max_g = g
suitable_th = threshold
return suitable_th

仔细看看代码就可以发现这个函数的时间复杂度非常高,最大的问题就是非常耗时,有多耗时呢?一张400*400像素的图片用时0.2秒,要知道我的程序全部跑完也不过0.7秒左右,光是求阈值就占去30%的时间,所以最好是优化一下这个部分。更恐怖的是,这个时间不是线性增长,而是指数增长的,以维基百科上大津法示例图为例子:

一张1024*768的图片求出阈值需要多久呢?

1.5秒。现在知道优化的重要性了吧。

优化

那么如何优化呢?其实非常简单,将灰度图缩放一下就行了。经过我的测试,哪怕长宽各缩放到原来的十分之一,也就是面积只有原来的百分之一,求出的阈值相差也不超过2,但是时间大大的缩短了,只有0.0107秒,只有原来的18分之一。并且在整个程序中时间占比只有百分之二。效果非常好!

1
2
3
img_grey = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)  # bgr turn into grey img
small_img_grey = cv2.resize(img_grey, None, fx=0.1, fy=0.1)
suitable_th = OTSU_enhance(small_img_grey)

使用求出的 suitable_th 进行二值化:

1
_, img_bin = cv2.threshold(img_grey, suitable_th, 255, cv2.cv2.THRESH_BINARY_INV)

还是用刚才的图片举例子:

即使加上缩放的时间也不过0.02秒,提升很大。

二值化的效果也非常好。