4. 图像基础操作:读取、显示、保存、像素操作与ROI提取
图像处理的第一步,说白了就是跟文件打交道。你拿到的不管是工业相机拍的,还是现场传回来的图片,总得先把它读进内存里,才能做后续的检测、识别。这一章我们就把这些最基础的操作捋一遍。别小看这些,我见过不少项目出问题,最后查出来就是读取格式不对,或者ROI截偏了。
4.1 图像读取:imread() 的细节
OpenCV里读取图像用的是 cv2.imread()。这个函数看着简单,但参数选不对,后面全白干。
核心参数:
cv2.IMREAD_COLOR (1):默认方式,加载为BGR三通道彩色图。注意是BGR,不是RGB!cv2.IMREAD_GRAYSCALE (0):直接转成单通道灰度图。工业检测里很多场景用这个,省内存、速度快。cv2.IMREAD_UNCHANGED (-1):保留原图所有通道,包括Alpha透明通道。
import cv2
# 彩色读取
img_color = cv2.imread('gear.jpg', cv2.IMREAD_COLOR)
# 灰度读取
img_gray = cv2.imread('gear.jpg', cv2.IMREAD_GRAYSCALE)
# 保留原通道
img_unchanged = cv2.imread('gear.png', cv2.IMREAD_UNCHANGED)
我曾经踩过的坑:有一次现场调试,图像死活读不出来,返回None。查了半天,发现是中文路径的问题。OpenCV的imread不支持中文路径和特殊字符。解决办法要么改路径名,要么用 cv2.imdecode() 配合 np.fromfile() 来读。
4.2 图像显示与保存
显示图像用 cv2.imshow(),保存用 cv2.imwrite()。这两个函数在调试阶段几乎天天用。
显示窗口的注意事项
我个人习惯在显示前先调一下窗口大小,尤其是高分辨率工业相机拍的图,直接显示可能超出屏幕。
cv2.namedWindow('Inspection Result', cv2.WINDOW_NORMAL)
cv2.resizeWindow('Inspection Result', 800, 600)
cv2.imshow('Inspection Result', img)
cv2.waitKey(0) # 等待按键
cv2.destroyAllWindows()
小技巧:cv2.waitKey(0) 表示无限等待按键。如果你想让窗口自动关闭,可以设置毫秒数,比如 cv2.waitKey(1000) 就是显示1秒后自动关闭。这在批量调试时很有用。
保存图像的质量控制
保存JPEG时可以控制压缩质量,保存PNG时可以控制压缩级别。这在工业存档时很关键。
# JPEG质量:95(0-100,越高越好)
cv2.imwrite('result.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 95])
# PNG压缩级别:3(0-9,越高文件越小但越慢)
cv2.imwrite('result.png', img, [cv2.IMWRITE_PNG_COMPRESSION, 3])
| 保存格式 | 参数名 | 值范围 | 说明 |
|---|---|---|---|
| JPEG | IMWRITE_JPEG_QUALITY | 0-100 | 数值越大,质量越高,文件越大 |
| PNG | IMWRITE_PNG_COMPRESSION | 0-9 | 数值越大,压缩率越高,速度越慢 |
| WebP | IMWRITE_WEBP_QUALITY | 0-100 | 适合网络传输,工业场景用得少 |
4.3 像素操作:图像的本质
图像在计算机里就是矩阵。对于灰度图,每个像素是一个0-255的值;对于彩色图,每个像素是BGR三个值。你想想看,所有的图像处理算法,归根结底都是在操作这些数字。
访问和修改像素
# 假设img是灰度图
pixel_value = img[100, 200] # 获取第100行、第200列的像素值
print(f'像素值: {pixel_value}')
# 修改像素
img[100, 200] = 128
# 彩色图访问
b, g, r = img_color[100, 200]
print(f'B: {b}, G: {g}, R: {r}')
# 修改彩色像素
img_color[100, 200] = [255, 0, 0] # 变成蓝色(BGR顺序)
注意:OpenCV中彩色图像通道顺序是BGR,不是RGB。如果你用 cv2.imread() 读图,然后用 matplotlib 显示,颜色会偏蓝偏黄。因为matplotlib默认用RGB。解决办法是用 cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 转换一下。
批量像素操作
实际项目中很少一个一个像素操作,太慢了。我们通常用NumPy的切片功能,或者用OpenCV提供的函数。
import numpy as np
# 将图像所有像素值减半(模拟曝光不足)
img_half = (img * 0.5).astype(np.uint8)
# 将图像中所有大于200的像素设为255(二值化阈值)
img_binary = np.where(img > 200, 255, 0).astype(np.uint8)
# 彩色图:将红色通道全部置0
img_color[:, :, 2] = 0 # 注意:BGR顺序,红色是索引2
4.4 ROI区域提取:只关注关键区域
ROI(Region of Interest)就是感兴趣区域。工业检测中,我们往往只关心图像中的某一部分,比如电路板上的某个焊点、手机屏幕的某个区域。提取ROI可以大幅减少计算量,提高检测速度。
使用NumPy切片提取ROI
这是最直接的方法。图像在NumPy中就是数组,切片操作天然支持。
# 假设我们要提取图像中从(100, 200)到(300, 400)的矩形区域
# 注意:坐标顺序是 [y1:y2, x1:x2]
roi = img[100:300, 200:400]
# 显示ROI
cv2.imshow('ROI', roi)
cv2.waitKey(0)
# 将ROI粘贴到另一位置
img[50:250, 50:250] = roi # 注意尺寸要一致
工业场景实例:我在做手机屏幕缺陷检测时,屏幕区域只占整张图的30%左右。如果不提取ROI,直接在全图上跑YOLO,一张图要处理200ms。提取ROI后,处理时间降到60ms,而且误检率也降低了,因为背景干扰被排除了。
使用cv2.selectROI交互式选择
调试阶段,有时候需要手动框选ROI区域。OpenCV提供了一个交互式函数。
# 交互式选择ROI
x, y, w, h = cv2.selectROI('Select ROI', img, False)
roi = img[y:y+h, x:x+w]
print(f'ROI坐标: x={x}, y={y}, w={w}, h={h}')
cv2.destroyAllWindows()
ROI的常见应用场景
| 场景 | ROI提取方式 | 说明 |
|---|---|---|
| PCB焊点检测 | 固定坐标切片 | 焊点位置固定,直接切片即可 |
| 传送带上工件 | 动态ROI(结合边缘检测) | 先找工件轮廓,再提取ROI |
| 液晶屏坏点检测 | 网格划分ROI | 将屏幕分成多个小区域逐一检测 |
| 字符识别 | 轮廓外接矩形 | 先找字符轮廓,再提取每个字符 |
4.5 本章知识体系
下面这张图把本章的核心操作串起来了。从图像读取开始,到显示、保存,再到像素级别的操作,最后是ROI提取。每一步都是后续章节的基础。
我的建议:初学者容易犯的错是搞混坐标顺序。记住:OpenCV中图像坐标是 (行, 列),也就是 (y, x)。而矩形框是 (x, y, w, h)。这两个顺序不一样,写代码时多留个心眼。
嗯,这一章的内容就这些。图像读取、显示、保存是基本功,像素操作是理解图像本质的关键,ROI提取则是提升效率的利器。把这些练熟了,后面学滤波、边缘检测、特征提取就会轻松很多。