图像处理基础:图像读取与显示、色彩空间转换、几何变换

各位同学,欢迎来到第二章。说实话,图像处理的基础操作就像你学编程时的“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 本章知识体系总览

下面这张图,是我自己整理的知识结构。你可以把它当作本章的“地图”,学完后再回来看,应该会有更深的理解。

图像处理基础操作知识体系 图像处理基础 读取与显示 BGR vs RGB 通道顺序 色彩空间转换 灰度:单通道,边缘检测 HSV:色调分离,缺陷提取 几何变换 缩放:INTER_AREA / INTER_CUBIC 旋转:warpAffine + 旋转矩阵 平移:仿射变换矩阵

2.5 避坑指南与个人经验

  • 图像路径问题: 我曾经在项目里用相对路径,结果换了一台电脑就跑不起来了。后来我统一用路径,或者用 os.path.join() 拼接,再也没出过问题。
  • 数据类型: OpenCV 默认用 uint8(0-255),但有些操作(比如旋转)会生成浮点数。记得用 np.uint8() 转换,否则显示出来全是白的。
  • 窗口卡死: 如果你用了 cv2.imshow() 但没加 cv2.waitKey(),窗口会一闪而过。加了 waitKey(0) 后,按任意键才关闭。这是新手最容易忽略的。
💡 我的工作流: 每次拿到一张新图像,我习惯先打印它的 shape 和 dtype,确认通道数和数据类型。这一步只要 2 秒钟,但能避免后面 80% 的 bug。