3、OpenCV基础:OpenCV简介与安装、图像读取与显示、色彩空间转换、ROI区域提取
各位同学,欢迎来到第三讲。从这一章开始,我们正式进入OpenCV的世界。说实话,OpenCV是我在嵌入式视觉领域打交道最多的库,没有之一。从最早的ARM9平台到现在的RK3588,我几乎每个项目都离不开它。今天咱们就把最基础、最核心的几个概念讲透。
3.1 OpenCV简介与安装
OpenCV全称是Open Source Computer Vision Library,说白了就是一个开源的计算机视觉库。它支持C++、Python、Java等多种语言,在嵌入式平台上我们通常用C++版本。我个人习惯在Ubuntu系统下开发,因为交叉编译工具链支持得最好。
为什么嵌入式首选OpenCV? 你想想看,一个库能同时搞定图像读取、矩阵运算、特征提取、模型推理,而且还有硬件加速接口——这简直就是为嵌入式量身定做的。我在做工业相机项目时,曾经对比过其他库,最后还是回到了OpenCV,因为它的API设计太成熟了,踩坑成本低。
安装方式(以Ubuntu 20.04为例)
有两种主流安装方式:apt直接安装和源码编译。我建议初学者先用apt,省时间。
# 方式一:apt安装(快速,但版本较旧)
sudo apt update
sudo apt install libopencv-dev python3-opencv
# 方式二:源码编译(推荐,可定制模块)
git clone https://github.com/opencv/opencv.git
cd opencv
mkdir build && cd build
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D WITH_GTK=ON \
-D WITH_FFMPEG=ON ..
make -j4
sudo make install
3.2 图像读取与显示
图像读取是视觉处理的第一步。OpenCV用imread()读取图像,用imshow()显示图像。这里有个坑:OpenCV默认读取的是BGR格式,不是我们熟悉的RGB。很多新手在这里翻车,包括我当年第一次做颜色识别时,死活找不到红色区域,后来才发现是通道顺序搞反了。
#include <opencv2/opencv.hpp>
using namespace cv;
int main() {
// 读取图像,IMREAD_COLOR表示彩色图
Mat img = imread("test.jpg", IMREAD_COLOR);
if (img.empty()) {
printf("图像读取失败!检查路径\n");
return -1;
}
// 显示图像
imshow("Display Window", img);
// 等待按键,参数0表示无限等待
waitKey(0);
// 保存图像
imwrite("output.jpg", img);
return 0;
}
读取模式详解:
| 标志位 | 含义 | 适用场景 |
|---|---|---|
| IMREAD_COLOR | 彩色图(默认) | 通用场景 |
| IMREAD_GRAYSCALE | 灰度图 | 模板匹配、边缘检测 |
| IMREAD_UNCHANGED | 包含Alpha通道 | 透明图像处理 |
3.3 色彩空间转换
色彩空间转换是图像预处理的关键步骤。OpenCV提供了cvtColor()函数,支持超过150种转换方式。但实际项目中,我们最常用的就三种:BGR↔GRAY、BGR↔HSV、BGR↔RGB。
为什么需要HSV? 因为RGB受光照影响太大。你想想看,同一个红色物体,在强光下可能变成粉红色,在暗光下变成暗红色。但HSV中的H(色调)分量基本不变。我在做工业零件分拣时,就是靠HSV空间稳定识别不同颜色的螺丝。
Mat bgr_img = imread("color.jpg");
Mat gray_img, hsv_img;
// BGR转灰度
cvtColor(bgr_img, gray_img, COLOR_BGR2GRAY);
// BGR转HSV
cvtColor(bgr_img, hsv_img, COLOR_BGR2HSV);
// 提取红色区域(H范围:0-10 和 160-180)
Mat mask;
inRange(hsv_img, Scalar(0, 50, 50), Scalar(10, 255, 255), mask);
🔑 核心要点: HSV的H范围是0-180(OpenCV中),不是0-360。S和V范围是0-255。这个细节我至少被问过10次,每次都是因为H范围设错了导致识别失败。
3.4 ROI区域提取
ROI(Region of Interest)就是感兴趣区域。说白了,就是只处理图像中我们关心的那一小块,其他部分直接忽略。这在嵌入式平台上特别重要——处理整张1080p图像可能需要50ms,但只处理一个200x200的ROI,可能只需要2ms。
两种提取方式:
// 方式一:使用Rect矩形区域
Mat img = imread("scene.jpg");
Rect roi_rect(100, 100, 200, 200); // (x, y, width, height)
Mat roi = img(roi_rect);
// 方式二:使用Range范围
Mat roi2 = img(Range(100, 300), Range(100, 300));
// 对ROI进行处理(比如转灰度)
Mat roi_gray;
cvtColor(roi, roi_gray, COLOR_BGR2GRAY);
// 注意:roi和img共享数据内存!
// 修改roi会直接影响原图
rectangle(img, roi_rect, Scalar(0, 255, 0), 2);
clone()做深拷贝:Mat roi_copy = img(roi_rect).clone();。我曾经在项目中因为这个问题,调试了整整一个下午才发现是浅拷贝导致的bug。
3.5 实战小技巧
最后分享几个我在嵌入式项目中积累的经验:
- 图像尺寸检查: 每次读取图像后,先检查
img.empty()和img.size()。嵌入式设备的文件系统可能不稳定,图片损坏是常有的事。 - 内存管理: 及时释放不再使用的Mat对象。虽然OpenCV有引用计数,但在循环中创建大量临时Mat会导致内存碎片。我习惯在关键循环里手动调用
release()。 - 调试技巧: 在开发板上没有显示器时,可以用
imwrite()保存中间结果到SD卡,然后拷贝到电脑上查看。这个方法虽然土,但非常有效。 - 性能优化: 尽量使用
UMat(通用内存)来利用OpenCL加速。在支持GPU的嵌入式平台(如Jetson系列)上,性能提升非常明显。
好了,这一章的内容就到这里。记住,OpenCV的基础操作就像练武的扎马步,看似简单,但决定了你后续所有算法实现的稳定性。下一章我们会讲图像预处理,包括滤波、直方图均衡化等,这些都是模板匹配的前置步骤。咱们下节课见。
公众号:蓝海资料掘金营,微信deep3321