Otsu算法简介
Otsu算法是一种图像二值化的算法,它的主要思想是将图像分成背景和前景两部分,背景和前景之间的差别越大越好,也就是说背景越黑,前景越白越好,这样的话,我们就可以通过阈值来将图像二值化,阈值的选取就是Otsu算法的核心。
原始论文1979:A threshold selection method from gray-level histograms
示例图片
示例图片-计算阈值并二值化
虽然Otsu算法是图像中确定阈值的方法,但也可以应用到其他方面。比如点云根据强度值进行分割,就可以使用Otsu算法。本质上Otsu就是通过统计学的方法将输入根据某个值分成两个类。
Otsu算法只能将输入分割成两类,其本质是确定一个阈值,使得用该阈值分割出来的两类的类间(between-class)方差最大,类内(within-class)方差最小。类间方差越大,说明两类之间的差别越大,类内方差越小,说明类内的差别越小。
数学形式
均值和概率
这里主要参考原始论文中的数学形式,以及维基百科中的数学形式。
假设图片中的所有像素灰度值可以分成个等级,。比如通常对于8bit灰度图像,我们可以用256个等级表示灰度值。等级的像素数为,总像素数为,。每个等级的像素出现的概率如下:
确定阈值后可以将像素分成两类 表示像素等级在的像素,表示像素等级在的像素
两个类的像素比例为:
并且
两个类的平均灰度值为:
其中,是等级为像素的一阶累积矩(first-order cumulative moments),是总的平均灰度值。
是等级为像素的概率和
根据上面关系,我们还可以得到:
类内/类间方差
两个类各自的方差
根据上面的定义,我们可以得到两个类各自的方差
总体方差
像素总体的方差为:
类内方差
因此,我们可以得到类内(within-class)方差的公式:
类间方差
类间(between-class)方差为:
类内方差与类间方差的关系
根据公式(11-13),我们可以得到:
Otsu算法的目标
Otsu算法的目标是找到一个阈值,使得类间方差最大,也就是说,找到一个阈值,使得类内方差最小。
Otsu算法步骤
- 统计每个像素等级的像素数量,并计算每个像素等级的概率,。
- 计算每个像素等级的概率和,。
- 计算每个像素等级的平均灰度值,。
- 计算总的平均灰度值。
- 根据公式(4)可以计算出每个像素等级的平均灰度值和,。
- 遍历所有像素等级 6.1 根据公式(12)计算类间方差。 6.2 如果大于之前的最大值,则更新最大值,并记录当前的像素等级
算法实现-python
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 40 41 42 43 44 45 46 47 48 49 50 51
| import numpy as np import matplotlib.pyplot as plt from skimage import io, img_as_float from skimage.filters import threshold_otsu
def otsu(gray_img) -> int: """ Otsu算法 :param gray_img: 灰度图像 :return: 阈值 """ pixel_nums = np.zeros((256, 1)) pixel_probs = np.zeros((256, 1)) for i in range(gray_img.shape[0]): for j in range(gray_img.shape[1]): pixel_nums[gray_img[i][j]] += 1 pixel_probs = pixel_nums / (gray_img.shape[0] * gray_img.shape[1])
pixel_probs_sum = np.zeros((256, 1)) for i in range(256): if i == 0: pixel_probs_sum[i] = pixel_probs[i] else: pixel_probs_sum[i] = pixel_probs_sum[i - 1] + pixel_probs[i]
pixel_means = np.zeros((256, 1)) for i in range(256): if i == 0: pixel_means[i] = 0 * pixel_probs[i] else: pixel_means[i] = pixel_means[i - 1] + i * pixel_probs[i]
img_mean = pixel_means[-1]
max_sigma_b = 0 best_threshold = 0 for i in range(256): sigma_b = (img_mean * pixel_probs_sum[i] - pixel_means[i]) ** 2 / (pixel_probs_sum[i] * (1 - pixel_probs_sum[i])) if sigma_b > max_sigma_b: max_sigma_b = sigma k = i
|