挑战图像处理100问(4)——Otsu

在这里插入图片描述
读取图像,然后将彩色图像用Otsu算法进行二值化。
Author: Tian YJ
原图如下:

在这里插入图片描述

关于Otsu算法

Otsu算法是灰度图像的自动阈值分割。发明人是个日本人,叫做Nobuyuki Otsu (大津展之),所以此算法也被称大津二值化法。它是一种基于全局的二值化算法,它是根据图像的灰度特性,将图像分为前景和背景两个部分。当取最佳國值时,两部分之间的差别应该是最大的,在Otsu算法中所采用的衡量差别的标准就是较为常见的最大类间方差。

前景和背景之间的类间方差如果越大,就说明构成图像的两个部分之间的差别越大,当部分目标被错分为背景或部分背景被错分为目标,都会导致两部分差别变小,当所取阈值的分割使类间方差最大时就意味着错分概率最小

Otsu使用的是聚类的思想,把图像的灰度数按灰度级分成2个部分(可以推广至n个部分),使得两个部分之间的灰度值差异最大,每个部分之间的灰度差异最小,通过方差的计算来寻找一个合适的灰度级别来划分。所以可以在二值化的时候采用Otsu算法来自动选取阈值进行二值化。Otsu算法被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,至今也被广泛应用于包含医学图像在内的各个领域。

算法流程

下图显示了Otsu算法的流程图,其具体步骤为:
(1)统计灰度级中每个像素在整幅图像中的个数;
(2)计算每个像素在整幅图像的概率分布;
(3)通过目标函数计算最大类间方差下的阈值;
(4)由所得阈值对图像进行阈值分割。

在这里插入图片描述

实现思路

类内方差类间方差的比值计算得来:

  • 小于阈值tt的类记作00,大于阈值tt的类记作11
  • w0w_0w1w_1是被阈值tt分开的两个类中的像素数占总像素数的比率(满足w0+w1=1w_0+w_1=1);
  • S02{S_0}^2S12{S_1}^2是这两个类中像素值的方差;
  • M0M_0M1M_1是这两个类的像素值的平均值;

即:

  • 类内方差:Sw2=w0 S02+w1 S12{S_w}^2=w_0 {S_0}^2+w_1 {S_1}^2
  • 类间方差:Sb2=w0 (M0Mt)2+w1 (M1Mt)2=w0 w1 (M0M1)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
  • 图像所有像素的方差:St2=Sw2+Sb2=常数{S_t}^2 = {S_w}^2 + {S_b}^2 = ext{常数}

根据以上的式子,我们用以下的式子计算分离度XX
X=Sb2Sw2=Sb2St2Sb2 X = frac{{S_b}^2}{{S_w}^2} = frac{{S_b}^2}{{S_t}^2 - {S_b}^2}

也就是说:
argmaxt X=argmaxt Sb2 argmaxlimits_{t} X=argmaxlimits_{t} {S_b}^2
换言之,如果使Sb2=w0 w1 (M0M1)2{S_b}^2={w_0} {w_1} (M_0 - M_1)^2最大,就可以得到最好的二值化阈值tt

代码实现

# -*- 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,效果非常好!

原文地址:https://www.cnblogs.com/Jack-Tim-TYJ/p/12831924.html