首页  > 怀旧特辑

图像二值化的几种方法

图像二值化的几种方法计算机最早用于计算数据,破译密码、统计人口信息等。

数据进到计算机当中需要变成计算机能理解的形式:

数据可以通过进制转换传入:十进制转二进制。

文本可以通过编码+码表(多种码表)的方式传入并识别。

图片可以通过文件头+内容的方式来识别。

图片有多种,常见的有:

二值图:仅纯黑与纯白 每个像素点1位,仅能表示0/1

灰度图: 将黑-白分成 0-255 来表示黑白的中间态 每个像素点8位,能表示0-255

彩色图: 三个通道,表示红绿蓝,三个通道混合出255255255种颜色 每个像素点24位

位 : 我们常说的保留2位小数,表示保留2个数字. 因此8位的二进制,就是8个二进制数据

它最多可以表示:2的0次方+2的1次方+.....2的7次方.(简化为:2的8次方-1) = 256-1 = 255

我们机器学习会占用大量的资源:存储需要硬盘,运算需要内存.

因此需要以尽可能少的数据来分析,这个步骤可以称之为:抽象或者简化,更专业的术语叫:特征提取.

我们搜索毕加索的名画 《公牛》 和它的草稿.

可以看到,毕加索很好的对牛进行了特征提取,他用更少的线条,表达了公牛这个主题.

另外我们也能发现,特征提取不是一个过程,而是一组过程.

提示注意:特征提取往往是一个不可逆的过程,确保你提取的是你要的特征,舍弃的是你不需要的特征.

我们这次项目的整体目标是:实现一个简单的图像识别功能.当然这样说你可能还是不太理解

你可以把它具象化为:手写数字识别 也可以具象化为 猫狗分类 鸢尾花分类 等等

第一步是把图像的特征提取出来,这章节我们完成特征提取的上半部分:数据简化

在图片的各自各中,我们显而易见二值图是最小的.我们尝试把一张彩色图片变成一张灰度图,再变成一张二值图.

原理​

人为设置阈值法​

在这种方法中,我们人为设定一个阈值,比如 取 255 的中间值 127。 大于这个值则取1,小于等于这个值则取0

在 Python 中,这种除法运算后向下取整的操作可以表示为 255 // 2,也等价于 int(255/2)。

存在的问题:

这种方法的主要问题在于,它并不能适应所有类型的图像。

例如,对于一张整体偏暗的照片,大部分像素值可能都会低于 127,因此二值化后的图像几乎全黑。

同样,对于一张整体偏亮的照片,大部分像素值可能都会高于 127,因此二值化后的图像几乎全白。

优化方法:平均值法​

为了解决上述问题,我们可以使用一种被称为"平均值法"的优化方法。在这种方法中,我们不再使用固定的阈值,而是计算图像本身的平均像素值,并用这个值作为阈值。

优点:

这种方法可以更好地适应不同亮度的图像,因为它根据图像的实际像素值分布来确定阈值,而不是简单地使用固定的阈值。

对于那些整体偏暗或偏亮的图像,使用平均值法可以得到更好的二值化效果。

注意:

尽管平均值法在许多情况下都能得到不错的结果,但它仍然不能解决所有问题。例如,对于那些既有很亮的区域又有很暗的区域的图像,平均值法可能无法得到理想的二值化效果。在这种情况下,可能需要使用更复杂的方法,如自适应阈值法或 Otsu's 方法等。

自适应阈值法(高阶学生需要掌握)​

自适应阈值法是一种更为复杂的二值化方法,它不是使用一个全局阈值,而是为图像中的每一个小区域计算一个阈值。这意味着在图像的不同部分可能会使用不同的阈值,使得二值化的效果更好。

自适应阈值法的基本步骤如下:

将图像分割为许多小区域。

对于每一个小区域,计算其像素值的平均值或中值,并将这个值作为该区域的阈值。

对每一个小区域进行二值化。

这种方法对于那些局部亮度变化大的图像特别有效,但是计算量比较大。。

该方法常常用于自动驾驶,可以根据不同的光照条件,自动调整阈值。

Otsu's 方法(高阶学生需要掌握)​

Otsu's 方法是一种自动确定阈值的方法,它通过使得二值化后的图像的类间方差最大化来确定最佳阈值。这种方法的基本步骤如下:

对图像的灰度直方图进行归一化,使其成为一个概率分布。

对于每一个可能的阈值,计算类间方差。

选择使得类间方差最大的阈值作为最佳阈值。

Otsu's 方法对于那些有明显的双峰灰度直方图的图像特别有效,但是对于灰度分布比较均匀的图像可能效果不佳。

直方图​

假设一张 2*2 大小的灰色图片。

一共四个像素,2 个像素是纯白(0),另外两个像素是纯黑(255)。

它的直方图大致如下(省去中间部分)

归一化​

归一化(Normalization)是一种常见的数据预处理技术,用于改变数据的范围以使其落在一个特定的区间,通常是[0,1]或者[-1,1]。归一化的目的是消除数据量纲和尺度的影响,使得不同尺度或单位的数据可以在同一水平上进行比较或处理。

