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
⚠️ 注意: 嵌入式平台交叉编译时,记得关闭不必要的模块(如CUDA、VTK),否则编译时间会让你怀疑人生。我曾经在树莓派上编译了整整6个小时...后来学乖了,只编译core、imgproc、highgui这几个核心模块。

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通道 透明图像处理
💡 我的经验: 在嵌入式设备上,尽量用IMREAD_GRAYSCALE读取灰度图。因为彩色图是3通道,灰度图只有1通道,内存占用直接减少2/3。对于模板匹配这种算法,灰度图完全够用,没必要浪费宝贵的DDR带宽。

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);
⚠️ 重要提醒: OpenCV的ROI是浅拷贝!也就是说,roi和原图img指向同一块内存。如果你只想读取ROI而不修改原图,一定要用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