1. 为什么需要交叉编译OpenCV?
在嵌入式Linux开发中,我们经常遇到一个尴尬的局面:开发机是x86架构的PC,而目标板却是ARM架构的嵌入式设备。这就好比你想在树莓派上运行一个图像处理程序,但发现直接在树莓派上编译OpenCV需要6个小时,而且编译过程中还可能因为内存不足而崩溃。这时候交叉编译就派上用场了。
交叉编译的本质是在一种架构的机器上生成另一种架构的可执行代码。具体到OpenCV,我们需要在x86的Ubuntu主机上编译出能在ARM开发板上运行的库文件。这样做有几个明显优势:
- 编译速度快:x86主机的性能远超嵌入式设备,编译OpenCV可能只需要30分钟
- 开发环境友好:可以使用更强大的IDE和调试工具
- 资源占用少:不会占用嵌入式设备宝贵的内存和存储空间
我最近在一个智能摄像头项目中使用RV1106芯片,就遇到了这样的场景。开发板只有512MB内存,直接编译OpenCV几乎不可能完成。通过交叉编译,不仅顺利完成了部署,还能利用PC的强大性能进行快速迭代。
2. 搭建交叉编译环境
2.1 准备编译工具链
交叉编译的第一步是准备好工具链。不同的芯片平台需要不同的工具链,常见的有:
- ARM架构:arm-linux-gnueabihf
- 瑞芯微平台:arm-rockchip830-linux-uclibcgnueabihf
- 全志平台:arm-linux-gnueabi
以瑞芯微RV1106为例,我们需要先获取对应的工具链。通常芯片厂商会提供SDK开发包,里面就包含工具链。如果没有,也可以从Linaro官网下载通用ARM工具链。
安装基本编译工具:
sudo apt-get install cmake cmake-qt-gui cmake-curses-gui2.2 安装依赖库
OpenCV的功能模块众多,依赖的第三方库也不少。在交叉编译前,我们需要在主机上安装这些库的开发版:
# 基础编译工具 sudo apt-get install build-essential git pkg-config # 图像处理相关 sudo apt-get install libjpeg-dev libpng-dev libtiff5-dev libdc1394-22-dev # 视频处理相关 sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev # 优化加速 sudo apt-get install libopenblas-dev libatlas-base-dev gfortran需要注意的是,这些库只是用于编译时的头文件引用,最终生成的OpenCV库不会依赖这些x86版本的库文件。
3. 获取和配置OpenCV源码
3.1 选择合适的OpenCV版本
OpenCV的版本选择很有讲究。最新版本虽然功能多,但对嵌入式设备来说可能过于庞大。我推荐使用3.4.x或4.5.x这些长期支持(LTS)版本,它们在嵌入式平台上有更好的兼容性。
下载源码:
wget https://github.com/opencv/opencv/archive/3.4.16.zip unzip 3.4.16.zip cd opencv-3.4.16 mkdir build install3.2 配置CMake交叉编译参数
进入build目录,使用cmake-gui进行图形化配置:
cd build cmake-gui ..在CMake GUI中需要设置几个关键参数:
- 指定工具链:设置CMAKE_TOOLCHAIN_FILE指向我们的交叉编译配置文件
- 安装路径:设置CMAKE_INSTALL_PREFIX为刚才创建的install目录
- 禁用测试:取消勾选BUILD_TESTS和BUILD_PERF_TESTS
- 模块选择:根据需求启用/禁用特定模块,比如不需要python支持可以禁用PYTHON相关选项
一个典型的rv1103.cmake工具链文件内容如下:
set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) set(tools /path/to/toolchain) set(CMAKE_C_COMPILER ${tools}/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc) set(CMAKE_CXX_COMPILER ${tools}/bin/arm-rockchip830-linux-uclibcgnueabihf-g++) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)4. 编译和安装OpenCV
配置完成后,就可以开始编译了:
make -j$(nproc) make install编译过程可能会遇到各种问题,下面是一些常见问题及解决方案:
问题1:undefined reference to `png_init_filter_functions_neon'解决:在CMake配置中添加:
SET(ARM 1) SET(ENABLE_NEON 1)问题2:对'pthread_create'未定义的引用解决:在CMAKE_EXE_LINKER_FLAGS中添加-lpthread
问题3:operator '&&' has no right operand解决:在报错文件开头添加#define _FILE_OFFSET_BITS 64
编译成功后,install目录下会生成bin、lib、include等目录,这些就是我们需要部署到开发板上的文件。
5. 创建CMake工程并交叉编译应用
5.1 编写测试程序
创建一个简单的OpenCV测试程序gpio.cpp:
#include <opencv2/opencv.hpp> #include <iostream> int main() { cv::Mat image = cv::imread("test.jpg"); if (image.empty()) { std::cout << "无法读取图片" << std::endl; return -1; } cv::Mat grayImage; cvtColor(image, grayImage, cv::COLOR_BGR2GRAY); cv::imwrite("gray.jpg", grayImage); return 0; }5.2 编写CMakeLists.txt
对应的CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.0) project(opencv_demo) # 设置OpenCV路径 set(OpenCV_DIR "/path/to/install/lib/cmake/opencv4") find_package(OpenCV REQUIRED) # 添加可执行文件 add_executable(gpio gpio.cpp) # 链接OpenCV库 target_link_libraries(gpio ${OpenCV_LIBS})5.3 交叉编译应用程序
创建build目录并执行交叉编译:
mkdir build cd build cmake -DCMAKE_TOOLCHAIN_FILE=../rv1103.cmake .. make编译成功后,会在build目录下生成gpio可执行文件。
6. 部署到开发板并调试
6.1 文件传输
将以下文件复制到开发板:
- 编译生成的可执行文件gpio
- install/lib下的所有.so库文件
- 测试图片test.jpg
可以使用scp命令传输:
scp -r gpio install/lib test.jpg root@开发板IP:/home/root6.2 设置库路径
在开发板上设置动态库路径:
export LD_LIBRARY_PATH=/home/root/lib:$LD_LIBRARY_PATH为了让设置永久生效,可以将其添加到/etc/profile文件中。
6.3 运行测试程序
chmod +x gpio ./gpio如果一切正常,程序会读取test.jpg并生成gray.jpg灰度图像。
7. 常见问题排查
在实际部署过程中,可能会遇到各种问题。下面分享几个我踩过的坑:
问题1:运行时提示找不到.so文件解决:检查LD_LIBRARY_PATH是否设置正确,确保所有依赖库都在指定路径
问题2:段错误(Segmentation fault)解决:可能是库版本不匹配,确保开发板上的库版本与编译时一致
问题3:无法打开摄像头解决:交叉编译时需要启用WITH_V4L选项,并确保开发板上有相应的设备节点
问题4:性能低下解决:检查是否启用了NEON加速,在CMake配置中设置ENABLE_NEON=ON
8. 工程化建议
对于实际项目,我建议采用以下工程结构:
project/ ├── app/ # 应用程序代码 ├── lib/ # 第三方库 ├── build/ # 编译目录 ├── scripts/ # 部署脚本 ├── CMakeLists.txt # 主构建文件 └── toolchain.cmake # 交叉编译配置在CMakeLists.txt中可以使用条件判断区分本地编译和交叉编译:
if(CMAKE_CROSSCOMPILING) # 交叉编译特定设置 set(LIBRARY_DIR "${CMAKE_SOURCE_DIR}/lib/arm") else() # 本地编译设置 set(LIBRARY_DIR "${CMAKE_SOURCE_DIR}/lib/x86") endif()对于大型项目,还可以考虑使用CPack生成安装包,或者集成CI/CD自动化流程。