计算机视觉(四):二值化
二值化,就是将图像从彩色或灰度模式转换为只有两种颜色(通常是黑色和白色)的模式。这个过程的本质是设定一个阈值 (Threshold),将图像中所有像素的灰度值与这个阈值进行比较。
基本原理
二值化的核心原理非常简单:
- 灰度化:如果原始图像是彩色的,首先需要将其转换为灰度图像。这是因为彩色图像有三个通道(红、绿、蓝),而灰度图像只有一个灰度通道,每个像素的值代表其亮度,从 0(最黑)到 255(最白)。
- 设定阈值:选择一个介于 0 到 255 之间的数值作为阈值。这个阈值是二值化成功的关键。
- 像素比较与转换:遍历图像中的每一个像素,执行以下操作:
- 如果像素的灰度值大于设定的阈值,就将其灰度值设置为一个固定值(通常是 255,代表白色)。
- 如果像素的灰度值小于或等于设定的阈值,就将其灰度值设置为另一个固定值(通常是 0,代表黑色)。
经过上述处理后,图像中的所有像素都只剩下两种可能的值:0 和 255,从而得到了一个黑白分明的二值化图像。
为什么要进行二值化?
二值化是许多图像处理和计算机视觉任务中的一个重要预处理步骤。它之所以重要,主要有以下几个原因:
- 简化图像数据:将图像从多级灰度简化为黑白两色,极大地减少了数据量和处理的复杂性。这使得后续的算法(如边缘检测、轮廓提取)可以更快、更高效地运行。
- 突出目标:通过合适的阈值,可以有效地将图像中的前景目标(如文字、物体)从背景中分离出来,使其更加突出和易于识别。
- 便于分析和识别:在光学字符识别(OCR)、条形码识别、医学图像分析等领域,二值化是必不可少的步骤。它能帮助算法更准确地识别出字符或病变区域。
如何选择合适的阈值?
选择阈值是二值化的核心挑战。错误的阈值会导致信息丢失或引入噪声。根据不同的应用场景,选择阈值的方法主要分为以下两类:
- 全局阈值(Global Thresholding)
- 概念:对整张图像使用同一个固定的阈值。
- 方法:
- 手动设置:根据经验或对图像的初步分析来设定一个固定的值。
- 自动计算:通过算法自动寻找一个最优的阈值,例如大津法(Otsu’s method)。大津法的基本思想是找到一个阈值,使得前景和背景的方差最大化,从而实现最佳的分离效果。
- 局限性:当图像的光照不均匀时,全局阈值效果会很差。例如,如果图像左侧很亮而右侧很暗,一个固定的阈值就无法同时正确地分离两边的目标。
- 局部阈值(Local/Adaptive Thresholding)
- 概念:将图像分割成许多小的区域,对每个小区域分别计算并应用不同的阈值。
- 方法:
- 均值法(Mean):计算每个小区域内的像素平均值作为该区域的阈值。
- 高斯法(Gaussian):在计算均值时,为中心像素附近的像素赋予更高的权重,从而得到一个加权平均值作为阈值。
- 优势:这种方法能很好地处理光照不均或背景复杂的情况,因为它能够根据局部环境自动调整阈值。
灰度图
灰度图的本质
- 单通道:彩色图像通常有三个颜色通道(红、绿、蓝,即 RGB),每个像素由这三个通道的值组合而成,可以表示数百万种颜色。而灰度图只有一个通道,每个像素的值只代表其亮度或灰度级别。
- 0-255 的数值:在 8 位灰度图中,每个像素的取值范围通常是 0 到 255。
- 0 代表最黑。
- 255 代表最白。
- 0 到 255 之间的值则代表不同深浅的灰色,值越大,颜色越亮。
- 黑白过渡:与只有纯黑和纯白的二值化图像不同,灰度图能够平滑地表现出从黑到白的各种过渡,保留了图像的更多细节信息。
为什么需要灰度图?
将彩色图转换为灰度图是许多图像处理任务中的一个重要步骤,主要有以下几个原因:
- 简化数据:灰度图的数据量比彩色图小得多。彩色图需要三个字节(RGB)来存储一个像素,而灰度图只需要一个字节。这可以大大减少存储空间和处理时间。
- 突出亮度信息:在许多视觉任务中,例如边缘检测、轮廓识别、物体追踪等,算法主要依赖于图像的亮度或明暗变化。将图像转换为灰度图可以去除不必要的颜色信息,使算法能更专注于分析亮度差异,从而提高效率和准确性。
- 预处理步骤:在进行二值化、直方图均衡化等操作之前,通常需要先将图像转换为灰度图。
如何从彩色图得到灰度图?
将一张彩色图片转换为灰度图,最常见的方法是根据人眼对不同颜色的敏感度,对 RGB 三个通道的值进行加权平均。
一个常用的转换公式是:
灰度值=0.299×红色值+0.587×绿色值+0.114×蓝色值
这个公式反映了人眼对绿色最敏感,其次是红色,对蓝色最不敏感。OpenCV 等库在进行彩色到灰度转换时,通常会采用类似的加权平均方法。
如果是YUV 转换为灰度图,由于 Y 分量本身就直接代表了图像的亮度信息,而灰度图的本质就是亮度图,因此,从 YUV 转换为灰度图只需要提取 Y 分量即可。
OpenCV实现YUV到灰度图的转换
import cv2
import numpy as np# 1. 加载一张彩色图像 (OpenCV默认以 BGR 格式读取)
bgr_img = cv2.imread('your_image_path.jpg')# 检查图像是否成功加载
if bgr_img is None:print("Error: Could not read the image.")
else:# 2. 将 BGR 图像转换为 YUV 格式# OpenCV 使用 YUV 而非 YCbCr,但原理相同yuv_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2YUV)# 3. 提取 Y 分量(亮度通道),作为灰度图# Y 分量是 YUV 图像的第一个通道gray_img = yuv_img[:,:,0]# 4. 显示原始彩色图像和生成的灰度图像cv2.imshow('Original BGR Image', bgr_img)cv2.imshow('Gray Image from Y Channel', gray_img)# 等待按键,然后关闭所有窗口cv2.waitKey(0)cv2.destroyAllWindows()
opencv实现二值化
cv2.threshold() 函数
ret, dst = cv2.threshold(src, thresh, maxval, type)
参数解释:
src
: 原始图像,必须是灰度图。thresh
: 设定的阈值。maxval
: 当像素值超过(或低于)阈值时,所赋予的最大值。通常是 255。type
: 值的类型,决定了如何应用阈值。这是最关键的参数,常用的类型包括:cv2.THRESH_BINARY
: 最基础的二值化。如果像素值大于thresh
,则设置为maxval
;否则设置为 0。cv2.THRESH_BINARY_INV
: 与THRESH_BINARY
相反。如果像素值大于thresh
,则设置为 0;否则设置为maxval
。cv2.THRESH_TRUNC
: 截断。如果像素值大于thresh
,则设置为thresh
;否则保持不变。cv2.THRESH_TOZERO
: 如果像素值大于thresh
,则保持不变;否则设置为 0。cv2.THRESH_TOZERO_INV
: 与THRESH_TOZERO
相反。如果像素值大于thresh
,则设置为 0;否则保持不变。
返回值:
ret
: 设定的阈值。当使用 Otsu’s 或 Triangle 方法时,返回的是自动计算出的阈值。dst
: 二值化后的图像。
实现基础二值化
import cv2
import numpy as np# 1. 加载图像并转换为灰度图
img = cv2.imread('your_image_path.jpg', cv2.IMREAD_GRAYSCALE)# 检查图像是否成功加载
if img is None:print("Error: Could not read the image.")
else:# 2. 设定阈值并进行二值化处理# 设定阈值为127,最大值为255ret, binary_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)# 打印返回的阈值(这里会是127)print(f"Threshold value returned: {ret}")# 3. 显示原始图像和二值化后的图像cv2.imshow('Original Grayscale Image', img)cv2.imshow('Binary Image', binary_img)# 等待按键,然后关闭所有窗口cv2.waitKey(0)cv2.destroyAllWindows()
使用 Otsu’s 方法自动寻找阈值
import cv2
import numpy as np# 1. 加载图像并转换为灰度图
img = cv2.imread('your_image_path.jpg', cv2.IMREAD_GRAYSCALE)if img is None:print("Error: Could not read the image.")
else:# 2. 使用 Otsu's 方法进行自动二值化# 注意:这里阈值参数传入 0,类型与 cv2.THRESH_OTSU 按位或ret, binary_otsu = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 打印 Otsu's 算法自动计算出的阈值print(f"Otsu's threshold value: {ret}")# 3. 显示图像cv2.imshow('Original Grayscale Image', img)cv2.imshow('Otsu\'s Binary Image', binary_otsu)cv2.waitKey(0)cv2.destroyAllWindows()