数字图像基础:从像素到色彩空间
图像处理的第一步,就是搞清楚图像到底是什么。说白了,计算机看到的不是「画面」,而是一堆数字。我刚开始接触这个领域时,总觉得图像处理很神秘,后来才发现——嗯,其实就是矩阵运算。
数字图像的表示方式
一张数字图像,本质上就是一个二维数组。每个位置上的数值,代表该点的亮度或颜色信息。
核心概念:图像 = f(x, y),其中 x、y 是空间坐标,f 是该点的像素值。
举个例子。一张 640×480 的图像,就是 640 列、480 行的矩阵。每个格子就是一个像素(pixel)。
| 图像类型 | 矩阵维度 | 每个像素的数值范围 |
|---|---|---|
| 二值图像 | M × N | 0 或 1 |
| 灰度图像 | M × N | 0 ~ 255 |
| 彩色图像 | M × N × 3 | 每个通道 0 ~ 255 |
我在项目中遇到过一个问题:有人把灰度图像当彩色图像处理,结果程序直接崩溃。原因就是维度对不上。灰度图是二维矩阵,彩色图是三维张量,千万别搞混。
像素:图像的最小单元
像素就是图像里最小的「点」。每个像素有自己的坐标和数值。
- 坐标:通常用 (行, 列) 表示,左上角为原点 (0, 0)
- 值:灰度图里是亮度,彩色图里是颜色向量
- 位深:每个像素用多少 bit 表示。8 bit 就是 0~255,16 bit 就是 0~65535
我的习惯:做阈值检测时,我一般先把图像转成 8 bit 灰度图。位深太高,计算量上去了,效果却不一定更好。
色彩空间:RGB、灰度与HSV
色彩空间,说白了就是描述颜色的「坐标系」。同一个颜色,在不同色彩空间里,数值完全不同。
RGB 色彩空间
最常用的色彩空间。红、绿、蓝三个通道叠加,组成所有颜色。
- 每个通道 0~255
- (0,0,0) 是黑色,(255,255,255) 是白色
- 适合显示器、相机等硬件设备
但 RGB 有个问题:它跟人对颜色的感知不太匹配。你调一下亮度,三个通道都得改。这就很麻烦。
灰度图像
灰度图只有亮度信息,没有颜色。每个像素一个值,0 是黑,255 是白。
为什么阈值检测常用灰度图?因为简单、快。你想想看,处理一个通道比处理三个通道,计算量直接降到三分之一。
我曾经踩过的坑:直接对彩色图像做阈值分割,结果发现不同光照条件下,同一个物体的 RGB 值变化很大。后来改成先转灰度再处理,稳定性好多了。
HSV 色彩空间
HSV 把颜色拆成三个分量:色调(Hue)、饱和度(Saturation)、明度(Value)。
| 分量 | 含义 | 范围 |
|---|---|---|
| H | 色调,决定是什么颜色 | 0° ~ 180°(OpenCV 里) |
| S | 饱和度,颜色纯度 | 0 ~ 255 |
| V | 明度,亮度 | 0 ~ 255 |
HSV 的好处是:色调和亮度是分开的。你想检测红色物体,直接看 H 通道就行,光照变化影响小。我个人做颜色相关的阈值检测时,首选 HSV。
色彩空间转换关系
下面这张图,是我自己总结的色彩空间转换逻辑。你看一遍,心里就有数了。
图像读取与显示:OpenCV 实战
理论说完了,咱们直接上手。Python 里最常用的图像库就是 OpenCV。我建议你装好 OpenCV 后,先跑通下面这段代码。
import cv2
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread('example.jpg')
# 查看图像信息
print(f"图像尺寸: {img.shape}")
print(f"数据类型: {img.dtype}")
print(f"像素总数: {img.size}")
# 显示图像
cv2.imshow('Original Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
注意:OpenCV 默认用 BGR 顺序读取彩色图像,不是 RGB。我第一次用的时候,显示出来的图像颜色全不对,查了半天才发现是这个原因。
色彩空间转换代码
# BGR 转灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# BGR 转 HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 显示转换结果
cv2.imshow('Gray', gray)
cv2.imshow('HSV', hsv)
cv2.waitKey(0)
cv2.destroyAllWindows()
你想想看,为什么 OpenCV 用 BGR 而不是 RGB?历史原因。早期相机硬件厂商用 BGR 排列,OpenCV 就沿用了这个习惯。所以显示图像时,记得用 cv2.COLOR_BGR2RGB 转一下。
像素操作:直接访问与修改
图像处理说到底,就是操作像素。下面这段代码,展示了如何直接访问和修改像素值。
# 访问像素 (行, 列)
pixel = img[100, 200]
print(f"BGR 值: {pixel}")
# 修改像素
img[100, 200] = [0, 0, 255] # 变成红色
# 批量操作:将左上角 50x50 区域设为绿色
img[0:50, 0:50] = [0, 255, 0]
我曾经犯过的错:直接在大循环里逐像素操作,一张 1920×1080 的图像,处理一次要好几秒。后来改用 NumPy 的向量化操作,速度提升了上百倍。记住:能用矩阵运算就别用 for 循环。
图像数据类型与范围
| 数据类型 | 范围 | 常见用途 |
|---|---|---|
| uint8 | 0 ~ 255 | 标准图像,最常用 |
| uint16 | 0 ~ 65535 | 医学图像、RAW 格式 |
| float32 | 0.0 ~ 1.0 | 深度学习输入、图像处理中间结果 |
做阈值检测时,我习惯先把图像转成 float32,归一化到 0~1 之间。这样后续的阈值参数更直观,也方便调试。
本章小结
- 图像是矩阵,像素是矩阵里的元素
- RGB 适合显示,HSV 适合颜色检测,灰度适合阈值分割
- OpenCV 用 BGR 顺序,显示时记得转换
- 操作像素优先用 NumPy 向量化,别写 for 循环