图像处理基础:图像读取与显示、色彩空间转换、几何变换
各位同学,欢迎来到第二章。说实话,图像处理的基础操作就像你学编程时的“Hello World”——简单,但绕不开。我做了这么多年机器视觉,发现很多缺陷检测的坑,其实都埋在这些基础操作里。今天我们就把它彻底捋清楚。
2.1 图像读取与显示——你的第一行视觉代码
在OpenCV里,读取图像用 cv2.imread(),显示用 cv2.imshow()。听起来很简单对吧?但这里有个细节,我当年刚入行时就吃过亏。
cv2.imread() 默认读取的是 BGR 格式,不是 RGB!很多新手直接用 plt.imshow() 显示,结果颜色全乱了。我曾经在项目里因为这个原因,花了整整一个下午排查为什么检测到的红色缺陷位置总是不对——后来发现是通道顺序搞反了。
import cv2
import matplotlib.pyplot as plt
# 读取图像(默认BGR)
img = cv2.imread('defect_sample.jpg')
# 显示图像(OpenCV方式)
cv2.imshow('Original', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 正确显示(转RGB后用matplotlib)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.title('缺陷样本')
plt.axis('off')
plt.show()
我个人习惯在读取图像后,第一时间检查图像是否为空。你想想看,如果路径写错了,img 就是个 None,后面所有操作都会崩。加一行判断,能省很多调试时间。
if img is None:
print("图像读取失败,请检查路径")
exit()
2.2 色彩空间转换——灰度与HSV
色彩空间转换,说白了就是把图像从一种颜色表示方式变成另一种。在缺陷检测里,最常用的就是灰度图和HSV。
2.2.1 灰度转换
为什么要转灰度?因为很多边缘检测算法(比如Canny)只接受单通道输入。而且灰度图数据量小,处理速度快。我建议你在做预处理时,先把彩色图转成灰度,除非颜色信息对缺陷判断至关重要。
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('灰度图', gray)
cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE),省一步转换。
2.2.2 HSV转换——为什么它比RGB更适合做缺陷检测?
RGB适合显示,但不适合做颜色分割。为什么呢?因为RGB的三个通道高度相关,光照一变,三个值一起变。而HSV把色调(H)、饱和度(S)、明度(V)分开了。你想想看,一个红色缺陷,不管它是在强光下还是阴影里,它的色调H值基本不变。这就给了我们一个稳定的分割依据。
我记得有一次做PCB板焊点缺陷检测,焊点氧化发黑,在RGB空间里很难跟背景区分。但转到HSV后,通过H通道的阈值分割,一下就揪出来了。
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 定义红色的HSV范围
lower_red = np.array([0, 50, 50])
upper_red = np.array([10, 255, 255])
# 提取红色区域
mask = cv2.inRange(hsv, lower_red, upper_red)
cv2.imshow('红色缺陷掩膜', mask)
| 色彩空间 | 通道含义 | 适用场景 |
|---|---|---|
| RGB | 红、绿、蓝 | 显示、常规处理 |
| 灰度 | 亮度(单通道) | 边缘检测、形态学操作 |
| HSV | 色调、饱和度、明度 | 颜色分割、缺陷提取 |
2.3 图像几何变换——缩放、旋转、平移
几何变换在缺陷定位中非常实用。比如你要把不同角度拍摄的工件对齐到标准模板,或者把ROI区域放大做精细检测。
2.3.1 缩放
用 cv2.resize()。这里有个坑:插值方法的选择。缩小图像用 INTER_AREA,放大用 INTER_CUBIC 或 INTER_LINEAR。我曾经为了省事,一律用默认的线性插值,结果放大后的图像边缘出现锯齿,导致后续的Blob分析把噪声误判为缺陷。
# 缩小到原来的一半
small = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
# 放大到2倍
large = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
2.3.2 旋转
旋转需要先构建一个旋转矩阵,然后用 cv2.warpAffine() 执行。嗯,这里要注意:旋转后图像会被裁剪,除非你手动调整输出尺寸。
h, w = img.shape[:2]
center = (w // 2, h // 2)
# 旋转矩阵:中心点、角度、缩放
M = cv2.getRotationMatrix2D(center, 45, 1.0)
# 执行旋转,保持图像完整
rotated = cv2.warpAffine(img, M, (w, h))
cv2.imshow('旋转45度', rotated)
2.3.3 平移
平移相对简单,也是用 warpAffine,只是变换矩阵不同。
# 向右平移50像素,向下平移30像素
M = np.float32([[1, 0, 50], [0, 1, 30]])
shifted = cv2.warpAffine(img, M, (w, h))
2.4 本章知识体系总览
下面这张图,是我自己整理的知识结构。你可以把它当作本章的“地图”,学完后再回来看,应该会有更深的理解。
2.5 避坑指南与个人经验
- 图像路径问题: 我曾经在项目里用相对路径,结果换了一台电脑就跑不起来了。后来我统一用路径,或者用
os.path.join()拼接,再也没出过问题。 - 数据类型: OpenCV 默认用 uint8(0-255),但有些操作(比如旋转)会生成浮点数。记得用
np.uint8()转换,否则显示出来全是白的。 - 窗口卡死: 如果你用了
cv2.imshow()但没加cv2.waitKey(),窗口会一闪而过。加了waitKey(0)后,按任意键才关闭。这是新手最容易忽略的。