4. OpenCV 图像处理基础:图像读取与显示、色彩空间转换、图像滤波与边缘检测、形态学操作
图像处理是嵌入式视觉的起点。说白了,摄像头采集到的原始数据,不能直接扔给神经网络。你得先把它“收拾”干净,转换成模型能理解的样子。
我个人习惯把 OpenCV 比作一把瑞士军刀。功能多,但真正常用的就那么几招。今天咱们就把这几招练熟。
4.1 图像读取与显示:第一行代码
先来个最基础的。读取一张图片,显示出来,然后等待按键退出。这段代码我几乎每天都会敲一遍。
#include <opencv2/opencv.hpp>
using namespace cv;
int main() {
// 读取图像,默认是彩色模式
Mat img = imread("test.jpg");
if (img.empty()) {
printf("图片没找到,检查路径\n");
return -1;
}
// 显示
imshow("Display Window", img);
waitKey(0); // 等待按键
destroyAllWindows(); // 关闭所有窗口
return 0;
}
namedWindow("win", WINDOW_NORMAL) 创建窗口,这样窗口可以缩放,适应小屏幕。
imread() 的第二个参数可以控制读取模式。常用的有:
| 标志 | 值 | 说明 |
|---|---|---|
| IMREAD_COLOR | 1 | 彩色,默认。3 通道 BGR |
| IMREAD_GRAYSCALE | 0 | 灰度图,1 通道。省内存,处理快 |
| IMREAD_UNCHANGED | -1 | 包含 Alpha 通道,原样读取 |
你想想看,在边缘设备上,内存和算力都有限。如果后续只做边缘检测,直接读成灰度图,能省掉一次色彩转换的开销。
4.2 色彩空间转换:BGR、灰度、HSV
OpenCV 默认是 BGR 顺序,不是 RGB。这个坑我刚开始也踩过。显示出来颜色不对,排查了半天才发现是通道顺序搞反了。
常用的转换函数是 cvtColor()。
Mat gray, hsv;
cvtColor(img, gray, COLOR_BGR2GRAY); // 转灰度
cvtColor(img, hsv, COLOR_BGR2HSV); // 转 HSV
为什么用 HSV?举个例子。你要在画面中找出红色物体。在 BGR 空间里,红色受光照影响很大,阈值很难调。但在 HSV 空间,H(色调)通道基本不受光照影响,你只需要设定 H 的范围就行。
H: 0–179 | S: 0–255 | V: 0–255
我在做交通灯识别项目时,就是靠 HSV 空间来分割红绿灯区域的。效果比 BGR 好太多。
4.3 图像滤波:去噪是预处理的关键
摄像头采集的图像,多少都有噪声。椒盐噪声、高斯噪声……不处理的话,边缘检测会出来一堆假边缘。
三种常用滤波:
| 滤波类型 | 函数 | 特点 | 适用场景 |
|---|---|---|---|
| 均值滤波 | blur() | 简单平均,速度快 | 噪声较轻,对实时性要求高 |
| 高斯滤波 | GaussianBlur() | 加权平均,保留边缘更好 | 高斯噪声,通用场景 |
| 中值滤波 | medianBlur() | 取中位数,抗椒盐噪声强 | 椒盐噪声明显时 |
Mat blurred;
blur(img, blurred, Size(3, 3)); // 均值
GaussianBlur(img, blurred, Size(3, 3), 0); // 高斯
medianBlur(img, blurred, 3); // 中值
4.4 边缘检测:Canny 算子
边缘检测里,Canny 是公认的“黄金标准”。它分四步走:去噪 → 计算梯度 → 非极大值抑制 → 双阈值筛选。
Mat edges;
Canny(blurred, edges, 50, 150); // 低阈值50,高阈值150
双阈值怎么调?我有个经验:高阈值设成低阈值的 2–3 倍。低阈值控制边缘的敏感度,太高会漏掉边缘,太低会引入噪声。
举个例子。我在做车道线检测时,低阈值设 40,高阈值设 120。效果不错。但换了一个光照环境,就得重新调。所以后来我写了个滑动条,动态调参,方便多了。
createTrackbar() 创建滑动条,实时调整 Canny 阈值。这样不用每次改代码重新编译。
4.5 形态学操作:膨胀、腐蚀、开闭运算
边缘检测完,得到的二值图往往有断点、有毛刺。形态学操作就是用来“修图”的。
| 操作 | 函数 | 效果 |
|---|---|---|
| 腐蚀 | erode() | 消除小白点,边缘向内收缩 |
| 膨胀 | dilate() | 连接断点,边缘向外扩张 |
| 开运算 | morphologyEx(..., MORPH_OPEN) | 先腐蚀后膨胀,去噪点 |
| 闭运算 | morphologyEx(..., MORPH_CLOSE) | 先膨胀后腐蚀,填小孔 |
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat result;
erode(edges, result, kernel); // 腐蚀
dilate(edges, result, kernel); // 膨胀
morphologyEx(edges, result, MORPH_OPEN, kernel); // 开运算
morphologyEx(edges, result, MORPH_CLOSE, kernel); // 闭运算
我曾经做过一个零件尺寸测量项目。边缘检测后,零件轮廓有断点,测量结果偏差很大。后来用了一次闭运算,轮廓连上了,测量精度直接提升到 0.1 毫米以内。
MORPH_RECT(矩形)—— 通用
MORPH_ELLIPSE(椭圆)—— 更平滑
MORPH_CROSS(十字)—— 保留角点
嗯,到这里,OpenCV 图像处理的基础链条就串起来了。从读图到显示,从色彩转换到滤波,再到边缘检测和形态学修图。每一步都是为下一步服务的。在嵌入式 Linux 上跑这些代码,只要注意内存和计算量,基本不会出大问题。