1. 为什么在SLAM十四讲的环境里,OpenCV 3.4.16值得单独折腾
如果你正在跟着《视觉SLAM十四讲》做练习,大概率会遇到一个很具体的问题:书上的代码依赖OpenCV 3.x,而你手头是Ubuntu 20.04,系统默认源里能装到的OpenCV版本要么太新(4.x),要么装上了但编译书里的例程时各种报错。这不是你代码写错了,而是版本匹配的问题。
OpenCV 3.4.16是3.4系列的最后一个维护版本,发布于2020年底,正好卡在Ubuntu 20.04 LTS的生命周期内。它既保留了3.x时代稳定的API接口(比如cv::solvePnP、cv::triangulatePoints这些SLAM里高频使用的函数),又修复了早期3.4.x版本中一些与C++11/14标准库兼容性的问题。对于SLAM十四讲这种大量使用特征点匹配、PnP求解、三角化、光流跟踪的教程来说,3.4.16是一个“不折腾”的选择。
但问题在于,Ubuntu 20.04的默认仓库里没有这个版本。你apt install libopencv-dev装到的是4.2.x,而4.x版本移除了一些3.x的旧接口,比如CV_LOAD_IMAGE_UNCHANGED这类宏定义被改了名字,cv::xfeatures2d模块的编译选项也变了。直接换4.x去跑书上的代码,轻则编译报错,重则运行时段错误,排查起来非常消耗时间。
所以这篇内容的核心就是:在Ubuntu 20.04上,从源码编译安装OpenCV 3.4.16,并且让它和SLAM十四讲的环境(Eigen、Sophus、Ceres、g2o)和平共处。我会把整个流程拆开,包括依赖安装、CMake参数选择、编译踩坑、环境变量配置,以及装完之后怎么验证、怎么和ROS共存(如果你后续要跑ORB-SLAM2之类的项目)。适合已经装好Ubuntu 20.04、准备开始SLAM十四讲实践、但被OpenCV版本卡住的读者。
2. 安装前的整体思路与版本选型逻辑
2.1 为什么不直接用apt装OpenCV
Ubuntu 20.04的libopencv-dev对应的是OpenCV 4.2.0。这个版本本身很稳定,但和SLAM十四讲第二版书上的代码存在几个硬性冲突。最典型的是cv::FileStorage读取cv::Mat时的类型检查更严格,书里有些示例用>>直接读矩阵,在4.x下会抛异常。另外,cv::viz模块在4.x里对VTK的依赖版本要求更高,而SLAM十四讲第13章的可视化部分用的是3.x的写法。
还有一个容易被忽略的点:如果你后续要编译ORB-SLAM2或者VINS-Mono这类开源SLAM系统,它们中的大多数在2020年前后锁定的就是OpenCV 3.4.x。用4.x去编译,CMakeLists里的find_package(OpenCV 3.4 REQUIRED)直接找不到,改版本号又可能遇到API不兼容。所以从源码装3.4.16,本质上是在为整个SLAM学习链路铺一条兼容性更好的路。
2.2 源码编译 vs 预编译包
有人会想去找现成的.deb包或者第三方PPA。我的建议是:对于OpenCV 3.4.16,源码编译虽然慢(取决于机器性能,大概20到40分钟),但可控性最强。你可以精确控制哪些模块编译、哪些不编译,比如xfeatures2d(SLAM十四讲里SIFT/SURF相关章节会用到)、viz(可视化)、cuda(如果你有NVIDIA显卡且想加速)。预编译包往往把这些模块拆得七零八落,装完还得补。
另外,源码编译可以指定安装路径。我习惯把OpenCV 3.4.16装到/usr/local/opencv3.4.16,而不是默认的/usr/local。这样做的好处是:系统里可以同时存在多个OpenCV版本,通过CMake的OpenCV_DIR变量切换,不会和apt装的4.x打架。后面我会详细说这个路径怎么配。
2.3 依赖项的选择与避坑
编译OpenCV 3.4.16需要一堆依赖。Ubuntu 20.04的软件源里大部分都有,但有几个版本敏感项需要注意:
- CMake:要求3.5以上,Ubuntu 20.04自带3.16,够用。
- GCC:Ubuntu 20.04默认是GCC 9,OpenCV 3.4.16对GCC 9的兼容性还行,但编译时会有一些警告,不影响。
- Python:如果你要用Python接口,注意OpenCV 3.4.16不支持Python 3.9以上的版本(Ubuntu 20.04默认是3.8,正好)。但如果你自己升级过Python,需要额外指定
PYTHON3_EXECUTABLE。 - VTK:
viz模块依赖VTK。Ubuntu 20.04源里的VTK是7.1,OpenCV 3.4.16对VTK 7的支持是OK的,但如果你装了VTK 9,编译会报错。所以不要随便升级VTK。 - Eigen:SLAM十四讲本身就要装Eigen 3.3以上,OpenCV编译时也会用到。建议先装Eigen,再装OpenCV。
还有一个隐藏坑:libjasper-dev在Ubuntu 20.04的默认源里被移除了,但OpenCV 3.4.16的imgcodecs模块默认会找它。解决办法是要么加一个旧源(不推荐),要么在CMake里关掉Jasper支持(-DWITH_JASPER=OFF)。JPEG 2000格式在SLAM里基本用不到,关掉完全没影响。
3. 从零开始的完整编译安装实操
3.1 依赖安装:一条命令搞定大部分
先把基础依赖装齐。打开终端,执行:
sudo apt update sudo apt install -y build-essential cmake git pkg-config \ libgtk-3-dev libavcodec-dev libavformat-dev libswscale-dev \ libv4l-dev libxvidcore-dev libx264-dev libjpeg-dev libpng-dev \ libtiff-dev gfortran openexr libatlas-base-dev python3-dev \ python3-numpy libtbb2 libtbb-dev libdc1394-22-dev这些包覆盖了图像编解码、视频流、并行计算(TBB)、Python绑定等核心需求。其中libdc1394-22-dev是用于工业相机的,SLAM里如果接双目相机可能会用到。
接下来单独处理几个容易出问题的:
sudo apt install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-devGStreamer用于视频流处理,ORB-SLAM2里读取视频文件时会依赖。
然后装Eigen(如果还没装):
sudo apt install -y libeigen3-devUbuntu 20.04源里的Eigen是3.3.7,满足SLAM十四讲的要求。
注意:不要装
libopencv-dev,否则后面CMake找OpenCV时会优先找到4.x版本,导致混乱。如果你已经装了,可以暂时不管,后面通过指定路径来覆盖。
3.2 下载OpenCV 3.4.16源码
从官方仓库拉取指定版本的源码。这里用git clone加checkout的方式,比下载zip包更可靠,因为可以随时切换分支。
cd ~ git clone https://github.com/opencv/opencv.git cd opencv git checkout 3.4.16如果你还需要contrib模块(SLAM十四讲里SIFT、SURF、ORB的扩展实现都在这里),继续:
cd ~ git clone https://github.com/opencv/opencv_contrib.git cd opencv_contrib git checkout 3.4.16提示:
opencv_contrib的版本必须和主仓库完全一致,否则CMake配置阶段会报错。3.4.16对应的是3.4.16标签,不要用3.4分支,因为分支会随时间更新。
3.3 CMake配置:参数逐个拆解
在opencv目录下创建build文件夹,然后运行cmake。这一步是整个安装过程中最关键的,参数选错了后面编译会出各种幺蛾子。
cd ~/opencv mkdir build && cd build下面是我实测稳定的一套CMake参数:
cmake -D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_INSTALL_PREFIX=/usr/local/opencv3.4.16 \ -D INSTALL_C_EXAMPLES=OFF \ -D INSTALL_PYTHON_EXAMPLES=OFF \ -D OPENCV_GENERATE_PKGCONFIG=ON \ -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \ -D WITH_CUDA=OFF \ -D WITH_VTK=ON \ -D WITH_JASPER=OFF \ -D WITH_OPENCL=ON \ -D WITH_TBB=ON \ -D WITH_EIGEN=ON \ -D BUILD_EXAMPLES=OFF \ -D BUILD_opencv_python3=ON \ -D PYTHON3_EXECUTABLE=/usr/bin/python3 \ -D PYTHON3_INCLUDE_DIR=/usr/include/python3.8 \ -D PYTHON3_NUMPY_INCLUDE_DIRS=/usr/lib/python3/dist-packages/numpy/core/include \ -D PYTHON3_PACKAGES_PATH=/usr/lib/python3/dist-packages \ ..逐个解释这些参数背后的考量:
CMAKE_INSTALL_PREFIX=/usr/local/opencv3.4.16:自定义安装路径,避免覆盖系统默认的OpenCV。后面通过OpenCV_DIR来指定这个路径。OPENCV_GENERATE_PKGCONFIG=ON:生成opencv3.pc文件,方便用pkg-config查询编译参数。Ubuntu 20.04下这个选项默认是OFF,必须手动打开。OPENCV_EXTRA_MODULES_PATH:指向opencv_contrib的modules目录,这样才会编译xfeatures2d等扩展模块。WITH_CUDA=OFF:如果你的机器没有NVIDIA显卡,或者你不想折腾CUDA,关掉。SLAM十四讲的前端部分用CPU足够。WITH_VTK=ON:开启viz模块,第13章的可视化需要。WITH_JASPER=OFF:前面说的,避开libjasper-dev缺失的问题。WITH_OPENCL=ON:OpenCL可以在部分图像处理操作上加速,即使没有独显,Intel核显也支持。PYTHON3_EXECUTABLE等:明确指定Python 3.8的路径,避免CMake找到其他版本的Python。
注意:如果你的
opencv_contrib不在~/opencv_contrib,把OPENCV_EXTRA_MODULES_PATH改成实际路径。另外,PYTHON3_NUMPY_INCLUDE_DIRS的路径可能因系统而异,可以用python3 -c "import numpy; print(numpy.get_include())"来确认。
3.4 编译与安装:时间和线程数的权衡
CMake配置成功后,会输出一个摘要,重点看OpenCV modules里有没有xfeatures2d、viz、python3。确认无误后开始编译:
make -j$(nproc)$(nproc)会自动获取CPU核心数。比如8核机器就是make -j8。但这里有个经验:如果你的内存小于8GB,-j8可能会导致编译过程中内存不足而卡死。我试过在4GB内存的虚拟机上用-j4,编译到opencv_contrib的xfeatures2d模块时直接OOM。稳妥的做法是:
- 内存8GB以上:
make -j$(nproc) - 内存4-8GB:
make -j4 - 内存4GB以下:
make -j2,虽然慢但不会崩
编译完成后安装:
sudo make install sudo ldconfigldconfig刷新动态链接库缓存,让系统能找到新装的OpenCV库。
3.5 环境变量配置:让CMake和pkg-config找到它
装完之后,系统里其实有两个OpenCV:apt装的4.x在/usr,新装的3.4.16在/usr/local/opencv3.4.16。如果不做配置,CMake默认会找到4.x。有两种方式指定:
方式一:在CMakeLists.txt里写死路径
set(OpenCV_DIR "/usr/local/opencv3.4.16/lib/cmake/opencv4") find_package(OpenCV 3.4 REQUIRED)注意路径里的opencv4是CMake配置文件的目录名,虽然装的是3.4.16,但OpenCV 3.4的CMake配置文件夹就叫opencv4,这是历史遗留,不用改。
方式二:通过环境变量全局指定
在~/.bashrc里加:
export OpenCV_DIR=/usr/local/opencv3.4.16/lib/cmake/opencv4 export LD_LIBRARY_PATH=/usr/local/opencv3.4.16/lib:$LD_LIBRARY_PATH export PKG_CONFIG_PATH=/usr/local/opencv3.4.16/lib/pkgconfig:$PKG_CONFIG_PATH然后source ~/.bashrc。这样用pkg-config --modversion opencv查到的就是3.4.16。
提示:
LD_LIBRARY_PATH这种方式在运行程序时生效,但如果你用sudo运行,环境变量可能不继承。更彻底的方式是在/etc/ld.so.conf.d/下新建一个opencv3.4.16.conf,写入/usr/local/opencv3.4.16/lib,然后sudo ldconfig。
4. 验证安装与SLAM十四讲环境联调
4.1 快速验证OpenCV版本和模块
写一个最小的测试程序:
#include <opencv2/opencv.hpp> #include <iostream> int main() { std::cout << "OpenCV version: " << CV_VERSION << std::endl; cv::Mat img = cv::imread("test.jpg"); if (img.empty()) { std::cout << "Image not found, but OpenCV is working." << std::endl; } cv::Ptr<cv::Feature2D> sift = cv::xfeatures2d::SIFT::create(); std::cout << "SIFT created successfully." << std::endl; return 0; }对应的CMakeLists.txt:
cmake_minimum_required(VERSION 3.5) project(test_opencv) set(OpenCV_DIR "/usr/local/opencv3.4.16/lib/cmake/opencv4") find_package(OpenCV 3.4 REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) add_executable(test_opencv main.cpp) target_link_libraries(test_opencv ${OpenCV_LIBS})编译运行:
mkdir build && cd build cmake .. make ./test_opencv如果输出OpenCV version: 3.4.16并且SIFT创建成功,说明主模块和contrib模块都装好了。
4.2 和Sophus、Ceres、g2o的兼容性检查
SLAM十四讲的环境里,Sophus依赖Eigen,Ceres依赖Eigen和glog,g2o依赖Eigen和OpenCV(可选)。OpenCV 3.4.16和这些库的兼容性总体很好,但有一个点要注意:Ceres如果编译时链接了OpenCV 4.x,而你的SLAM程序链接了3.4.16,可能会出现符号冲突。解决办法是统一用3.4.16,或者在Ceres的CMake配置里关掉OpenCV支持(-DUSE_OPENCV=OFF),因为SLAM十四讲里Ceres主要用于后端优化,不直接依赖OpenCV。
g2o的编译如果开启了G2O_USE_OPENCV,它会去找系统里的OpenCV。这时候通过OpenCV_DIR指定3.4.16即可。
4.3 多版本共存时的切换技巧
如果你同时有OpenCV 4.x(apt装的)和3.4.16(源码装的),在不同项目间切换时,推荐用CMake的find_package指定版本:
find_package(OpenCV 3.4 EXACT REQUIRED)EXACT关键字强制要求精确匹配3.4.x,这样即使系统里有4.x,CMake也会优先找3.4.16。但前提是OpenCV_DIR指向了3.4.16的配置目录。
另一种方式是用update-alternatives管理,但OpenCV的库文件和头文件太多,配置起来繁琐,不如直接改OpenCV_DIR来得快。
5. 常见编译错误与排查速查
5.1 CMake阶段报错
错误1:Could NOT find Jasper
这是最常见的。即使加了-DWITH_JASPER=OFF,有时候CMake还是会去找。解决办法是在CMake参数里再加一个-DCMAKE_DISABLE_FIND_PACKAGE_Jasper=TRUE,彻底禁用。
错误2:No package 'gtk+-3.0' found
说明libgtk-3-dev没装。执行sudo apt install libgtk-3-dev。如果已经装了还报错,检查pkg-config --modversion gtk+-3.0是否能输出。
错误3:Python相关路径找不到
如果你不用Python接口,直接在CMake里加-DBUILD_opencv_python3=OFF,省去麻烦。如果要用,确认PYTHON3_EXECUTABLE指向的是/usr/bin/python3,而不是conda环境里的Python。conda的Python路径和系统Python混用会导致numpy头文件找不到。
5.2 编译阶段报错
错误4:internal compiler error: Killed (program cc1plus)
这是内存不足。降低make -j的并行数,或者临时增加swap空间。我试过在4GB内存的机器上编译,用make -j2加上2GB swap,顺利通过。
错误5:xfeatures2d模块编译失败
通常是因为opencv_contrib的版本和主仓库不匹配。确认两个仓库都checkout 3.4.16。如果还是失败,检查opencv_contrib/modules/xfeatures2d/CMakeLists.txt里是否有对OpenCV版本的硬性检查。
错误6:viz模块报VTK相关错误
Ubuntu 20.04的VTK 7.1和OpenCV 3.4.16的viz模块兼容性还行,但如果报vtkRenderingOpenGL找不到,安装libvtk7-dev和libvtk7-qt-dev。如果还是不行,直接-DWITH_VTK=OFF,放弃viz模块,SLAM十四讲的可视化部分可以用Pangolin替代。
5.3 运行阶段报错
错误7:error while loading shared libraries: libopencv_core.so.3.4
说明ldconfig没生效,或者LD_LIBRARY_PATH没配。执行sudo ldconfig,然后确认/usr/local/opencv3.4.16/lib在/etc/ld.so.conf.d/的配置里。
错误8:程序运行时崩溃,提示cv::xfeatures2d::SIFT::create()未定义
说明xfeatures2d模块没编译进去。检查CMake配置摘要里是否有xfeatures2d,如果没有,确认OPENCV_EXTRA_MODULES_PATH路径正确,并且opencv_contrib已经checkout到3.4.16。
| 错误现象 | 可能原因 | 解决方法 |
|---|---|---|
| CMake找不到Jasper | 系统缺少libjasper-dev | 加-DWITH_JASPER=OFF -DCMAKE_DISABLE_FIND_PACKAGE_Jasper=TRUE |
| 编译时OOM | 内存不足 | 降低make -j并行数,或增加swap |
| SIFT不可用 | contrib模块未编译 | 检查OPENCV_EXTRA_MODULES_PATH和contrib版本 |
| 运行时找不到库 | ldconfig未刷新 | sudo ldconfig,配置/etc/ld.so.conf.d/ |
| Python绑定失败 | Python路径混乱 | 指定PYTHON3_EXECUTABLE,或关闭Python编译 |
提示:每次修改CMake参数后,建议删除
build文件夹重新来,避免缓存干扰。rm -rf build && mkdir build && cd build。
6. 装完之后的一些经验之谈
编译OpenCV 3.4.16这件事,说难不难,但细节特别多。我第一次装的时候,因为没关Jasper,卡了半个小时;第二次因为opencv_contrib版本对不上,xfeatures2d编译失败;第三次因为内存不够,编译到一半进程被kill。后来总结下来,最稳的流程就是:先装齐依赖,确认Eigen和VTK版本,然后CMake参数一次到位,编译时根据内存调整并行数。
还有一个点:如果你后续要跑ORB-SLAM2,它除了OpenCV还依赖Pangolin和DBoW2。Pangolin的安装相对独立,但DBoW2的CMakeLists里会find_package(OpenCV 3.4 REQUIRED),这时候你的OpenCV_DIR配置就派上用场了。我试过用apt装的OpenCV 4.2去编译ORB-SLAM2,改了一堆代码才通过,后来换成3.4.16,原版代码直接编译成功,省了很多事。
最后分享一个小技巧:编译完成后,可以把/usr/local/opencv3.4.16整个目录打包备份。以后如果在其他机器上部署,直接解压到相同路径,配好环境变量就能用,省去重复编译的时间。这个目录大概1.5GB左右,压缩后500MB不到,放在移动硬盘里很方便。