4、OpenCV基础与环境搭建:OpenCV的编译与安装、核心数据结构(Mat)、图像读写与显示、视频流处理
各位同学,欢迎来到第四章。这一章我们聊聊OpenCV。
说实话,OpenCV在SLAM里就像厨师手里的菜刀。你不会天天想着怎么磨刀,但刀好不好用,直接决定你切菜顺不顺。我见过不少同学,算法写得飞起,结果在OpenCV环境配置上卡了两天,心态直接崩了。嗯,咱们今天就把这个坑填平。
4.1 OpenCV的编译与安装
先说说安装。很多人喜欢用apt-get install一键搞定。我个人习惯是——源码编译。为什么?
你想想看,SLAM项目里经常要用到一些非标准模块,比如contrib里的特征匹配、cuda加速。用包管理器装的那个版本,往往缺胳膊少腿。我曾经在项目里因为用了系统自带的OpenCV,结果发现没有SIFT模块,最后不得不重新编译,白白浪费了半天。
所以,我建议你从一开始就养成源码编译的习惯。流程其实不复杂:
# 1. 下载源码
git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git
# 2. 创建构建目录
cd opencv && mkdir build && cd build
# 3. CMake配置(重点)
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D OPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules \
-D WITH_CUDA=ON \
-D WITH_OPENGL=ON \
-D BUILD_EXAMPLES=ON ..
# 4. 编译(-j4根据你的CPU核心数调整)
make -j4
# 5. 安装
sudo make install
ippicv下载失败。我建议你提前把ippicv的包下载好,放到opencv/.cache目录下。不然编译到一半卡住,真的很崩溃。
编译完成后,验证一下:
pkg-config --modversion opencv4
# 输出类似 4.8.0 就对了
4.2 核心数据结构:Mat
Mat是OpenCV的灵魂。说白了,它就是一张图片在内存里的表示方式。
我记得刚学OpenCV时,总搞不清Mat和IplImage的区别。后来踩了个坑才明白——Mat是自动内存管理的,IplImage需要手动释放。在SLAM这种长时间运行的系统里,内存泄漏是致命的。所以,请忘掉IplImage,只用Mat。
Mat的核心结构其实很简单:
| 成员 | 含义 | 举例 |
|---|---|---|
| rows | 行数(高度) | 480 |
| cols | 列数(宽度) | 640 |
| channels() | 通道数 | 3(彩色)或1(灰度) |
| depth() | 像素深度 | CV_8U, CV_32F |
| data | 指向像素数据的指针 | uchar* |
创建Mat的方式有很多种,我常用的就这几种:
// 1. 创建空图
Mat img_empty;
// 2. 创建指定大小的灰度图
Mat img_gray(480, 640, CV_8UC1);
// 3. 创建彩色图并初始化为黑色
Mat img_color(480, 640, CV_8UC3, Scalar(0, 0, 0));
// 4. 从已有数据创建(不拷贝数据,共享内存)
Mat img_roi = img_color(Rect(100, 100, 200, 200));
CV_16UC1(16位无符号),显示时需要转成CV_8UC1。用convertTo()配合缩放因子,比直接遍历像素快得多。
4.3 图像读写与显示
图像读写是基本功。但这里有个细节很多人会忽略——路径问题。
我曾经在调试一个ORB-SLAM的demo时,图像一直加载失败。查了半天,发现是相对路径的问题。程序的工作目录和代码文件不在同一个地方。从那以后,我养成了一个习惯:所有图像路径都用绝对路径,或者用argc/argv传参。
读写代码很简单:
#include <opencv2/opencv.hpp>
using namespace cv;
int main() {
// 读取图像(彩色模式)
Mat img = imread("/home/user/data/left.png", IMREAD_COLOR);
if (img.empty()) {
printf("图像加载失败!检查路径\n");
return -1;
}
// 显示图像
namedWindow("SLAM Left View", WINDOW_NORMAL);
imshow("SLAM Left View", img);
waitKey(0); // 等待按键
// 保存图像
imwrite("/home/user/data/left_copy.png", img);
return 0;
}
这里有个小坑:imread默认是IMREAD_COLOR,会把图像转成BGR三通道。如果你读的是灰度图,记得用IMREAD_GRAYSCALE。不然你后面做特征提取时,会发现维度对不上。
4.4 视频流处理
SLAM里处理视频流是家常便饭。不管是来自摄像头还是录好的视频文件,OpenCV都用VideoCapture搞定。
先看一个从摄像头读取的例子:
VideoCapture cap(0); // 0表示第一个摄像头
if (!cap.isOpened()) {
printf("摄像头打开失败\n");
return -1;
}
Mat frame;
while (cap.read(frame)) {
// 这里可以做特征提取、位姿估计等
imshow("SLAM Live", frame);
// 按'q'退出
if (waitKey(30) == 'q') break;
}
从视频文件读取,就是把cap(0)换成文件路径:
VideoCapture cap("/home/user/data/slam_dataset.mp4");
- 降低分辨率:
cap.set(CAP_PROP_FRAME_WIDTH, 640) - 降低帧率:只处理每3帧中的1帧
- 使用灰度图:减少内存带宽占用
还有一个容易被忽视的问题——时间戳。在SLAM里,每一帧图像的时间戳至关重要。你可以用cap.get(CAP_PROP_POS_MSEC)获取当前帧的时间(毫秒级)。但要注意,这个时间戳是视频文件里的,不是系统实时时间。如果你需要系统时间,记得在cap.read()前后用std::chrono自己打时间戳。
好了,这一章的内容就到这里。OpenCV的基础操作,说白了就是三板斧:读进来、处理完、写出去。但就是这三板斧,在SLAM里要玩得溜,还得靠多练。下一章我们开始讲图像预处理,那是SLAM前端的第一步,也是最重要的一步。