第 2 章

认识图像表示方法

本章共 6 个小节 · 图像数据的底层表达
在进入 OpenCV 的函数操作之前,先理解计算机内部是怎么"存"一张图像的。本章从最简单的位图像(每个像素只有 0/1)开始,逐步认识灰度、RGB、BGR 色彩空间,最后学会用 shape/size/dtype 查看图像属性,以及读写单个像素的 BGR 值。
2-1

位图像表示法

想象一张 12×12 的位图,代表英文字母 H。每一个格子称为一个像素,像素的值只能是 01

于是整张图就可以用一个 12×12 的 0/1 矩阵来表示。因为每个像素只由 1 个 bit 组成,这种表示法就叫做位图像表示法

位图像与 0/1 矩阵
字母 H 的位图像与 0/1 矩阵
字母 H 的 12x12 位图像与对应矩阵,参考原书第 2-2 页。
2-2

GRAY 色彩空间

单纯用 0/1 表示像素,精度太低。把表达范围扩展到 0 ~ 255(256 级),就能得到更细腻的灰度图像,这就是 GRAY 色彩空间

256 级刚好用 8 个 bit(1 个 Byte) 就能表示。下表是几个代表性灰度值的视觉对照:

10 进制值灰度色彩
0 
32 
64 
96 
128 
160 
192 
224 
255 
GRAY 色彩空间示意
灰度图像与 0 到 255 灰阶对照
灰度图像与 0 到 255 灰阶对照,参考原书第 2-3 页。

灰度图像在程序里可以用一个二维数组来表达:行数 × 列数,每个元素是 0~255 的整数。

2-3

RGB 色彩空间

彩色图像最常见的表达方式是 RGB 色彩空间:任何颜色都由 R(Red 红)G(Green 绿)B(Blue 蓝) 三原色按不同比例组成,每个分量称为一个通道(channel),取值范围都是 0 ~ 255

RGB 三原色
RGB 三原色与 materialui 色彩网站示意
RGB 三原色与配色网站查看 RGB 值的示意,参考原书第 2-4 页。
原书将 Channels 翻译为「通道」,也有文献翻译为「色版」。

2-3-1 由色彩得知 RGB 通道值

可以用配色网站 materialui.co/colors 查看颜色对应的 R / G / B 值:点任意色块,右上角会显示 rgb(R, G, B)

2-3-2 使用 RGB 通道值获得色彩区块

Excel 的「设置单元格格式 → 填充 → 其他颜色 → 自定义」里可以直接输入 R / G / B 数值,立刻看到对应的颜色区块。例如 (255, 255, 0) 就是黄色。

输入 RGB 通道值取得色彩区块
Office 自定义颜色对话框输入 RGB 值
在自定义颜色对话框中输入 RGB 通道值取得颜色区块,参考原书第 2-5 页。

RGB 每个通道 256 个值,三个通道组合起来共可表示:

256 * 256 * 256 = 16,777,216 种颜色

2-3-3 RGB 彩色像素的表示法

一张 RGB 彩色图像可以用一个三维数组来表达,形状是 (高, 宽, 3),也就是三个二维数组(B 层、G 层、R 层)叠在一起。

RGB 彩色像素表示法
RGB 彩色像素的三维数组表示法
RGB 彩色像素以三维数组表示,参考原书第 2-6 页。
2-4

BGR 色彩空间

传统 RGB 顺序是 R → G → B,但 OpenCV 使用的是 BGR 顺序,即:

这是 OpenCV 的默认色彩空间,后续所有像素读写、通道拆分都要记住这个顺序。

2-5

获得图像的属性

imread() 读进来的图像本质上是一个 Numpy 数组,有三个常用属性:

程序示例 ch2_1.py:打印灰度图像的属性值

1# ch2_1.py 2import cv2 3 4img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) # 灰度读取 5print("打印灰度图像的属性") 6print(f"shape = {img.shape}") 7print(f"size = {img.size}") 8print(f"dtype = {img.dtype}")
执行结果
打印灰度图像的属性
shape = (345, 342)
size = 117990
dtype = uint8
图示
Windows 画图软件显示 jk.jpg 尺寸与坐标
Windows 画图软件打开 jk.jpg,状态栏显示 342 × 345 像素(x × y 顺序)。
重点 画图软件显示 342 × 345(x × y),但 OpenCV 的 shape(345, 342),也就是 (y, x)(行数, 列数)。坐标轴习惯和 Numpy 索引顺序相反,务必注意。

size 返回 117990 = 345 × 342dtype 返回 uint8,表示 8 位无符号整数。

程序示例 ch2_2.py:打印彩色图像的属性值