举个例子,假设你有一组数据,范围在 100 到 1000 之间。通过归一化,你可以把这组数据的范围变为 0 到 1。最常见的归一化方法是线性归一化,公式如下:

x′=x−min(x)max(x)−min(x)x' = \frac{x - min(x)}{max(x) - min(x)}x′=max(x)−min(x)x−min(x)​

其中xxx是原始数据,min(x)min(x)min(x)和max(x)max(x)max(x)分别是数据的最小值和最大值,x′x'x′是归一化后的数据。

在很多机器学习和数据分析任务中,归一化是一个常见且重要的步骤,因为很多算法在处理数据时都假设数据已经被归一化,或者至少数据在各个维度上的范围是相似的。

方差​

差(第一声)的意思是离正常有多远

按照以下的步骤来计算方差:

求数值的 平均

从每一个数值减去平均,然后求差的平方。

把差的平方相加,求结果的平均。(拓展:标准差是方差的平方根)

为什么要求差的平方?

如果我们只把和平均的差加起来……负值和正值便会互相抵消

用绝对值叫 平均差 ,当数据比较分散,但平均后结果不变,因此它不能反映数据离散程度

|4| + |4| + |−4| + |−4| = 16 16/4 = 4

|7| + |1| + |−6| + |−2| = 16 16/4 = 4

方差(标准差)可以反映数据的离散程度

√ [( 72 + 12 + 62 + 22)/4] = 4.74

类间方差​

类间方差是指二值化后的两个类(即,阈值以上和以下的像素)的平均值之间的方差。

给定一个灰度图像,其灰度直方图为 h(rk)h(r_k)h(rk​),k=0,1,2,...,L−1k = 0, 1, 2, ..., L-1k=0,1,2,...,L−1,其中rkr_krk​是像素值,LLL是灰度级别(对于 8 位图像,L=256L=256L=256)。设阈值为ttt,则像素值小于ttt的类记为 C0,像素值大于等于ttt的类记为 C1。

首先,我们需要计算两个类的概率 p0(t)p_0(t)p0​(t) 和 p1(t)p_1(t)p1​(t),它们分别表示图像中像素值小于ttt和大于等于ttt的概率:

p0(t)=∑i=0t−1h(ri)p_0(t) = \sum_{i=0}^{t-1} h(r_i)p0​(t)=∑i=0t−1​h(ri​)

p1(t)=∑i=tL−1h(ri)p_1(t) = \sum_{i=t}^{L-1} h(r_i)p1​(t)=∑i=tL−1​h(ri​)

然后,我们计算两个类的平均灰度 μ0(t)\mu_0(t)μ0​(t) 和 μ1(t)\mu_1(t)μ1​(t):

μ0(t)=∑i=0t−1i⋅h(ri)p0(t)\mu_0(t) = \sum_{i=0}^{t-1} \frac{i \cdot h(r_i)}{p_0(t)}μ0​(t)=∑i=0t−1​p0​(t)i⋅h(ri​)​

μ1(t)=∑i=tL−1i⋅h(ri)p1(t)\mu_1(t) = \sum_{i=t}^{L-1} \frac{i \cdot h(r_i)}{p_1(t)}μ1​(t)=∑i=tL−1​p1​(t)i⋅h(ri​)​

类间方差 σB2(t)\sigma^2_B(t)σB2​(t) 定义为:

σB2(t)=p0(t)⋅(μ0(t)−μT)2+p1(t)⋅(μ1(t)−μT)2\sigma^2_B(t) = p_0(t) \cdot ( \mu_0(t) - \mu_T )^2 + p_1(t) \cdot ( \mu_1(t) - \mu_T )^2σB2​(t)=p0​(t)⋅(μ0​(t)−μT​)2+p1​(t)⋅(μ1​(t)−μT​)2

其中,μT\mu_TμT​ 是图像的总平均灰度,计算公式为:

μT=∑i=0L−1i⋅h(ri)\mu_T = \sum_{i=0}^{L-1} i \cdot h(r_i)μT​=∑i=0L−1​i⋅h(ri​)

Otsu's 方法的目标就是找到一个阈值ttt,使得类间方差σB2(t)\sigma^2_B(t)σB2​(t)最大。这个阈值就是最佳的二值化阈值。这种方法假设图像包含两类像素(即前景和背景),且这两类像素的灰度值分布是双峰的。在这种情况下,类间方差最大的阈值可以很好地分割这两类像素。

(t)最大。这个阈值就是最佳的二值化阈值。这种方法假设图像包含两类像素(即前景和背景),且这两类像素的灰度值分布是双峰的。在这种情况下,类间方差最大的阈值可以很好地分割这两类像素。

缺点:计算量都比较大,特别是对于大图像或高分辨率的图像。因此,在实际使用时需要考虑到计算效率和内存使用的问题。

方法​

你可能需要安装如下代码库。

pip install numpy pillow matplotlib

注意,这些代码示例都假设 image 是一个二维的 NumPy 数组,代表一个灰度图像,像素值在 0 到 255 之间。

