2. 图像基础操作:图像的读取、显示、保存、图像属性与像素操作
各位同学,欢迎来到第二章。上一章我们聊了OpenCV的环境搭建,今天咱们直接上手,玩点实在的——图像的基本操作。
说白了,图像在计算机眼里就是个数字矩阵。你看到的猫猫狗狗、风景照片,对OpenCV来说就是一堆0到255之间的数字。我刚开始学的时候,总觉得这很抽象,但当你亲手把一张图片的像素值打印出来,看着满屏的数字,那种感觉——嗯,一下子就通了。
2.1 图像的读取:把图片请进内存
在OpenCV里,读取图像用的是 cv2.imread() 函数。这就像你去图书馆借书,得告诉管理员你要哪本书。
import cv2
# 读取一张图片
img = cv2.imread('lena.jpg')
这里有个坑,我必须要说。OpenCV默认读取的通道顺序是 BGR,不是我们熟悉的RGB。为什么?因为早期相机硬件厂商的习惯。我在项目中第一次用OpenCV显示彩色图像时,发现人脸是蓝色的,当时还以为是摄像头坏了……后来才反应过来,是通道顺序搞反了。
cv2.imread() 还有第二个参数,控制读取方式:
| 参数值 | 含义 | 实际场景 |
|---|---|---|
cv2.IMREAD_COLOR (默认值1) |
加载彩色图像,忽略透明度 | 日常处理彩色照片 |
cv2.IMREAD_GRAYSCALE (值0) |
以灰度模式加载 | 人脸识别、边缘检测前常用 |
cv2.IMREAD_UNCHANGED (值-1) |
加载原图,包括Alpha通道 | 处理PNG透明图时必用 |
cv2.imread('path', 0) 直接读灰度图,省去后面转换的步骤。但如果你要显示彩色图,记得用默认值。
2.2 图像的显示:看看你读对了没
读进来之后,总得看一眼吧?cv2.imshow() 就是干这个的。
cv2.imshow('窗口标题', img)
cv2.waitKey(0) # 等待按键,0表示无限等待
cv2.destroyAllWindows()
你想想看,如果不加 waitKey(),窗口会一闪而过,你根本来不及看。我曾经在给客户演示时犯过这个错,窗口闪了一下就没了,场面一度很尴尬。
waitKey() 的参数是毫秒数。比如 cv2.waitKey(1000) 表示等待1秒后自动关闭。返回值是按键的ASCII码,这个后面做交互时会用到。
imshow() 可能无法正常显示。这时候可以用 matplotlib 来显示,但记得把BGR转成RGB:
import matplotlib.pyplot as plt
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.show()
2.3 图像的保存:把成果写进硬盘
处理完图像,总得保存下来吧?cv2.imwrite() 搞定。
cv2.imwrite('output.jpg', img)
就这么简单。但这里有个细节——保存质量。对于JPEG格式,你可以指定压缩质量:
cv2.imwrite('output.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 95])
质量范围是0-100,数值越大,画质越好,文件也越大。我一般设95,既保证画质,文件也不会太大。对于PNG格式,可以指定压缩级别:
cv2.imwrite('output.png', img, [cv2.IMWRITE_PNG_COMPRESSION, 3])
压缩级别0-9,0表示不压缩,9表示最大压缩。我个人习惯用3,平衡速度和大小。
2.4 图像属性:认识你的数据
图像读进来了,它长什么样?有多大?什么类型?这些信息很重要。
import cv2
img = cv2.imread('lena.jpg')
# 形状:高度、宽度、通道数
print('图像形状:', img.shape) # 例如 (512, 512, 3)
# 像素总数
print('像素总数:', img.size) # 512 * 512 * 3 = 786432
# 数据类型
print('数据类型:', img.dtype) # uint8
shape 返回一个元组:(高度, 宽度, 通道数)。灰度图只有两个值:(高度, 宽度)。
size 是所有像素的总数。对于彩色图,是 高×宽×3。
dtype 通常是 uint8,也就是0-255的整数。为什么是255?因为8位二进制最大能表示255。你想想看,如果每个像素用16位表示,范围就是0-65535,那就是高动态范围图像了。
uint16 类型的,直接用 imshow() 显示出来全白。后来才意识到,OpenCV的显示函数默认按0-255范围显示,超出部分会被截断。解决办法是归一化到0-255。
2.5 像素操作:图像的本质
终于到了最核心的部分。图像就是矩阵,矩阵由像素组成。我们可以直接访问和修改像素值。
2.5.1 访问单个像素
# 访问第100行、第200列的像素
pixel = img[100, 200]
print(pixel) # 彩色图返回 [B, G, R] 三个值
# 只访问蓝色通道
blue = img[100, 200, 0]
注意:行对应y坐标,列对应x坐标。也就是说 img[y, x]。这个顺序和数学中的坐标系不太一样,我刚开始也经常搞混。
2.5.2 修改像素值
# 把第100行、第200列的像素设为红色
img[100, 200] = [0, 0, 255] # BGR顺序,红色是 [0, 0, 255]
# 批量修改:把左上角50x50区域设为绿色
img[0:50, 0:50] = [0, 255, 0]
2.5.3 图像裁剪
说白了,图像裁剪就是取矩阵的一部分:
# 裁剪出第100到300行、第200到400列的区域
roi = img[100:300, 200:400]
cv2.imshow('ROI', roi)
这个操作在目标检测中太常用了。比如你检测到人脸,就把人脸区域裁剪出来,再做后续处理。
2.5.4 通道拆分与合并
有时候你需要单独处理某个颜色通道:
# 方法一:索引
b = img[:, :, 0]
g = img[:, :, 1]
r = img[:, :, 2]
# 方法二:使用OpenCV函数
b, g, r = cv2.split(img)
# 合并通道
img_merged = cv2.merge([b, g, r])
cv2.split() 虽然方便,但会创建新的数组,消耗内存。如果只是读取某个通道,直接用索引 img[:, :, 0] 更快。我在处理4K视频时,用索引比用split快了将近一倍。
2.6 综合示例:从读取到保存的完整流程
来,我们写一个完整的例子,把今天学的串起来:
import cv2
import numpy as np
# 1. 读取图像
img = cv2.imread('lena.jpg', cv2.IMREAD_COLOR)
print(f'图像尺寸: {img.shape}')
print(f'数据类型: {img.dtype}')
# 2. 显示原图
cv2.imshow('Original', img)
# 3. 像素操作:把图像中心区域涂成蓝色
h, w = img.shape[:2]
center_h, center_w = h // 2, w // 2
img[center_h-50:center_h+50, center_w-50:center_w+50] = [255, 0, 0]
# 4. 显示修改后的图
cv2.imshow('Modified', img)
# 5. 保存结果
cv2.imwrite('modified_lena.jpg', img)
# 6. 等待按键并关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
这个例子虽然简单,但涵盖了图像处理的基本流程:读→处理→显示→保存。你想想看,后面所有的图像处理算法,本质上都是在做第3步——修改像素值。只是修改的规则越来越复杂而已。
2.7 本章小结
今天我们学了五件事:
- 读取:
cv2.imread(),注意BGR顺序和读取模式 - 显示:
cv2.imshow()+waitKey(),别忘了这对组合拳 - 保存:
cv2.imwrite(),可以控制压缩质量 - 属性:
shape、size、dtype,认识你的数据 - 像素操作:访问、修改、裁剪、通道拆分合并
这些是图像处理的基本功。下一章我们会聊颜色空间转换,到时候你会发现,原来图像还可以用不同的"语言"来描述。嗯,今天就到这里,动手试试吧。
公众号:蓝海资料掘金营,微信deep3321