1# ch2_2.py 2import cv2 3 4img = cv2.imread("jk.jpg") # 彩色读取 5print("打印彩色图像的属性") 6print(f"shape = {img.shape}") 7print(f"size = {img.size}") 8print(f"dtype = {img.dtype}")
执行结果
打印彩色图像的属性
shape = (345, 342, 3)
size = 353970
dtype = uint8

size353970 = 345 × 342 × 3,多了一个通道维度。

2-6

像素的 BGR 值

jk.jpg 大小是 342 × 345,OpenCV 的坐标约定:

图示
鼠标指向像素坐标 (118, 169)
鼠标移到脸颊附近,画图软件左下角显示坐标 118, 169(x, y)。下面的示例将读取这一点的 BGR 值。

2-6-1 读取特定灰度图像像素坐标的 BGR 值

访问像素的语法是 img[y, x](先 y 后 x)。对灰度图,返回一个 uint8 标量:

img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) px = img[169, 118] # 获取坐标 (x=118, y=169) 的灰度值

程序示例 ch2_3.py:读取灰度图像 (169, 118) 的值

1# ch2_3.py 2import cv2 3 4pt_y = 169 5pt_x = 118 6img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) # 灰度读取 7px = img[pt_y, pt_x] # 读 px 点 8print(type(px)) 9print(f"BGR = {px}")
执行结果
<class 'numpy.uint8'>
BGR = 128

2-6-2 读取特定彩色图像像素坐标的 BGR 值

对彩色图,img[y, x] 返回一个长度为 3 的 ndarray,依序是 [B, G, R]

程序示例 ch2_4.py:读取彩色图像 (169, 118) 的 BGR 值

1# ch2_4.py 2import cv2 3 4pt_y = 169 5pt_x = 118 6img = cv2.imread("jk.jpg") # 彩色读取 7px = img[pt_y, pt_x] # 读 px 点 8print(type(px)) 9print(f"BGR = {px}")
执行结果
<class 'numpy.ndarray'>
BGR = [ 45 112 191]

也可以一次只取一个通道,加第三个索引即可:

blue = img[pt_y, pt_x, 0] # B 通道值 green = img[pt_y, pt_x, 1] # G 通道值 red = img[pt_y, pt_x, 2] # R 通道值

程序示例 ch2_5.py:分别列出三个通道的值

1# ch2_5.py 2import cv2 3 4pt_y = 169 5pt_x = 118 6img = cv2.imread("jk.jpg") # 彩色读取 7blue = img[pt_y, pt_x, 0] # 读 B 通道值 8green = img[pt_y, pt_x, 1] # 读 G 通道值 9red = img[pt_y, pt_x, 2] # 读 R 通道值 10print(f"BGR = {blue}, {green}, {red}")
执行结果
BGR = 45, 112, 191

2-6-3 修改特定图像像素坐标的 BGR 值

改一个像素的值,直接赋一个长度为 3 的列表就行:

px = [blue, green, red] # 注意顺序是 B, G, R

程序示例 ch2_6.py:把 (169, 118) 改成白色 [255, 255, 255]

1# ch2_6.py 2import cv2 3 4pt_y = 169 5pt_x = 118 6img = cv2.imread("jk.jpg") # 彩色读取 7px = img[pt_y, pt_x] # 读 px 点 8print(f"更改前 BGR = {px}") 9px = [255, 255, 255] # 修改 px 点 10print(f"更改后 BGR = {px}")
执行结果
更改前 BGR = [ 45 112 191]
更改后 BGR = [255, 255, 255]

改单个像素肉眼几乎看不出来,下面改一整块区域就明显了 —— 把图像右下角 50×50 的区域全涂成白色:

程序示例 ch2_7.py:右下角 50×50 像素改为白色

1# ch2_7.py 2import cv2 3 4img = cv2.imread("jk.jpg") # 彩色读取 5cv2.imshow("Before the change", img) 6for y in range(img.shape[0]-50, img.shape[0]): 7 for x in range(img.shape[1]-50, img.shape[1]): 8 img[y, x] = [255, 255, 255] 9cv2.imshow("After the change", img) 10cv2.waitKey(0) 11cv2.destroyAllWindows()
执行结果
ch2_7.py 执行结果 Before/After 对比
左侧 Before the change 是原图;右侧 After the change 图像右下角出现一个白色方块(50×50 像素区域被改成白色)。
习题

1:调整 ch2_7.py,改为在图像下方显示一条黄色横条

提示:横条跨越整个宽度,高度可选 50 像素;黄色 BGR = [0, 255, 255]

习题参考效果
参考效果:右侧 After 图底部多了一条黄色横条。