4、OpenCV基础:图像读取与显示、颜色空间转换、图像滤波与边缘检测

好,咱们进入正题。这一章是OpenCV的入门实操课。说白了,就是让你学会怎么把一张图片“请”进程序里,再对它做点手脚,最后看看效果。

我刚开始做嵌入式视觉那会儿,觉得OpenCV就是个调库的活儿。后来踩的坑多了才发现——基础不牢,地动山摇。图像读取的编码方式搞错,颜色空间没转对,滤波参数选得离谱……这些看似简单的问题,在边缘设备上会被放大十倍。

所以,咱们稳扎稳打。今天这四块内容,你吃透了,后面的行人检测和跟踪才能跑得顺。

4.1 图像读取与显示:你的第一行视觉代码

先来最基础的。把一张图片从硬盘里读进来,再显示到屏幕上。OpenCV里用的是 imread()imshow()

import cv2

# 读取图像
img = cv2.imread('pedestrian.jpg')

# 显示图像
cv2.imshow('Pedestrian Detection', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

嗯,这里要注意一个坑。 imread() 默认是以 BGR 格式读取的。对,你没看错,不是RGB。这是OpenCV的历史遗留问题。我曾经在项目里直接用 matplotlib 显示OpenCV读进来的图,结果颜色全乱了,找了半天bug……

避坑指南: 如果你用 matplotlib 显示OpenCV图像,记得先转换颜色空间。否则你会看到一张“蓝脸”的行人。

另外,在边缘设备上,比如树莓派或者Jetson Nano,显示图像可能会有点卡。我建议你调试阶段用 imshow,部署阶段直接保存到文件或者通过管道传输。

4.2 颜色空间转换:为什么非转不可?

颜色空间转换,说白了就是把图像从一种颜色表达方式变成另一种。最常见的操作是 BGR 转灰度BGR 转 HSV

你想想看,行人检测里,很多时候我们并不需要颜色信息。一张灰度图,计算量直接降到原来的三分之一。在边缘设备上,这就是实打实的性能提升。

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

我个人习惯在做任何图像处理之前,先看一眼图像的通道数和数据类型。用 img.shapeimg.dtype 检查一下,能避免很多低级错误。

小技巧: HSV空间在做颜色分割时特别好用。比如你要检测行人身上的荧光背心,用HSV比用BGR稳定得多。

4.3 图像滤波:让图像“干净”一点

滤波,说白了就是去噪。摄像头采集的图像,尤其是低成本摄像头,噪声多得让人头疼。我遇到过最夸张的一次,是在一个光线很差的停车场,图像上全是椒盐噪声,行人轮廓都看不清。

常用的滤波有三种:

滤波类型 函数 特点 适用场景
均值滤波 cv2.blur() 简单快速,但会模糊边缘 噪声较轻的场景
高斯滤波 cv2.GaussianBlur() 权重分配,边缘保留稍好 高斯噪声,行人检测预处理
中值滤波 cv2.medianBlur() 对椒盐噪声效果极佳 摄像头坏点、强噪声环境
# 高斯滤波,核大小5x5
blurred = cv2.GaussianBlur(img, (5, 5), 0)

# 中值滤波,核大小5
median = cv2.medianBlur(img, 5)

为什么核大小通常是奇数?因为需要有一个中心像素点。你想想看,如果是偶数,中心点怎么定义?

核心经验: 在边缘设备上,我建议优先用高斯滤波。它效果好,计算量适中。中值滤波虽然去噪强,但计算量大,帧率会掉。

4.4 边缘检测:找到物体的“轮廓”

边缘检测,是计算机视觉里最经典的操作之一。它帮我们找到图像中亮度变化剧烈的地方——说白了,就是物体的边界。

最常用的方法是 Canny边缘检测。它分四步:去噪、计算梯度、非极大值抑制、双阈值检测。

edges = cv2.Canny(blurred, 50, 150)

这里两个阈值是关键。 50 是低阈值, 150 是高阈值。低于低阈值的点被丢弃,高于高阈值的点被保留为强边缘,介于两者之间的点如果与强边缘相连则保留。

我曾经在一个项目中,为了调这两个阈值,花了整整一个下午。后来发现,不同的光照条件下,最优阈值差别很大。所以我后来写了个自适应阈值的函数,根据图像的平均亮度动态调整。

避坑指南: Canny检测前一定要先滤波。不滤波直接上Canny,你会得到一张“毛刺”图,全是噪声点,根本没法用。

另外,在边缘设备上,Canny的计算量不小。如果你对实时性要求高,可以考虑用 Sobel算子 或者 Laplacian算子 做简化版边缘检测。

# Sobel边缘检测
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
sobel = cv2.magnitude(sobelx, sobely)

嗯,这里要注意,Sobel算子的输出是浮点数,需要转成 uint8 才能正常显示。

4.5 实战组合:从读取到边缘检测的完整流程

咱们把今天学的串起来,写一个完整的例子。这个流程,我在多个行人检测项目里都用过,很稳。

import cv2

# 1. 读取图像
img = cv2.imread('pedestrian.jpg')

# 2. 转灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 3. 高斯滤波去噪
blurred = cv2.GaussianBlur(gray, (5, 5), 0)

# 4. Canny边缘检测
edges = cv2.Canny(blurred, 50, 150)

# 5. 显示结果
cv2.imshow('Original', img)
cv2.imshow('Edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()

这个流程,你可以在自己的图片上试试。我建议你用一张行人比较清晰的图片,看看边缘检测能不能把人的轮廓勾出来。

小技巧: 在边缘设备上,可以把 GaussianBlur 的核大小从5x5改成3x3,速度能提升不少,效果差别不大。

好了,今天的内容就到这里。图像读取、颜色空间转换、滤波、边缘检测——这四个基础操作,是后续所有视觉算法的基石。你把这些练熟了,后面学行人检测和跟踪,会轻松很多。

下一章,咱们聊聊特征提取。到时候你会看到,今天学的边缘检测,其实是很多高级特征的基础。