读取图像,然后将彩色图像用Otsu算法进行二值化。
Author: Tian YJ
原图如下:
关于Otsu算法
Otsu算法是灰度图像的自动阈值分割。发明人是个日本人,叫做Nobuyuki Otsu (大津展之),所以此算法也被称大津二值化法。它是一种基于全局的二值化算法,它是根据图像的灰度特性,将图像分为前景和背景两个部分。当取最佳國值时,两部分之间的差别应该是最大的,在Otsu算法中所采用的衡量差别的标准就是较为常见的最大类间方差。
前景和背景之间的类间方差如果越大,就说明构成图像的两个部分之间的差别越大,当部分目标被错分为背景或部分背景被错分为目标,都会导致两部分差别变小,当所取阈值的分割使类间方差最大时就意味着错分概率最小
Otsu使用的是聚类的思想,把图像的灰度数按灰度级分成2个部分(可以推广至n个部分),使得两个部分之间的灰度值差异最大,每个部分之间的灰度差异最小,通过方差的计算来寻找一个合适的灰度级别来划分。所以可以在二值化的时候采用Otsu算法来自动选取阈值进行二值化。Otsu算法被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,至今也被广泛应用于包含医学图像在内的各个领域。
算法流程
下图显示了Otsu算法的流程图,其具体步骤为:
(1)统计灰度级中每个像素在整幅图像中的个数;
(2)计算每个像素在整幅图像的概率分布;
(3)通过目标函数计算最大类间方差下的阈值;
(4)由所得阈值对图像进行阈值分割。
实现思路
从类内方差和类间方差的比值计算得来:
- 小于阈值 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是这两个类的像素值的平均值;
即:
- 类内方差: 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=常数
根据以上的式子,我们用以下的式子计算分离度
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
也就是说:
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。
代码实现
# -*- coding: utf-8 -*-
"""
Created on Tue Apr 7 16:43:27 2020
@author: Tian YJ
"""
import numpy as np
import cv2
# 灰度化函数
def BGR2GRAY(img):
# 灰度化
out = np.ones((H,W,3))
for i in range(H):
for j in range(W):
out[i,j,:] = 0.299*img[i,j,0] + 0.578*img[i,j,1] + 0.114*img[i,j,2]
out = out.astype(np.uint8)
return out
# Otsu 二值化
def otsu_binarization(out, th=128):
max_var = 0 # 初始化最大类间方差
max_t = 0 # 初始化最佳阈值
## 确定阈值
for t in range(255):
x_0 = out[np.where(out < t)] # 划分为0类
m_0 = np.mean(x_0) if len(x_0)>0 else 0 # 0类像素值的平均值
w_0 = len(x_0) / (H*W) # 0类像素数占总像素数的比例
x_1 = out[np.where(out >= t)] # 划分为1类
m_1 = np.mean(x_1) if len(x_1)>0 else 0 # 1类像素值的平均值
w_1 = len(x_1) / (H*W) # 1类像素数占总像素数的比例
var = w_0 * w_1 * ((m_0 - m_1)**2) # 求类间方差
if var > max_var:
max_var = var
max_t = t
# 找到最佳阈值后开始二值化
print('最佳阈值:', max_t)
th = max_t
out[out < th] = 0
out[out >= th] = 255
return out
# 读取图片
path = 'C:/Users/86187/Desktop/image/'
file_in = path + 'cake.jpg'
file_out = path + 'otsu_binarization.jpg'
img = cv2.imread(file_in)
# 获取图片尺寸
H, W, C = img.shape
# 调用函数
out = BGR2GRAY(img)
out = otsu_binarization(out)
# 保存图片
cv2.imwrite(file_out, out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果
输出图像 | 最佳阈值 |
---|---|
![]() | ![]() |
普通二值化 | Otsu二值化 |
---|---|
![]() | ![]() |
两种二值化结果对比,可以看出,Otsu确实牛B,效果非常好!