2、图像基础操作:图像的读取、显示与保存、像素操作、色彩空间转换

好,咱们正式开始动手了。

这一章,说白了就是 OpenCV 的「基本功」。你想想看,不管多复杂的文档扫描流程,第一步永远是把图像读进来。读不对,后面全白搭。我个人习惯,拿到一张图,先看看它的像素值,再确认一下色彩空间——嗯,心里有底了,才敢往下走。

2.1 图像的读取、显示与保存

OpenCV 读取图像,用的是 cv2.imread()。这个函数看着简单,但坑不少。我刚开始用的时候,就吃过它的亏。

import cv2

# 读取图像
img = cv2.imread('document.jpg')

# 显示图像
cv2.imshow('Document', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 保存图像
cv2.imwrite('output.jpg', img)

这里有个关键点:imread() 默认读取的是 BGR 格式。不是 RGB,是 BGR!

⚠️ 我曾经踩过的坑: 有一次我读了一张图,直接用 matplotlib 显示,结果颜色全偏蓝。折腾了半天才发现,matplotlib 默认是 RGB,而 OpenCV 是 BGR。从那以后,我每次读完图,第一件事就是检查通道顺序。

另外,imread() 的第二个参数可以指定读取方式:

参数 含义
cv2.IMREAD_COLOR 默认,读取彩色图(BGR)
cv2.IMREAD_GRAYSCALE 读取为灰度图
cv2.IMREAD_UNCHANGED 保留 Alpha 通道

显示图像时,waitKey(0) 的意思是「一直等待,直到用户按键」。这个 0 很关键——如果你写成 waitKey(1),窗口会闪一下就消失。嗯,这里要注意。

2.2 像素操作

图像是什么?说白了,就是一个二维数组。每个元素就是一个像素。彩色图的每个像素,又包含 B、G、R 三个通道的值。

我习惯把像素操作分成两类:读像素写像素

2.2.1 读取像素值

import cv2
import numpy as np

img = cv2.imread('document.jpg')

# 读取 (100, 200) 位置的像素
px = img[100, 200]
print(px)  # 输出 [B, G, R] 值

# 只读取蓝色通道
blue = img[100, 200, 0]
print(blue)

你想想看,为什么是 [100, 200] 而不是 [200, 100]?因为 OpenCV 的坐标是 (行, 列),也就是 (y, x)。这一点和很多图像处理库不一样。我刚开始也经常搞混,后来干脆在代码里加了个注释:# 注意:先y后x

2.2.2 修改像素值

# 将 (100, 200) 位置的像素设为白色
img[100, 200] = [255, 255, 255]

# 批量修改:将左上角 50x50 区域设为红色
img[0:50, 0:50] = [0, 0, 255]
💡 我的经验: 批量操作时,用 NumPy 的切片语法比用 for 循环快得多。比如你要把整张图的蓝色通道清零,直接写 img[:, :, 0] = 0 就行。我在处理高分辨率文档扫描图时,这个技巧能省下好几秒。

2.3 色彩空间转换

色彩空间转换,是文档扫描中绕不开的一步。为什么?因为很多 OCR 算法对灰度图或者二值图效果更好。你想想看,彩色图里那些花里胡哨的背景,对 OCR 来说全是噪音。

2.3.1 BGR 转灰度

这是最常用的转换。说白了,就是把彩色图变成黑白图。

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

灰度图只有一个通道,取值范围 0(黑)到 255(白)。

2.3.2 BGR 转 RGB

这个转换,我几乎每次做项目都会用到。因为 OpenCV 是 BGR,而很多其他库(比如 matplotlib、PIL)用的是 RGB。

rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
🔑 记住这个口诀: 「OpenCV 读图是 BGR,显示保存要小心。转成灰度用 GRAY,转成 RGB 给别家。」

2.3.3 其他常用转换

转换类型 代码 用途
BGR → HSV cv2.COLOR_BGR2HSV 颜色分割、物体检测
BGR → LAB cv2.COLOR_BGR2LAB 颜色校正、图像增强
BGR → YUV cv2.COLOR_BGR2YUV 视频处理、压缩

我个人在文档扫描中,最常用的是 BGR→灰度。偶尔遇到光照不均匀的文档,我会先转成 LAB,然后对 L 通道做直方图均衡化——效果比直接处理 BGR 好很多。

2.4 实战小技巧

好,理论说完了,咱们来点实际的。我总结了几条写代码时的习惯,分享给你:

  1. 读完图先检查if img is None: print('图片没读进来')。路径写错了、文件损坏了,OpenCV 不会报错,只会返回 None。我吃过这个亏,debug 了半小时才发现是路径少了个斜杠。
  2. 显示图像时加个标题cv2.imshow('Original', img)。窗口多了容易搞混,标题能救命。
  3. 保存时注意格式cv2.imwrite('output.png', img) 会保存为 PNG,无损压缩。如果是 JPEG,可以指定质量:cv2.imwrite('output.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 90])
  4. 批量处理时用循环:比如你要把一整个文件夹的图片都转成灰度,写个 for 循环,配合 os.listdir(),几分钟搞定。
💡 一个小技巧: 如果你不确定当前图像是什么色彩空间,可以打印它的 shape 属性。灰度图是 (h, w),彩色图是 (h, w, 3),带 Alpha 通道的是 (h, w, 4)。这个习惯帮我避免了很多低级错误。

嗯,这一章的内容就到这里。图像基础操作看着简单,但它是后面所有高级功能的地基。你想想看,如果连读图都读不对,后面的文档矫正、OCR 识别还怎么玩?

下一章,咱们会聊图像的几何变换——说白了,就是怎么把歪歪扭扭的文档照片给「掰正」了。到时候见。