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提取。每一步都是后续章节的基础。

图像基础操作知识体系 图像基础操作知识体系 图像读取 imread() 显示 imshow() 保存 imwrite() 像素访问与修改 批量操作 (NumPy) 切片提取 ROI 交互式 selectROI() 工业视觉应用:缺陷检测、定位、测量 💡 学习路径建议 先掌握读取和显示,再深入像素操作,最后结合ROI提取应用到实际项目中。

我的建议:初学者容易犯的错是搞混坐标顺序。记住:OpenCV中图像坐标是 (行, 列),也就是 (y, x)。而矩形框是 (x, y, w, h)。这两个顺序不一样,写代码时多留个心眼。

嗯,这一章的内容就这些。图像读取、显示、保存是基本功,像素操作是理解图像本质的关键,ROI提取则是提升效率的利器。把这些练熟了,后面学滤波、边缘检测、特征提取就会轻松很多。