图像处理基础(上):图像读取与显示、色彩空间转换、几何变换
各位同学,欢迎来到第二章。上一章我们搭好了开发环境,今天咱们来点真家伙——图像处理的基础操作。
说实话,图像处理是整个交通标志识别系统的地基。你想想看,摄像头拍回来的原始图像,噪点多、光照乱、角度歪,直接扔给模型去识别,那结果肯定惨不忍睹。我当年第一次做车牌识别项目时,就吃了这个亏——图像预处理没做好,模型准确率死活上不去。
好,咱们开始。今天要讲三个核心模块:图像怎么读进来、怎么在不同色彩空间里切换、怎么对图像做几何变换。
一、图像读取与显示
在OpenCV里,读取图像用 cv2.imread(),显示用 cv2.imshow()。听起来简单吧?但这里有个坑,我踩过。
来看代码:
import cv2
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread('traffic_sign.jpg')
# 检查是否读取成功
if img is None:
print("图像读取失败,检查路径")
exit()
# 显示图像(OpenCV方式)
cv2.imshow('Traffic Sign', img)
cv2.waitKey(0) # 等待按键
cv2.destroyAllWindows()
# 如果用matplotlib显示,需要转换通道
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.title('交通标志')
plt.show()
这里有个小技巧:cv2.waitKey(0) 会让窗口一直显示,直到你按下任意键。如果你想让窗口自动关闭,可以设置一个延时,比如 cv2.waitKey(3000) 表示3秒后自动关闭。
二、色彩空间转换
色彩空间转换,说白了就是把图像从一种颜色表示方式变成另一种。最常用的三种:RGB、HSV、灰度图。
2.1 RGB与灰度图
灰度图只有亮度信息,没有颜色。为什么要转灰度?因为很多算法(比如边缘检测、特征提取)在灰度图上跑得更快,而且不受颜色干扰。
# 转灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 显示灰度图
cv2.imshow('Gray Image', gray)
cv2.waitKey(0)
灰度化的原理其实很简单:对RGB三个通道加权平均。OpenCV用的权重是:Gray = 0.299*R + 0.587*G + 0.114*B。为什么绿色权重最高?因为人眼对绿色最敏感。
2.2 RGB与HSV
HSV色彩空间把颜色分解成色调(Hue)、饱和度(Saturation)、明度(Value)。这在做颜色分割时特别好用。
举个例子:你想从图像里提取红色交通标志。在RGB空间里,红色可能是 (200, 30, 30),也可能是 (180, 20, 20),阈值很难定。但在HSV空间里,红色的色调值集中在0°附近,你只需要设定一个H的范围就行。
# BGR转HSV
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)
# 提取红色区域
red_region = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('Red Region', red_region)
cv2.waitKey(0)
为什么要用 cv2.inRange()?它会生成一个二值掩码,在指定范围内的像素设为255(白色),其他设为0(黑色)。然后用 bitwise_and 把原图和掩码做与运算,就能提取出目标区域。
2.3 色彩空间转换的应用场景
| 色彩空间 | 适用场景 | 我的建议 |
|---|---|---|
| RGB | 通用显示、深度学习输入 | 注意OpenCV是BGR顺序 |
| HSV | 颜色分割、目标检测 | 做交通标志识别首选 |
| 灰度 | 边缘检测、特征提取 | 能提速,但会丢失颜色信息 |
三、图像几何变换
几何变换,就是改变图像的形状、大小、位置。交通标志在摄像头里可能是歪的、小的、斜的,我们需要把它们校正到标准状态。
3.1 缩放
缩放用 cv2.resize()。这里有个选择:用哪种插值方法?
# 缩小图像(用最近邻插值)
small = cv2.resize(img, (100, 100), interpolation=cv2.INTER_NEAREST)
# 放大图像(用双线性插值)
large = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_LINEAR)
# 高质量放大(用三次插值)
high_quality = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
3.2 平移
平移就是让图像在x和y方向上移动。需要构造一个仿射变换矩阵。
import numpy as np
# 定义平移矩阵
M = np.float32([[1, 0, 50], # x方向移动50像素
[0, 1, 30]]) # y方向移动30像素
# 执行平移
shifted = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
cv2.imshow('Shifted Image', shifted)
cv2.waitKey(0)
注意 warpAffine 的第三个参数是输出图像的尺寸。如果移动后部分图像超出边界,会被裁剪掉。
3.3 旋转
旋转也是用仿射变换。OpenCV提供了 cv2.getRotationMatrix2D() 来生成旋转矩阵。
# 获取图像中心
h, w = img.shape[:2]
center = (w // 2, h // 2)
# 生成旋转矩阵(旋转45度,缩放1.0)
M = cv2.getRotationMatrix2D(center, 45, 1.0)
# 执行旋转
rotated = cv2.warpAffine(img, M, (w, h))
cv2.imshow('Rotated Image', rotated)
cv2.waitKey(0)
这里有个问题:旋转后图像四个角会被裁剪掉。如果想保留完整图像,需要调整输出尺寸。
# 计算旋转后的新尺寸
cos = abs(M[0, 0])
sin = abs(M[0, 1])
new_w = int(h * sin + w * cos)
new_h = int(h * cos + w * sin)
# 调整平移量
M[0, 2] += new_w // 2 - center[0]
M[1, 2] += new_h // 2 - center[1]
rotated_full = cv2.warpAffine(img, M, (new_w, new_h))
3.4 仿射变换与透视变换
仿射变换是线性变换加平移,保持平行线平行。透视变换则更灵活,可以把矩形变成任意四边形。
在交通标志识别中,透视变换特别有用。比如你拍到的标志是倾斜的,可以用透视变换把它校正成正面视角。
# 原图中的四个点(标志的四个角)
pts1 = np.float32([[50, 50], [200, 50], [50, 200], [200, 200]])
# 目标图中的四个点(校正后的位置)
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
# 计算透视变换矩阵
M = cv2.getPerspectiveTransform(pts1, pts2)
# 执行透视变换
warped = cv2.warpPerspective(img, M, (300, 300))
cv2.imshow('Warped Image', warped)
cv2.waitKey(0)
四、实战小练习
好了,理论讲完了。咱们来个小练习,把今天学的串起来。
任务:读取一张交通标志图像,先转HSV提取红色区域,然后对提取到的区域做透视校正,最后缩放到统一尺寸。
import cv2
import numpy as np
# 1. 读取图像
img = cv2.imread('stop_sign.jpg')
if img is None:
print("读取失败")
exit()
# 2. 转HSV提取红色
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_red = np.array([0, 50, 50])
upper_red = np.array([10, 255, 255])
mask = cv2.inRange(hsv, lower_red, upper_red)
# 3. 找到轮廓
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if len(contours) == 0:
print("未找到红色区域")
exit()
# 取最大轮廓
cnt = max(contours, key=cv2.contourArea)
# 4. 获取四个角点(用近似多边形)
epsilon = 0.02 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
if len(approx) == 4:
pts1 = np.float32(approx)
pts2 = np.float32([[0, 0], [200, 0], [0, 200], [200, 200]])
M = cv2.getPerspectiveTransform(pts1, pts2)
warped = cv2.warpPerspective(img, M, (200, 200))
# 5. 缩放到标准尺寸
final = cv2.resize(warped, (100, 100))
cv2.imshow('Result', final)
cv2.waitKey(0)
else:
print("未找到四边形轮廓")
这个练习涵盖了今天讲的三个核心内容。你可以在自己的数据集上试试,看看效果如何。
好,今天就到这里。下一章我们会讲图像滤波、边缘检测和形态学操作,这些都是图像预处理的重头戏。记得动手敲代码,光看是学不会的。