2. 图像基础操作:读取、显示、保存与属性
各位同学,欢迎来到第二章。
上一章我们搭好了OpenCV的环境,今天咱们来点实在的——图像的基础操作。说白了,就是怎么把一张图片“请”进程序里,怎么看看它长什么样,最后怎么把它存下来。
我个人习惯把这一章叫做“图像的Hello World”。你想想看,不管以后做多复杂的手势识别,第一步永远是:把摄像头拍到的画面读进来。所以,这一章打下的基础,会贯穿整个课程。
2.1 读取图像:imread() 函数
读取图像,用的是 cv2.imread()。这个函数很简单,给它一个文件路径,它就给你返回一个数组——没错,在OpenCV里,图像本质上就是一个多维数组(NumPy数组)。
import cv2
# 读取图像
img = cv2.imread('hand.jpg')
print(type(img)) # 输出:<class 'numpy.ndarray'>
这里有个坑,我一开始就踩过。如果路径不对,或者文件不存在,imread() 不会报错,而是返回 None。嗯,这很坑。所以,我建议你每次读取后,都检查一下:
if img is None:
print("图像读取失败,请检查路径")
exit()
imread() 还有第二个参数,用来控制读取方式。常用的有这几个:
| 参数 | 值 | 含义 |
|---|---|---|
cv2.IMREAD_COLOR |
1 | 默认值,加载彩色图像。忽略透明度。 |
cv2.IMREAD_GRAYSCALE |
0 | 加载为灰度图。 |
cv2.IMREAD_UNCHANGED |
-1 | 加载原图,包括Alpha通道(透明度)。 |
IMREAD_COLOR 读取,因为后续需要颜色信息来分割皮肤。但如果你只是做边缘检测之类的,直接读成灰度图,能省不少内存。
2.2 显示图像:imshow() 与 waitKey()
读进来了,总得看一眼吧?用 cv2.imshow()。
cv2.imshow('Hand Image', img)
cv2.waitKey(0) # 等待按键
cv2.destroyAllWindows()
这里有个关键点,waitKey() 是必须的。没有它,窗口会一闪而过。为什么?因为OpenCV的显示机制是事件驱动的,waitKey() 负责刷新窗口并等待用户输入。
waitKey(0) 表示无限等待,直到你按下任意键。waitKey(30) 表示等待30毫秒——这在处理视频流时非常有用,相当于设置了帧率。
waitKey(),结果程序直接卡死,窗口也关不掉。嗯,血的教训。另外,destroyAllWindows() 最好也加上,养成好习惯。
2.3 保存图像:imwrite()
处理完图像,想存下来?用 cv2.imwrite()。
cv2.imwrite('hand_processed.jpg', img)
第一个参数是保存路径,第二个参数是图像数据。它会根据文件扩展名自动选择编码格式。比如 .jpg 就用JPEG压缩,.png 就用PNG压缩。
这里有个小技巧。保存JPEG时,你可以控制质量:
cv2.imwrite('hand_high_quality.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 95])
质量范围是0-100,默认95。数值越大,文件越大,画质越好。保存PNG时,可以控制压缩级别:
cv2.imwrite('hand_compressed.png', img, [cv2.IMWRITE_PNG_COMPRESSION, 9])
压缩级别0-9,9是最高压缩(文件最小,但保存最慢)。
output/ 文件夹不存在,imwrite() 会静默失败,返回 False。所以,记得先 os.makedirs()。
2.4 图像属性:尺寸、通道、像素值
图像读进来了,我们得知道它的一些基本信息。说白了,就是三个属性:
- 尺寸(shape): 高度、宽度、通道数
- 通道(channels): 是灰度图还是彩色图?
- 像素值(dtype): 每个像素点的数值范围
来看代码:
import cv2
img = cv2.imread('hand.jpg')
# 获取图像属性
print("图像形状:", img.shape) # (高度, 宽度, 通道数)
print("图像大小:", img.size) # 总像素数 = 高 * 宽 * 通道
print("数据类型:", img.dtype) # 通常是 uint8
# 单独获取
height, width, channels = img.shape
print(f"高度: {height}, 宽度: {width}, 通道数: {channels}")
输出示例:
图像形状: (480, 640, 3)
图像大小: 921600
数据类型: uint8
高度: 480, 宽度: 640, 通道数: 3
解释一下:
- shape: 返回一个元组。对于彩色图,是 (高, 宽, 3)。3代表BGR三个通道。对于灰度图,是 (高, 宽),没有第三个维度。
- size: 所有像素的总数。480*640*3 = 921600。
- dtype: 通常是
uint8,即0-255的整数。这也是为什么像素值范围是0-255。
那么,如何访问具体的像素值呢?
# 访问 (100, 200) 位置的像素
pixel = img[100, 200]
print("BGR值:", pixel) # [B, G, R]
# 只访问蓝色通道
blue = img[100, 200, 0]
print("蓝色值:", blue)
# 修改像素值
img[100, 200] = [255, 0, 0] # 变成蓝色
注意,OpenCV默认的颜色顺序是 BGR,不是RGB。这一点特别容易搞混。我记得刚开始做项目时,用OpenCV读图,然后用Matplotlib显示,结果颜色全乱了——因为Matplotlib用的是RGB顺序。
2.5 实战小练习:读取、显示、保存一条龙
来,我们写一个完整的例子。把上面学的串起来:
import cv2
import os
# 1. 读取图像
img_path = 'hand.jpg'
img = cv2.imread(img_path)
if img is None:
print(f"无法读取图像: {img_path}")
exit()
# 2. 显示图像属性
print(f"图像尺寸: {img.shape}")
print(f"总像素数: {img.size}")
# 3. 显示图像
cv2.imshow('Original Hand', img)
cv2.waitKey(0)
# 4. 修改一下:把图像上半部分变蓝
img[:240, :] = [255, 0, 0] # 前240行变成蓝色
# 5. 显示修改后的图像
cv2.imshow('Modified Hand', img)
cv2.waitKey(0)
# 6. 保存图像
output_dir = 'output'
if not os.path.exists(output_dir):
os.makedirs(output_dir)
cv2.imwrite(f'{output_dir}/hand_modified.jpg', img)
print("图像已保存")
cv2.destroyAllWindows()
这个例子,你跑一遍,就能直观感受到:读取、显示、修改像素、保存,整个流程就通了。
小结
这一章,我们学了四个基本操作:
- 读取:
cv2.imread(),注意检查返回值是否为None - 显示:
cv2.imshow()+waitKey(),缺一不可 - 保存:
cv2.imwrite(),注意目录存在 - 属性: shape、size、dtype,以及像素访问
下一章,我们会进入更核心的内容——颜色空间转换。为什么肤色检测要用HSV?为什么BGR不适合做颜色分割?到时候你就明白了。
好了,今天的课就到这里。去试试代码吧,有问题随时交流。