3、图像基础与OpenCV入门:图像在计算机中的表示
各位同学,欢迎来到第三章。这一章我们聊聊图像在计算机里到底长什么样。说白了,就是搞清楚计算机是怎么“看”到一张图片的。我刚开始学的时候,总觉得图像就是一堆像素点,没什么了不起。但真正做项目时才发现,不理解这些基础,后面调参、debug会非常痛苦。
3.1 图像在计算机中的表示
3.1.1 像素——图像的最小单元
一张数字图像,本质上就是一个二维矩阵。矩阵里的每个元素,就是一个像素。你可以把像素想象成马赛克瓷砖,每个瓷砖都有自己的颜色值。
举个例子,一张 640×480 的图像,就是有 640 列、480 行,总共 307200 个像素点。每个像素点存储一个数值(灰度图)或一组数值(彩色图)。
核心概念:图像 = 矩阵,像素 = 矩阵元素。
3.1.2 通道——颜色的维度
灰度图只有一个通道,每个像素值范围 0~255,0 是纯黑,255 是纯白。彩色图通常有三个通道:红、绿、蓝,也就是我们常说的 RGB。
我在项目中遇到过一个问题:用 OpenCV 读取彩色图像,结果发现颜色不对。后来才意识到,OpenCV 默认用的是 BGR 顺序,而不是我们习惯的 RGB。嗯,这里要注意,很多库的通道顺序不一样。
| 图像类型 | 通道数 | 常见用途 |
|---|---|---|
| 灰度图 | 1 | 边缘检测、二值化 |
| RGB 彩色图 | 3 | 日常显示、目标检测 |
| RGBA 彩色图 | 4 | 带透明通道的图像 |
3.1.3 色彩空间——换个角度看颜色
除了 RGB,还有 HSV、YUV、Lab 等色彩空间。为什么需要这么多?因为不同任务适合不同的色彩空间。
举个例子,你想根据颜色识别一个红色物体。在 RGB 空间里,红色受光照影响很大,很难设定一个固定的阈值。但换成 HSV 空间,你只需要设定色调(Hue)的范围,光照变化基本不影响。我做过一个交通标志识别项目,用 HSV 做颜色分割,效果比 RGB 好太多了。
我的建议:做颜色相关的任务,优先考虑 HSV 空间。做图像压缩或传输,YUV 更常用。
3.2 OpenCV 基本操作
3.2.1 读取图像
OpenCV 读取图像用 cv2.imread()。默认读取为 BGR 彩色图。如果你想读成灰度图,加个参数 cv2.IMREAD_GRAYSCALE。
import cv2
# 读取彩色图(BGR)
img = cv2.imread('cat.jpg')
# 读取灰度图
gray = cv2.imread('cat.jpg', cv2.IMREAD_GRAYSCALE)
# 读取带透明通道的图
img_alpha = cv2.imread('cat.png', cv2.IMREAD_UNCHANGED)
我曾经犯过一个低级错误:路径写错了,OpenCV 不会报错,只会返回 None。然后我后面调用 img.shape 直接崩溃。所以,读取后最好检查一下:
if img is None:
print("图片没找到,检查路径!")
exit()
3.2.2 显示图像
显示图像用 cv2.imshow(),配合 cv2.waitKey() 和 cv2.destroyAllWindows()。
cv2.imshow('窗口标题', img)
cv2.waitKey(0) # 等待按键,0表示无限等待
cv2.destroyAllWindows()
注意:在 Jupyter Notebook 里,cv2.imshow() 可能不好使。我一般用 matplotlib 来显示,但记得把 BGR 转成 RGB:
import matplotlib.pyplot as plt
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.show()
3.2.3 保存图像
保存用 cv2.imwrite()。支持多种格式,比如 jpg、png、bmp。
cv2.imwrite('output.jpg', img)
保存时有个小细节:jpg 是有损压缩,你可以设置质量参数。png 是无损压缩,适合保存边缘清晰的图像。
# 保存高质量 jpg
cv2.imwrite('output.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 95])
# 保存 png
cv2.imwrite('output.png', img)
3.3 图像属性与数据类型
3.3.1 图像的基本属性
一张图像有三个基本属性:高度、宽度、通道数。用 img.shape 获取。
h, w, c = img.shape
print(f"高度: {h}, 宽度: {w}, 通道数: {c}")
如果是灰度图,img.shape 只返回两个值:高度和宽度。所以写代码时要注意,别假设一定有第三个值。
| 属性 | 代码 | 说明 |
|---|---|---|
| 形状 | img.shape | 返回 (h, w, c) 或 (h, w) |
| 像素总数 | img.size | h × w × c |
| 数据类型 | img.dtype | 通常是 uint8 |
3.3.2 数据类型
OpenCV 默认用 uint8 存储像素值,范围 0~255。但有些场景需要更高精度,比如深度图、HDR 图像,会用 uint16 或 float32。
我做过一个项目,需要把两张图像做差值运算。直接用 uint8 相减,结果全是 0 或 255,因为溢出了。后来我把图像转成 float32 再计算,才得到正确结果。
注意:用 uint8 做加减乘除时,结果会自动截断到 0~255。如果需要精确计算,先转成浮点型。
# 转成浮点型
img_float = img.astype(np.float32)
# 计算完后,再转回 uint8 用于显示
img_result = np.clip(img_float, 0, 255).astype(np.uint8)
3.3.3 像素访问与修改
你可以像操作 NumPy 数组一样操作图像。比如获取某个像素的值:
# 获取 (100, 200) 位置的像素值
pixel = img[100, 200]
print(pixel) # 彩色图返回 [B, G, R]
修改像素:
# 把 (100, 200) 位置设为红色(BGR 顺序)
img[100, 200] = [0, 0, 255]
批量操作更常用,比如把整张图变亮:
img_brighter = np.clip(img + 50, 0, 255).astype(np.uint8)
这里用 np.clip 防止溢出,是个好习惯。
3.4 避坑指南
我曾经踩过的坑:
- 用
cv2.imread()读取路径含中文的文件,OpenCV 会失败。解决办法:用cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_COLOR)。 - 显示图像时,窗口一闪而过。记得加
cv2.waitKey(0)。 - 保存图像时,如果路径不存在,OpenCV 不会自动创建目录。需要先
os.makedirs()。
好了,这一章的内容就到这里。图像基础是后续所有视觉算法的基石,别小看这些概念。下一章我们会进入图像预处理,到时候你会感谢今天打下的基础。
公众号:蓝海资料掘金营,微信deep3321