自适应二值化及伪影
一、自适应二值化
自适应二值化方法是针对全局阈值法(如 Otsu)在光照不均匀或局部对比度差异大的场景中效果不佳而提出的解决方案。它通过动态计算图像局部区域的阈值,实现更精准的二值化。以下是几种主流自适应二值化方法的详解:
1. 基于块的局部阈值法
原理
将图像划分为多个子块,每个子块独立计算阈值(如子块均值或高斯加权均值),并根据该阈值对块内像素进行二值化。
关键参数
- 块大小(Block Size):子块的尺寸(通常为奇数,如 3×3、5×5)。
- C 值:对计算出的阈值进行偏移(如减去 C 值,避免过分割)。
实现步骤
- 将图像分割为互不重叠的子块。
- 对每个子块计算统计量(如均值、高斯加权均值)。
- 根据统计量和 C 值生成局部阈值。
- 对每个子块内的像素进行二值化。
优缺点
- 优点:适应局部光照变化。
- 缺点:块边界可能出现伪影;块大小需手动调整。
OpenCV 示例
import cv2
# 读取灰度图
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# 自适应均值阈值
adaptive_mean = cv2.adaptiveThreshold(
img, 255,
cv2.ADAPTIVE_THRESH_MEAN_C, # 均值法
cv2.THRESH_BINARY,
blockSize=11, # 块大小
C=2 # 偏移量
)
# 自适应高斯阈值
adaptive_gaussian = cv2.adaptiveThreshold(
img, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C, # 高斯加权法
cv2.THRESH_BINARY,
blockSize=11,
C=2
)
2. Niblack 算法
原理
基于局部区域的均值和标准差动态计算阈值:
:子块均值
:子块标准差
- k:调节系数(通常取 - 0.2~0.2)
改进点
通过引入标准差考虑局部对比度,适用于纹理复杂的图像。
实现步骤
- 计算每个子块的均值和标准差。
- 根据公式生成阈值。
- 二值化:像素值 > T → 255,否则 → 0。
优缺点
- 优点:对纹理和噪声有更好的适应性。
- 缺点:计算复杂度较高;k值需手动调参。
示例:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def niblack(image, window_size=15, k=0.2):
height, width = image.shape
binary_image = np.zeros_like(image)
half_window = window_size // 2
for y in range(height):
for x in range(width):
# 计算局部区域的边界
y_min = max(0, y - half_window)
y_max = min(height, y + half_window + 1)
x_min = max(0, x - half_window)
x_max = min(width, x + half_window + 1)
# 提取局部区域
local_region = image[y_min:y_max, x_min:x_max]
# 计算局部区域的均值和标准差
mean = np.mean(local_region)
std = np.std(local_region)
# 计算阈值
threshold = mean + k * std
# 根据阈值进行二值化
if image[y, x] > threshold:
binary_image[y, x] = 255
else:
binary_image[y, x] = 0
return binary_image
# 读取图像并转换为灰度图
image = cv2.imread('your_image.jpg', cv2.IMREAD_GRAYSCALE)
# 使用Niblack算法进行二值化
binary_image = niblack(image, window_size=15, k=0.2)
# 显示原始图像和二值化后的图像
plt.figure(figsize=(10, 5))
plt.subplot(121)
plt.imshow(image, cmap='gray')
plt.title('Original Image')
plt.axis('off')
plt.subplot(122)
plt.imshow(binary_image, cmap='gray')
plt.title('Niblack Binarization')
plt.axis('off')
plt.show()
代码解释
- 定义
niblack
函数:该函数接受图像、窗口大小和调节系数作为输入,返回二值化后的图像。 - 遍历图像的每个像素:对于每个像素,提取其局部区域,并计算该区域的均值和标准差。
- 计算阈值:根据 Niblack 算法的公式计算阈值。
- 根据阈值进行二值化:将像素值大于阈值的像素设为 255,小于阈值的像素设为 0。
- 返回二值化后的图像:将处理后的图像返回。
- 读取图像并进行二值化:使用
cv2.imread
函数读取图像,并调用niblack
函数进行二值化。 - 显示原始图像和二值化后的图像:使用
Matplotlib
库显示原始图像和二值化后的图像
3. Sauvola 算法
原理
在 Niblack 算法基础上改进,解决低对比度区域过分割问题:
- R:最大可能的标准差(通常设为 128)
改进点
- 引入R防止分母为零。
- 通过
归一化标准差,增强低对比度区域的鲁棒性。
实现步骤
- 计算子块的均值
和标准差
。
- 根据公式生成阈值。
- 二值化:像素值 > T → 255,否则 → 0。
优缺点
- 优点:更适合低对比度场景(如老旧文档扫描)。
- 缺点:计算复杂度高;需选择合适的k和R。
示例:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def sauvola(image, window_size=15, k=0.2, R=128):
height, width = image.shape
binary_image = np.zeros_like(image)
half_window = window_size // 2
for y in range(height):
for x in range(width):
# 计算局部区域的边界
y_min = max(0, y - half_window)
y_max = min(height, y + half_window + 1)
x_min = max(0, x - half_window)
x_max = min(width, x + half_window + 1)
# 提取局部区域
local_region = image[y_min:y_max, x_min:x_max]
# 计算局部区域的均值和标准差
mean = np.mean(local_region)
std = np.std(local_region)
# 计算阈值
threshold = mean * (1 + k * ((std / R) - 1))
# 根据阈值进行二值化
if image[y, x] > threshold:
binary_image[y, x] = 255
else:
binary_image[y, x] = 0
return binary_image
# 读取图像并转换为灰度图
image = cv2.imread('your_image.jpg', cv2.IMREAD_GRAYSCALE)
# 使用Sauvola算法进行二值化
binary_image = sauvola(image, window_size=15, k=0.2, R=128)
# 显示原始图像和二值化后的图像
plt.figure(figsize=(10, 5))
plt.subplot(121)
plt.imshow(image, cmap='gray')
plt.title('Original Image')
plt.axis('off')
plt.subplot(122)
plt.imshow(binary_image, cmap='gray')
plt.title('Sauvola Binarization')
plt.axis('off')
plt.show()
代码解释
- 定义
sauvola
函数:该函数接收图像、窗口大小、调节系数和最大可能的标准差作为输入,返回二值化后的图像。 - 遍历图像的每个像素:针对每个像素,提取其局部区域,并计算该区域的均值和标准差。
- 计算阈值:依据 Sauvola 算法的公式计算阈值。
- 根据阈值进行二值化:把像素值大于阈值的像素设为 255,小于阈值的像素设为 0。
- 返回二值化后的图像:将处理后的图像返回。
- 读取图像并进行二值化:使用
cv2.imread
函数读取图像,并调用sauvola
函数进行二值化。 - 显示原始图像和二值化后的图像:使用
Matplotlib
库显示原始图像和二值化后的图像。
4. Bernsen 算法
原理
基于局部区域的最大和最小值计算阈值:
- 若
,则强制二值化为背景。
关键参数
:对比度阈值(通常设为 15~30)。
实现步骤
- 计算子块内的最大和最小值。
- 若对比度不足
,设为背景。
- 否则,以均值为阈值二值化。
优缺点
- 优点:计算速度快,适合实时处理。
- 缺点:对噪声敏感,需结合降噪预处理。
示例:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def bernsen(image, window_size=15, delta=15):
"""
Bernsen算法实现
:param image: 输入的灰度图像
:param window_size: 局部窗口的大小,通常为奇数
:param delta: 对比度阈值
:return: 二值化后的图像
"""
height, width = image.shape
binary_image = np.zeros_like(image)
half_window = window_size // 2
for y in range(height):
for x in range(width):
# 计算局部区域的边界
y_min = max(0, y - half_window)
y_max = min(height, y + half_window + 1)
x_min = max(0, x - half_window)
x_max = min(width, x + half_window + 1)
# 提取局部区域
local_region = image[y_min:y_max, x_min:x_max]
# 计算局部区域的最大值和最小值
local_max = np.max(local_region)
local_min = np.min(local_region)
# 计算对比度
contrast = local_max - local_min
# 计算阈值
threshold = (local_max + local_min) / 2
if contrast < delta:
# 若对比度小于阈值,则认为是低对比度区域,将该像素设为背景(这里设为0)
binary_image[y, x] = 0
else:
# 根据阈值进行二值化
if image[y, x] > threshold:
binary_image[y, x] = 255
else:
binary_image[y, x] = 0
return binary_image
# 读取图像并转换为灰度图
image = cv2.imread('your_image.jpg', cv2.IMREAD_GRAYSCALE)
# 使用Bernsen算法进行二值化
binary_image = bernsen(image, window_size=15, delta=15)
# 显示原始图像和二值化后的图像
plt.figure(figsize=(10, 5))
plt.subplot(121)
plt.imshow(image, cmap='gray')
plt.title('Original Image')
plt.axis('off')
plt.subplot(122)
plt.imshow(binary_image, cmap='gray')
plt.title('Bernsen Binarization')
plt.axis('off')
plt.show()
代码解释:
- 函数定义:定义了
bernsen
函数,该函数接收三个参数:输入的灰度图像image
、局部窗口的大小window_size
(通常为奇数)和对比度阈值delta
。 - 遍历图像像素:对于图像中的每个像素,提取其所在的局部区域。
- 计算局部区域的最值和对比度:计算局部区域的最大值
local_max
和最小值local_min
,并计算它们的差值作为对比度contrast
。 - 计算阈值:通过
(local_max + local_min) / 2
计算阈值。 - 二值化处理:
- 如果对比度小于
delta
,认为该区域是低对比度区域,将该像素设为背景(这里设为 0)。 - 否则,根据计算得到的阈值对该像素进行二值化处理。
- 如果对比度小于
- 读取图像并进行二值化:使用
cv2.imread
函数读取图像,并将其转换为灰度图。调用bernsen
函数进行二值化处理。 - 显示结果:使用
matplotlib
库显示原始图像和二值化后的图像。
注意事项
window_size
和delta
是可以调整的参数,你可以根据具体的图像特点进行调整,以获得更好的二值化效果。一般来说,window_size
越大,算法对局部信息的考虑范围越广,但计算量也会相应增加;delta
越大,对低对比度区域的判定越严格。
5. 自适应 Otsu(局部 Otsu)
原理
将图像划分为多个子块,对每个子块单独应用 Otsu 算法计算阈值。
实现步骤
- 分块。
- 对每个子块计算 Otsu 阈值。
- 对每个子块内的像素二值化。
优缺点
- 优点:结合 Otsu 的自适应性,处理复杂光照。
- 缺点:计算量大;块边界可能不一致。
示例:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def adaptive_otsu(image, block_size=16):
"""
自适应Otsu算法实现
:param image: 输入的灰度图像
:param block_size: 子块的大小
:return: 二值化后的图像
"""
height, width = image.shape
binary_image = np.zeros_like(image)
for y in range(0, height, block_size):
for x in range(0, width, block_size):
# 计算子块的边界
y_end = min(y + block_size, height)
x_end = min(x + block_size, width)
# 提取子块
sub_block = image[y:y_end, x:x_end]
# 对当前子块应用Otsu算法
_, sub_binary = cv2.threshold(sub_block, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 将子块的二值化结果放回原图像对应的位置
binary_image[y:y_end, x:x_end] = sub_binary
return binary_image
# 读取图像并转换为灰度图
image = cv2.imread('your_image.jpg', cv2.IMREAD_GRAYSCALE)
# 使用自适应Otsu算法进行二值化
binary_image = adaptive_otsu(image, block_size=16)
# 显示原始图像和二值化后的图像
plt.figure(figsize=(10, 5))
plt.subplot(121)
plt.imshow(image, cmap='gray')
plt.title('Original Image')
plt.axis('off')
plt.subplot(122)
plt.imshow(binary_image, cmap='gray')
plt.title('Adaptive Otsu Binarization')
plt.axis('off')
plt.show()
代码解释:
- 函数定义:定义了
adaptive_otsu
函数,它接收两个参数,image
是输入的灰度图像,block_size
是子块的大小,默认值为 16。 - 遍历子块:通过嵌套的
for
循环遍历图像的每个子块。 - 提取子块:根据当前的坐标和子块大小,确定子块的边界,并提取出对应的子块。
- 应用 Otsu 算法:对提取的子块使用
cv2.threshold
函数结合cv2.THRESH_BINARY + cv2.THRESH_OTSU
参数进行 Otsu 二值化处理。 - 更新二值化图像:将子块的二值化结果放回原图像对应的位置。
- 读取图像并进行二值化:使用
cv2.imread
函数读取图像,并将其转换为灰度图。调用adaptive_otsu
函数进行自适应 Otsu 二值化处理。 - 显示结果:使用
matplotlib
库显示原始图像和二值化后的图像。
注意事项:
block_size
是一个重要的参数,它会影响二值化的效果。较小的block_size
可以更好地适应局部光照变化,但可能会增加计算量和出现块边界伪影;较大的block_size
则可能导致对局部光照变化的适应性变差。你可以根据具体的图像特点调整该参数。
6. 方法对比与选择
方法 | 适用场景 | 计算复杂度 | 调参难度 |
---|---|---|---|
自适应均值 / 高斯 | 均匀光照下的轻微对比度变化 | 低 | 低 |
Niblack | 纹理复杂、噪声较多的图像 | 中 | 中 |
Sauvola | 低对比度、老旧文档扫描 | 高 | 高 |
Bernsen | 实时处理、简单场景 | 低 | 中 |
自适应 Otsu | 全局光照不均、复杂背景 | 高 | 低 |
7. 优化技巧
- 预处理降噪:先进行高斯模糊或中值滤波,减少噪声对局部统计量的影响。
- 重叠分块:相邻子块部分重叠,减少边界伪影。
- 动态块大小:根据图像复杂度调整块大小(如使用多尺度分块)。
- 结合全局阈值:对整体均匀的区域使用全局阈值,局部复杂区域使用自适应方法。
总结
自适应二值化通过局部阈值解决全局阈值的局限性,适用于光照不均、对比度差异大的场景。选择方法时需权衡计算复杂度、调参难度和场景需求。实际应用中,常结合预处理(如降噪)和后处理(如形态学操作)提升效果。
二、伪影
伪影(Artifact)是指在图像处理或信号采集过程中,由于算法局限性、硬件误差或噪声干扰等因素引入的非真实、异常的图像结构或像素值。在二值化处理中,伪影通常表现为图像中出现的不自然边缘、斑点或纹理,这些特征并非原始图像的真实内容。
二值化中伪影的常见表现
-
块边界伪影
- 现象:图像被划分为多个子块后,块与块之间的阈值差异导致边界处出现明显的亮暗分割线。
- 原因:基于块的局部阈值法(如自适应均值 / 高斯阈值)中,相邻子块的局部阈值不同,导致块边界像素的二值化结果不一致。
-
噪声放大伪影
- 现象:图像中出现孤立的亮点或暗点,类似于椒盐噪声。
- 原因:局部阈值计算受噪声影响(如 Niblack 算法中标准差的波动),导致部分噪声像素被错误分类。
-
纹理丢失或过分割伪影
- 现象:图像中原本连续的纹理被分割成不连贯的区域,或低对比度区域被错误地全部设为背景。
- 原因:阈值选择不当(如 Sauvola 算法中参数 k 设置不合理),或局部统计量未能准确反映图像内容。
伪影的主要成因
-
分块处理的局限性
- 固定块大小无法适应图像内容的变化,导致边缘或纹理区域被错误分割。
-
局部统计量的不稳定性
- 子块内像素分布不均(如包含噪声或复杂纹理)时,均值、标准差等统计量无法代表真实特征。
-
参数设置不合理
- 如块大小、C 值、k 值等参数未根据图像特性调整,导致阈值计算偏离最优值。
减少伪影的方法
-
重叠分块
- 相邻子块部分重叠(如 50% 重叠),通过插值或加权平均平滑块边界的阈值差异。
-
动态块大小
- 根据图像复杂度自动调整块大小(如在纹理密集区域使用更小的块)。
-
预处理降噪
- 使用高斯模糊、中值滤波等方法减少噪声对局部统计量的干扰。
-
后处理优化
- 应用形态学操作(如闭运算)填补小孔或平滑边缘,或通过连通组件分析去除孤立噪声点。
-
改进阈值计算方法
- 采用更鲁棒的局部统计量(如中值而非均值),或结合全局信息(如自适应 Otsu)。
示例说明
-
基于块的局部阈值法伪影:
若图像中某块均值较高(如明亮区域),相邻块均值较低(如阴影区域),块边界的像素可能因阈值差异被错误二值化,形成明显的分界线。 -
Niblack 算法伪影:
当子块内存在噪声时,标准差 σ 增大,导致阈值 T 偏高,部分真实前景像素被误判为背景。
总结
伪影是二值化处理中常见的干扰,其成因与算法设计、参数选择及图像特性密切相关。通过优化分块策略、预处理降噪及后处理调整,可以有效减少伪影,提升二值化结果的质量。