# 用于数据计算import numpy as np# 用于图像读取与处理from PIL import Image# 用于图像显示import matplotlib.pyplot as pltclass ImageThresholding: def __init__(self, image_path): # 打开图片 img_data = Image.open(image_path) # 转化为灰度图 img_data_L = img_data.convert('L')'''.convert('参数'),其中参数有很多种,L表示灰度,1表示二值化,但是模块默认的二值化效果不是很好,可以通过下面方法查看默认的二值化结果self.image = Image.open(image_path).convert('1')self.image.show()''' # 图片数组化'''现阶段可以把数组近似理解为列表''' img_data_List = np.array(img_data_L) self.image = img_data_List def threshold_fixed(self, threshold=127):'''数组和数字比大小会得到另一个数组[3,5,2] > 4 3 与 4 比较结果是 False5 与 4 比较结果是 True2 与 4 比较结果是 False最后得到 [False , True , False].astype(int)作用是把布尔值转化为数字False 转化为 0True 转化为 1[False , True , False] 得到 [0 , 1 , 0]''' return (self.image > threshold).astype(int) def threshold_mean(self):'''.mean 方法是求平均值''' threshold = self.image.mean() return (self.image > threshold).astype(int) def threshold_adaptive(self, window_size=3):'''zero_like 是生成一个同样大小格式,但是数据全为0的数组window_size = 3 表示窗口大小为3*3 ,通常为 2 ~ 6 ,太大运算量会爆炸下面的嵌套循环表示分块''' binary_image = np.zeros_like(self.image) h, w = self.image.shape for i in range(h): for j in range(w): i_start = max(0, i - window_size // 2) i_end = min(h, i + window_size // 2 + 1) j_start = max(0, j - window_size // 2) j_end = min(w, j + window_size // 2 + 1) window = self.image[i_start:i_end, j_start:j_end] threshold = window.mean() binary_image[i, j] = self.image[i, j] > threshold return binary_image.astype(int) def threshold_otsu(self): histogram = np.bincount(self.image.ravel(), minlength=256) histogram = histogram / np.sum(histogram) mu_T = np.sum([i * histogram[i] for i in range(256)]) best_sigma_B = 0 best_threshold = 0 for t in range(256): p_0 = np.sum(histogram[:t]) p_1 = np.sum(histogram[t:]) mu_0 = np.sum([i * histogram[i] for i in range(t)]) / p_0 if p_0 > 0 else 0 mu_1 = np.sum([i * histogram[i] for i in range(t, 256)]) / p_1 if p_1 > 0 else 0 sigma_B = p_0 * (mu_0 - mu_T) ** 2 + p_1 * (mu_1 - mu_T) ** 2 if sigma_B > best_sigma_B: best_sigma_B = sigma_B best_threshold = t return (self.image > best_threshold).astype(int) def show_image(self, image, title): plt.imshow(image, cmap='gray')# 显示灰度图 plt.title(title)# 设置标题 plt.axis('off')# 不要显示坐标轴 plt.show()if __name__ == "__main__": thresholding = ImageThresholding('img.bmp') thresholding.show_image(thresholding.threshold_fixed(), 'Fixed Thresholding') thresholding.show_image(thresholding.threshold_mean(), 'Mean Thresholding') thresholding.show_image(thresholding.threshold_adaptive(), 'Adaptive Thresholding') thresholding.show_image(thresholding.threshold_otsu(), 'Otsu Thresholding')

以下原图与四种二值化方案的图像效果:

|

其他​

除了上述的方法之外,还有许多其他的二值化方法,每种方法都有其适用的场景和优缺点。

以下是一些常见的二值化方法:

双峰法(Bimodal method):这种方法假设图像的直方图是双峰的,即有两个主要的亮度级别(通常对应于对象和背景)。阈值被设置为这两个峰值之间的最低点。

最小误差法(Minimum error method):这种方法假设像素的灰度值来自两个具有相同方差的高斯分布。阈值被设置为最小化两个分布之间的误差的值。

最大熵法(Maximum entropy method):这种方法选择使得二值化后的两个类(即,阈值以上和以下的像素)的熵之和最大的阈值。

Kittler-Illingworth 最小误差法:这种方法是最小误差法的一个改进版本,它可以处理来自不同高斯分布(即,具有不同方差)的像素。

ISODATA 算法:这是一种迭代的方法,它开始于图像的平均灰度值,然后迭代地计算两个类的平均值,直到阈值稳定。

Niblack 方法:这是一种局部阈值选择方法,它根据每个像素周围的像素的平均值和标准差来计算阈值。

Sauvola 方法:这是 Niblack 方法的一个改进版本,它引入了一个新的参数来处理不同对比度的图像。

多阈值方法:这些方法不是选择一个阈值,而是选择多个阈值,将像素分割成多个类。这对于一些应用可能会很有用,例如在处理具有多个亮度级别的对象的图像时。

后话​

今天这节课中用到的特征提取思想是神经网络的前置内容,特征提取+算法 = 机器学习

机器学习如果想达到效果最好,往往需要充分利用当前计算机的资源,

而特征提取的小而准意味着同样性能的机器,可以学习到更多的样本.见的更多,自然识的更广.