前言:
最近由于在做图像处理方面的工作,需要自己编写提取图像中对象边缘的算法,我目前所采用的事最简单的阈值分割算法,在此算法中最重要的一个参数就是用于分割前景和背景的阈值。刚开始做的时候阈值都是通过手动调整然后观察效果来设定的,之后再网上查阅了许多前辈的博客之后,了解了OTSU算法,可以用于自动计算最佳的分割阈值。
原理:
大津法(OTSU)是一种确定图像二值化分割阈值的算法,由日本学者大津于1979年提出。从大津法的原理上来讲,该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景图像的类间方差最大。
按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。
对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为μ,类间方差记为g。
假设图像的背景较暗,并且图像的大小为M×N,图像中像素的灰度值小于阈值T的像素个数记作N0,像素灰度大于阈值T的像素个数记作N1,则有:
ω0=N0/ M×N (1)
ω1=N1/ M×N (2)
N0+N1=M×N (3)
ω0+ω1=1 (4)
μ=ω0*μ0+ω1*μ1 (5)
g=ω0(μ0-μ)^2+ω1(μ1-μ)^2 (6)
将式(5)代入式(6),得到等价公式:
g=ω0ω1(μ0-μ1)^2 (7) 这就是类间方差
采用遍历的方法得到使类间方差g最大的阈值T,即为所求。
C++实现:
在我制作的MFC程序中加入了OTSU算法的实现,具体代码见github
Python实现:
import numpy as np
def OTSU(img_array): #传入的参数为ndarray形式
height = img_array.shape[0]
width = img_array.shape[1]
count_pixel = np.zeros(256)
for i in range(height):
for j in range(width):
count_pixel[int(img_array[i][j])] += 1
# fig = plt.figure() #通过绘制直方图可以观察像素的分布情况
# ax = fig.add_subplot(111)
# ax.bar(np.linspace(0, 255, 256), count_pixel)
# ax.set_xlabel("pixels")
# ax.set_ylabel("num")
# plt.show()
max_variance = 0.0
best_thresold = 0
for thresold in range(256):
n0 = count_pixel[:thresold].sum()
n1 = count_pixel[thresold:].sum()
w0 = n0 / (height * width)
w1 = n1 / (height * width)
u0 = 0.0
u1 = 0.0
for i in range(thresold):
u0 += i * count_pixel[i]
for j in range(thresold, 256):
u1 += j * count_pixel[j]
u = u0 * w0 + u1 * w1
tmp_var = w0 * np.power((u - u0), 2) + w1 * np.power((u - u1), 2)
if tmp_var > max_variance:
best_thresold = thresold
max_variance = tmp_var
return best_thresold