第 6 章

图像处理的基础知识

本章共 5 个小节 · 灰度像素、彩色像素、alpha、item/itemset、ROI
本章把前面学过的 NumPy 数组知识应用到具体图像处理:读取与修改灰度像素、编辑 BGR 彩色像素、处理 alpha 透明通道、 使用 item()itemset() 读写像素,以及用切片取得 ROI 感兴趣区域。
6-1

灰度图像的编辑

灰度图像是二维数组。读取单个像素时使用 image[y, x],其中 y 是行索引,x 是列索引。 修改像素时直接给这个位置赋值即可。

程序实例 ch6_1.py:自创 5×12 灰度图像数组并修改像素

# ch6_1.py import cv2 import numpy as np image = np.zeros((5, 12), np.uint8) print(f"修改前 image =\n{image}") print(f"image[1, 4] = {image[1, 4]}") image[1, 4] = 255 print(f"修改后 image =\n{image}") print(f"image[1, 4] = {image[1, 4]}")
执行结果
ch6_1.py 灰度数组修改结果
单一灰度像素读取与修改,参考原书第 6-2 页。

真实灰度图像也可以用同样方式编辑。下面例子读取 jk.jpg 为灰阶图,然后把眼睛区域改为白色长条。

程序实例 ch6_2.py:读取灰度图像并遮住眼睛区域

# ch6_2.py import cv2 img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) cv2.imshow("Before modify", img) for y in range(120, 140): for x in range(110, 210): img[y, x] = 255 cv2.imshow("After modify", img) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch6_2.py 灰度图像遮罩结果
逐像素修改灰度图像局部区域,参考原书第 6-3 页。
6-2

彩色图像的编辑

彩色图像是三维数组,形状通常是 (height, width, 3)。第三维的索引 012 分别代表 B、G、R 通道。单个彩色像素可以看作 [B, G, R] 三元素数组。

BGR 数组结构
BGR 彩色数组结构示意
彩色图像数组的列、行与通道关系,参考原书第 6-4 页。

程序实例 ch6_3.py:建立 2×3×3 的蓝、绿、红数组并列印内容

# ch6_3.py import cv2 import numpy as np blue_img = np.zeros((2, 3, 3), np.uint8) blue_img[:, :, 0] = 255 print(f"blue image =\n{blue_img}") green_img = np.zeros((2, 3, 3), np.uint8) green_img[:, :, 1] = 255 print(f"green image =\n{green_img}") red_img = np.zeros((2, 3, 3), np.uint8) red_img[:, :, 2] = 255 print(f"red image =\n{red_img}")

程序实例 ch6_4.py:建立蓝、绿、红色视窗

# ch6_4.py import cv2 import numpy as np blue_img = np.zeros((100, 150, 3), np.uint8) blue_img[:, :, 0] = 255 print(f"blue image =\n{blue_img}") cv2.imshow("Blue Image", blue_img) green_img = np.zeros((100, 150, 3), np.uint8) green_img[:, :, 1] = 255 print(f"green image =\n{green_img}") cv2.imshow("Green Image", green_img) red_img = np.zeros((100, 150, 3), np.uint8) red_img[:, :, 2] = 255 print(f"red image =\n{red_img}") cv2.imshow("Red Image", red_img) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch6_4.py BGR 三色窗口
蓝、绿、红三个彩色窗口,参考原书第 6-5 页。

程序实例 ch6_5.py:一次修改一个彩色像素的 BGR 值

# ch6_5.py import cv2 import numpy as np blue = np.zeros((2, 3, 3), np.uint8) blue[:, :, 0] = 255 print(f"blue =\n{blue}") print(f"blue[0, 1] = {blue[0, 1]}") blue[0, 1] = [50, 100, 150] print("修改后") print(f"blue =\n{blue}") print(f"blue[0, 1] = {blue[0, 1]}")

程序实例 ch6_6.py:只修改一个像素的单一通道

# ch6_6.py import cv2 import numpy as np blue = np.zeros((2, 3, 3), np.uint8) blue[:, :, 0] = 255 print(f"blue =\n{blue}") print(f"blue[0, 1, 2] = {blue[0, 1, 2]}") blue[0, 1, 2] = 50 print("修改后") print(f"blue =\n{blue}") print(f"blue[0, 1, 2] = {blue[0, 1, 2]}")

