在计算机视觉和图像处理领域,理解计算机如何"看"图像是基础中的基础。本文将详细介绍图像处理中的两个核心概念:灰度化和二值化,并通过实验方法展示不同算法的效果差异。
一、计算机眼中的图像
1. 像素
像素(Pixel)是图像的基本单元,每个像素包含颜色信息。在彩色图像中,通常使用RGB(红绿蓝)三通道表示,每个通道取值范围为0-255。(注意:这里的b是blue,而不是black)
import cv2
import numpy as np# 创建一个3x3的红色像素块
red_pixel = np.zeros((3, 3, 3), dtype=np.uint8)
red_pixel[:, :, 2] = 255 # OpenCV使用BGR格式,所以索引2是红色通道,[所有行、列的第二个元素值为255]
cv2.imshow('Red Pixel Block', red_pixel)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. 图像
计算机采用0/1编码的系统,数字图像也是利用0/1来记录信息,我们平常接触的图像都是8位数图像(“8位数图像”通常指的是**颜色深度为8位**的图像,即每个颜色通道(如红、绿、蓝)用8位二进制数(1字节)表示),包含0~255灰度(2⁸=256种可能),其中0,代表最黑,1,表示最白。
2.1.二值图像(黑白两个颜色:水墨画)
一幅二值图像的二维矩阵仅由0、1两个值构成,“0”代表黑色,“1”代白色。由于每一像素(矩阵中每一元素)取值仅有0、1两种可能,所以计算机中二值图像的数据类型通常为1个二进制位。二值图像通常用于文字、线条图的扫描识别(OCR)和掩膜图像的存储。
2.2.灰度图(黑色->白色之间:类似于以前的黑白电视机)
每个像素只有一个采样颜色的图像,这类图像通常显示为从最暗黑色到最亮的白色的灰度,尽管理论上这个采样可以任何颜色的不同深浅,甚至可以是不同亮度上的不同颜色。灰度图像与黑白图像不同,在计算机图像领域中黑白图像只有黑色与白色两种颜色;但是,灰度图像在黑色与白色之间还有许多级的颜色深度。灰度图像经常是在单个电磁波频谱如可见光内测量每个像素的亮度得到的,用于显示的灰度图像通常用每个采样像素8位的非线性尺度来保存,这样可以有256级灰度(如果用16位,则有65536级)。
3.彩色图(现实中看到的图片)
每个像素通常是由红(R)、绿(G)、蓝(B)三个分量来表示的,分量介于(0,255)。RGB图像与索引图像一样都可以用来表示彩色图像。与索引图像一样,它分别用红(R)、绿(G)、蓝(B)三原色的组合来表示每个像素的颜色。但与索引图像不同的是,RGB图像每一个像素的颜色值(由RGB三原色表示)直接存放在图像矩阵中,由于每一像素的颜色需由R、G、B三个分量来表示,M、N分别表示图像的行列数,三个M x N的二维矩阵分别表示各个像素的R、G、B三个颜色分量。RGB图像的数据类型一般为8位无符号整形,通常用于表示和存放真彩色图像。
图像本质上是一个三维数组(高度×宽度×通道数)。我们可以直接访问和修改像素值:
# 读取图像
img = cv2.imread('example.jpg')
if img is None:# 创建示例图像img = np.zeros((300, 400, 3), dtype=np.uint8)# cv.rectangle(img,leftupperL:右上角坐标,rightdown:左下角坐标,color:颜色矩阵,thickness:线条厚度,默认为1,-1:当thickness取值为-1时,这意味着矩形将被填充,而不是绘制边框。也就是说,整个矩形区域会被指定的颜色填满。)cv2.rectangle(img, (50, 50), (200, 200), (0, 0, 255), -1) # 红色矩形cv2.circle(img, (300, 150), 80, (0, 255, 0), -1) # 绿色圆形cv2.putText(img, "Example", (150, 250), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2) # 蓝色文字# 访问像素值
print("左上角像素值(BGR):", img[0, 0])
二、灰度化实验
灰度化是将彩色图像转换为灰度图像的过程,常用方法有:
1. 最大值法
取RGB三个通道中的最大值作为灰度值:
Gray = max(R, G, B)
def max_grayscale(img):return np.max(img, axis=2)gray_max = max_grayscale(img)
cv2.imshow('Max Grayscale', gray_max)
cv2.waitKey(0)
2. 平均值法
取RGB三个通道的平均值作为灰度值:
Gray = (R + G + B) / 3
def mean_grayscale(img):return np.mean(img, axis=2).astype(np.uint8)gray_mean = mean_grayscale(img)
cv2.imshow('Mean Grayscale', gray_mean)
cv2.waitKey(0)
3. 加权均值法
考虑人眼对不同颜色的敏感度,使用加权平均:
Gray = 0.299*R + 0.587*G + 0.114*B
def weighted_grayscale(img):# 使用OpenCV内置函数return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)gray_weighted = weighted_grayscale(img)
cv2.imshow('Weighted Grayscale', gray_weighted)
cv2.waitKey(0)
4. 两个极端的灰度值
可以尝试只保留最亮或最暗的像素:
-
仅保留最大值:所有像素设为255
-
仅保留最小值:所有像素设为0
gray_white = np.full_like(gray_weighted, 255)
gray_black = np.zeros_like(gray_weighted)cv2.imshow('Extreme White', gray_white)
cv2.imshow('Extreme Black', gray_black)
cv2.waitKey(0)
cv2.destroyAllWindows()
三、二值化实验
二值化是将灰度图像转换为只有黑白两色的图像,以下是六种常用方法:
1.普通阈值法:设定一个阈值,低于等于阈值就变成0,超过阈值就设为给定的最大值
2.反阈值法:故名思意,和普通阈值法相反,超过阈值的变成0,低于等于的设为给定的最大值
3.截断阈值法:设定一个阈值,低于等于阈值的不改变,超过阈值的设为阈值
4.低阈值零处理:故名思意,低于等于阈值的设为0,超过阈值的不变
5.超阈值零处理:故名思意,超过阈值的设为0,低于等于阈值的不变
6.OTSU 阈值法:自动确定图像二值化最优阈值的算法,它基于图像灰度直方图,通过最大化类间方差来找到一个阈值,使前景和背景的分离效果达到最佳
# 首先转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 1. 普通阈值法
_, thresh_binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)# 2. 反阈值法
_, thresh_binary_inv = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)# 3. 截断阈值法
_, thresh_trunc = cv2.threshold(gray, 127, 255, cv2.THRESH_TRUNC)# 4. 低阈值零处理
_, thresh_tozero = cv2.threshold(gray, 127, 255, cv2.THRESH_TOZERO)# 5. 超阈值零处理
_, thresh_tozero_inv = cv2.threshold(gray, 127, 255, cv2.THRESH_TOZERO_INV)# 6. OTSU自动阈值法
_, thresh_otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 显示所有结果
results = [gray, thresh_binary, thresh_binary_inv, thresh_trunc, thresh_tozero, thresh_tozero_inv, thresh_otsu]
titles = ['Gray', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV', 'OTSU']for i in range(7):cv2.imshow(titles[i], results[i])cv2.waitKey(500) # 每个显示0.5秒cv2.destroyAllWindows()
四、自适应二值化
自适应二值化能处理光照不均的图像,以下是两种实现方式:
1. 取均值法
使用邻域均值作为阈值,块大小必须是奇数
adaptive_mean = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY, 11, 2) # 11是块大小,2是从均值中减去的常数cv2.imshow('Adaptive Mean', adaptive_mean)
cv2.waitKey(0)
2. 高斯加权法
使用高斯加权和作为阈值,更注重中心像素
adaptive_gauss = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY, 11, 2)cv2.imshow('Adaptive Gaussian', adaptive_gauss)
cv2.waitKey(0)
cv2.destroyAllWindows()
五、完整可运行代码
import cv2
import numpy as npdef main():# 读取或创建图像img = cv2.imread('example.jpg')if img is None:img = np.zeros((300, 400, 3), dtype=np.uint8)cv2.rectangle(img, (50, 50), (200, 200), (0, 0, 255), -1)cv2.circle(img, (300, 150), 80, (0, 255, 0), -1)cv2.putText(img, "Example", (150, 250), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)# 灰度化实验gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)gray_max = np.max(img, axis=2)gray_mean = np.mean(img, axis=2).astype(np.uint8)cv2.imshow('Original', img)cv2.imshow('OpenCV Gray', gray)cv2.imshow('Max Gray', gray_max)cv2.imshow('Mean Gray', gray_mean)cv2.waitKey(0)# 二值化实验_, thresh_binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)_, thresh_otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)cv2.imshow('Binary', thresh_binary)cv2.imshow('OTSU', thresh_otsu)cv2.waitKey(0)# 自适应二值化adaptive = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY, 11, 2)cv2.imshow('Adaptive', adaptive)cv2.waitKey(0)cv2.destroyAllWindows()if __name__ == "__main__":main()
六、应用场景与总结
-
灰度化应用:
-
人脸检测前的预处理
-
降低计算复杂度
-
某些算法需要单通道输入
-
-
二值化应用:
-
文档扫描和OCR
-
图像分割
-
边缘检测预处理
-
-
方法选择建议:
-
光照均匀:普通阈值法或OTSU法
-
光照不均:自适应二值化
-
需要保留部分灰度信息:截断阈值法
-
本文介绍的方法都是图像处理的基础操作,理解这些原理对后续学习更高级的图像处理技术至关重要。建议读者动手实践,调整参数观察不同效果。