对实际彩色图像,可以一次修改某个像素的 BGR 三个通道,也可以逐通道修改。原书先用循环示范,再给出切片简化写法。

程序实例 ch6_7.py:读取彩色图像并编辑局部色条

# ch6_7.py import cv2 img = cv2.imread("jk.jpg") cv2.imshow("Before modify", img) print(f"修改前 img[115,110] = {img[115, 110]}") print(f"修改前 img[125,110] = {img[125, 110]}") print(f"修改前 img[135,110] = {img[135, 110]}") for y in range(115, 125): for x in range(110, 210): img[y, x] = [255, 0, 255] for z in range(125, 135): for y in range(110, 210): for x in range(0, 3): img[z, y, x] = 255 for y in range(135, 145): for x in range(110, 210): img[y, x] = [0, 255, 255] cv2.imshow("After modify", img) print(f"修改后 img[115,110] = {img[115, 110]}") print(f"修改后 img[125,110] = {img[125, 110]}") print(f"修改后 img[135,110] = {img[135, 110]}") cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch6_7.py 彩色局部修改
用 BGR 数组值修改彩色图像局部区域,参考原书第 6-7 页。

程序实例 ch6_7_1.py:用切片取代 ch6_7.py 的部分循环

# ch6_7_1.py import cv2 img = cv2.imread("jk.jpg") cv2.imshow("Before modify", img) print(f"修改前 img[115,110] = {img[115, 110]}") img[115:125, 110:210] = [255, 0, 255] img[125:135, 110:210] = [255, 255, 255] img[135:145, 110:210] = [0, 255, 255] cv2.imshow("After modify", img) print(f"修改后 img[115,110] = {img[115, 110]}") cv2.waitKey(0) cv2.destroyAllWindows()
效率 逐像素循环直观但慢;区域颜色替换优先使用 NumPy 切片,例如 img[115:125, 110:210] = [255, 0, 255]
6-3

编辑含 alpha 通道的彩色图像

含 alpha 的彩色图像是四通道数组,顺序为 B、G、R、A。A 表示透明度,0 是完全透明, 255 是完全不透明。读取 PNG 这类带透明度的文件时,要使用 cv2.IMREAD_UNCHANGED 保留 alpha 通道。

image = cv2.imread("street.png", cv2.IMREAD_UNCHANGED)

程序实例 ch6_8.py:修改 PNG 图像局部 alpha 值

# ch6_8.py import cv2 img = cv2.imread("street.png", cv2.IMREAD_UNCHANGED) cv2.imshow("Before modify", img) print(f"修改前 img[10,50] = {img[10, 50]}") print(f"修改前 img[50,99] = {img[50, 99]}") for z in range(0, 200): for y in range(0, 200): img[z, y, 3] = 128 print(f"修改后 img[10,50] = {img[10, 50]}") print(f"修改后 img[50,99] = {img[50, 99]}") cv2.imwrite("street128.png", img) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch6_8.py alpha PNG 保存结果
修改 alpha 后保存 PNG 的结果,参考原书第 6-9 页。
6-4

Numpy 高效率读取与设置像素的方法

原书在这一节对比了传统循环和 NumPy 写法。区域修改优先用切片;单点读取和设置也可以用 item()itemset()

程序实例 ch6_8_1.py:用一行切片修改 alpha 通道

# ch6_8_1.py import cv2 img = cv2.imread("street.png", cv2.IMREAD_UNCHANGED) cv2.imshow("Before modify", img) print(f"修改前 img[10,50] = {img[10, 50]}") print(f"修改前 img[50,99] = {img[50, 99]}") print("-" * 70) img[0:200, 0:200, 3] = 128 print(f"修改后 img[10,50] = {img[10, 50]}") print(f"修改后 img[50,99] = {img[50, 99]}") cv2.imwrite("street128_1.png", img) cv2.waitKey(0) cv2.destroyAllWindows()
图像类型读取设置
灰度图image.item(y, x)image.itemset((y, x), value)
彩色图image.item(y, x, channel)image.itemset((y, x, channel), value)

程序实例 ch6_9.py:灰度数组的 item() 与 itemset()

# ch6_9.py import numpy as np image = np.random.randint(0, 200, size=(3, 5), dtype=np.uint8) print(f"image =\n{image}") print(f"修改前 image.item(1, 3) = {image.item(1, 3)}") image.itemset((1, 3), 255) print("-" * 70) print(f"修改后 image =\n{image}") print(f"修改后 image.item(1, 3) = {image.item(1, 3)}")
执行结果
ch6_9.py itemset 灰度修改
使用 itemset() 修改灰度像素,参考原书第 6-10 页。

程序实例 ch6_10.py:用 itemset() 重写 ch6_2.py

# ch6_10.py import cv2 img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) cv2.imshow("Before modify", img) for y in range(120, 140): for x in range(110, 210): img.itemset((y, x), 255) cv2.imshow("After modify", img) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch6_11.py:彩色数组的 item() 与 itemset()

# ch6_11.py import cv2 import numpy as np blue = np.zeros((2, 3, 3), np.uint8) blue[:, :, 0] = 255 print(f"blue =\n{blue}") print(f"blue[0, 1, 2] = {blue.item(0, 1, 2)}") blue.itemset((0, 1, 2), 50) print("修改后") print(f"blue =\n{blue}") print(f"blue[0, 1, 2] = {blue.item(0, 1, 2)}")

程序实例 ch6_12.py:用 itemset() 修改彩色图像局部区域

# ch6_12.py import cv2 img = cv2.imread("jk.jpg") cv2.imshow("Before modify", img) print(f"修改前 img[115,110,1] = {img.item(115, 110, 1)}") print(f"修改前 img[125,110,1] = {img.item(125, 110, 1)}") print(f"修改前 img[135,110,1] = {img.item(135, 110, 1)}") for z in range(115, 145): for y in range(110, 210): for x in range(0, 3): img.itemset((z, y, x), 255) cv2.imshow("After modify", img) print(f"修改后 img[115,110,1] = {img.item(115, 110, 1)}") print(f"修改后 img[125,110,1] = {img.item(125, 110, 1)}") print(f"修改后 img[135,110,1] = {img.item(135, 110, 1)}") cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch6_12.py itemset 彩色修改
使用 itemset() 修改彩色图像局部像素值,参考原书第 6-12 页。
6-5

图像感兴趣区域的编辑

ROI 是 Region of Interest,也就是感兴趣区域。用切片取得一块区域后,可以显示、保存、覆盖、生成马赛克,或贴到另一张图上。

roi = img[y1:y2, x1:x2]

程序实例 ch6_13.py:截取人脸 ROI

# ch6_13.py import cv2 img = cv2.imread("jk.jpg") cv2.imshow("Huang Image", img) face = img[30:220, 80:250] cv2.imshow("Face", face) cv2.waitKey(0) cv2.destroyAllWindows()
ROI 复制
ch6_13.py ROI 人脸复制
截取脸部区域并显示,参考原书第 6-13 页。

程序实例 ch6_14.py:为 ROI 设置马赛克效果

# ch6_14.py import cv2 import numpy as np img = cv2.imread("jk.jpg") cv2.imshow("Huang Image", img) face = np.random.randint(0, 256, size=(190, 170, 3), dtype=np.uint8) img[30:220, 80:250] = face cv2.imshow("Face", img) cv2.waitKey(0) cv2.destroyAllWindows()
马赛克效果
ch6_14.py ROI 马赛克
对 ROI 区域做随机彩色马赛克,参考原书第 6-14 页。

程序实例 ch6_15.py:把 ROI 贴到另一张图像上

# ch6_15.py import cv2 import numpy as np img = cv2.imread("jk.jpg") cv2.imshow("Huang Image", img) usa = cv2.imread("money.jpg") cv2.imshow("Money Image", usa) face = img[30:220, 80:250] usa[30:220, 120:290] = face cv2.imshow("Image", usa) cv2.waitKey(0) cv2.destroyAllWindows()
ROI 贴图
ch6_15.py ROI 图像转贴
把一张图像的 ROI 贴到另一张图像上,参考原书第 6-15 页。
习题

1:参考 ch6_7_1.py 的观念,重新设计整个 ch6_7.py,让执行结果与原程序相同。

2:重新设计 ch6_12.py,用一列指令取代第 10 到第 13 列的逐通道循环。

img[115:145, 110:210] = [255, 255, 255]

3:参考 ch6_15.py,将感兴趣区域移植到相同大小的空白画布。