RK3568开发板OpenCV 4.5.5交叉编译与Qt接入避坑指南
简介:这份《ATK-DLRK3568_OpenCV4使用手册》面向具备Linux基础、希望把计算机视觉程序部署到嵌入式板卡上的开发者,围绕正点原子ATK-DLRK3568(四核A55@2.0GHz、默认Buildroot、OpenCV 4.5.5)讲解OpenCV4应用的编写、交叉编译与运行。压缩包仅含1个PDF文件,约1.41MB,篇幅集中、查阅方便。内容从OpenCV4概念与最小示例入手,依次覆盖交叉编译工具链安装,C++版本在命令行、Makefile、CMakeLists和Qt工程中的组织方式,Python版本的调用方法,并附常见问题排查。读者可据此搭建可复用的编译环境,理解不同构建方式的差异,掌握Qt项目集成OpenCV的配置要点,并在遇到库路径、编译选项等问题时快速定位。已有120人学习,适合作为嵌入式视觉入门的案头手册。
1. 先对齐版本再动手:RK3568 板子上那份 OpenCV 4.5.5 到底在哪
有个开发者把 C++ 版 OpenCV 程序在 Ubuntu 上编译得干干净净,scp 拷到板子上,一执行就报error while loading shared libraries。查了一下午,问题不在代码,而在他默认“板子上肯定没装 OpenCV,得自己从源码编一份”。实际上出厂 Buildroot 镜像里已经放好了一份 OpenCV 4.5.5,运行库在/usr/lib,头文件在交叉工具链 sysroot 的/usr/include/opencv4下,Python 的cv2接口也一并打包好了,连 freetype 字体渲染都在里面。
嵌入式开发做计算机视觉,最省事的路径往往是最不好找的那条:不重新编译 OpenCV,而是把手上的交叉编译工具链和板子里的现成库对齐。这也是交叉编译 OpenCV 程序最容易踩坑的地方——工具链版本、sysroot 路径、链接库名,三样里有一样对不上,编译阶段一路绿灯,运行时才报错。这套环境适合手上已有 ATK-DLRK3568(四核 A55@2.0GHz)开发板、想跑 C++ 或 Python 视觉程序的开发者,也适合要把 OpenCV 接进 Qt 工程的人。
2. 环境清点与交叉编译工具链安装:三个路径别写混
交叉编译这件事,麻烦从来不在敲哪条命令,而在于“哪套头文件配哪个编译器”。主机上如果同时装过 x86 的 OpenCV、装过别的板子的工具链,环境变量一乱,编译器就会悄悄拿主机库去链接,编出来的二进制在板子上必然跑不起来。所以装之前先把三件事钉死:编译器绝对路径、sysroot 绝对路径、板端库的实际位置。三样都用绝对路径写,不依赖 PATH,也不依赖pkg-config的默认搜索顺序。
2.1 主机与板端的版本对照
先把对照关系列出来,后面所有命令都从这里取路径。文档里关于系统范围有一句很关键的话:所有内容建立在 Buildroot 出厂系统之上,Debian 和 Yocto 不在说明范围内。这句话决定了你不能拿 Debian 根文件系统上的库路径去套。
| 项目 | 主机侧(Ubuntu 20.04) | 板端(Buildroot 出厂系统) |
|---|---|---|
| 架构 | x86_64 | aarch64(armv8) |
| 编译器前缀 | aarch64-buildroot-linux-gnu- | 无(不需要) |
| 工具链安装根 | /opt/atk-dlrk356x-toolchain | 无 |
| OpenCV 版本 | 由 sysroot 提供,与板端一致 | 4.5.5 |
| 头文件 | …/sysroot/usr/include/opencv4 | 同左(打包进镜像) |
| 运行库 | …/sysroot/usr/lib | /usr/lib |
| Python 接口 | 不需要 | 板端python3+cv2 |
有一个细节必须提前说:工具链的安装目录名,在随板资料的不同章节里出现过atk-dlrk3568-toolchain和atk-dlrk356x-toolchain两种写法。前者是.run安装包默认落地的目录,后者是示例命令里用的目录。它们指的不是两个东西,但你照抄命令时一旦写成一种、实际装成另一种,后面每一条编译命令都会报No such file or directory。常见做法是安装时接受默认路径,装完ls /opt看一眼实际目录名,然后把后续所有配置统一成那一个名字。我一般会把实际路径记在工程的README里,避免几天后自己都记不清。
2.2.run工具链的安装步骤与路径确认
工具链来自随板资料光盘里的交叉编译工具目录,文件名形如atk-dlrk3568-toolchain-arm-buildroot-linux-gnueabihf-x86_64-(版本号).run。它不是通用的 Linaro 工具链,而是从这块板子的 SDK 里用 Buildroot 生成后打包出来的,好处是不用编 SDK 也能直接拿来开发。这个包用 WinScp 之类的工具传到 Ubuntu 上,落地位置随意,我习惯放在~/toolchain/下。
# 1. 进入安装包所在目录 cd ~/toolchain # 2. 补上可执行权限,.run 包默认不带 x 位 chmod 777 atk-dlrk3568-toolchain-arm-buildroot-linux-gnueabihf-x86_64-*.run # 3. 执行安装,交互过程中会问安装路径 ./atk-dlrk3568-toolchain-arm-buildroot-linux-gnueabihf-x86_64-*.run执行后会进入交互式安装。第一步是询问安装路径,默认是/opt/atk-dlrk3568-toolchain;这里建议直接回车接受默认值,理由不是“官方推荐”,而是后面所有示例、Makefile、CMakeLists里的路径都基于它,你改成别的路径并不会报错,只会在每次复制别人配置时多一道手工替换。第二步输入y或Y确认,安装过程大约一到两分钟,速度取决于磁盘。装完不要急着往下走,先做下面三件事。
安装路径不要带空格、不要放在中文目录下。.run解压出来的脚本和pkgconfig里的路径是按字面拼的,路径里有空格时,--sysroot=后面的参数会在空格处被 shell 截断,报出来的错是“找不到某个 .o 文件”,跟实际原因完全对不上。
2.3 装完三处自检:编译器、sysroot、opencv4 头文件
这一步很多人跳过,然后在第四章花两小时排查。三处自检分别对应后面编译命令里的三个参数来源。
# 自检一:编译器能不能跑、版本对不对 /opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-g++ --version # 自检二:sysroot 里的 opencv4 头文件在不在 ls /opt/atk-dlrk356x-toolchain/aarch64-buildroot-linux-gnu/sysroot/usr/include/opencv4 # 自检三:链接库和 pkgconfig 描述文件在不在 ls /opt/atk-dlrk356x-toolchain/aarch64-buildroot-linux-gnu/sysroot/usr/lib | grep opencv ls /opt/atk-dlrk356x-toolchain/aarch64-buildroot-linux-gnu/sysroot/usr/lib/pkgconfig | grep opencv4第一条要能看到 GCC 的版本输出,说明工具链本身解压完整、没有缺动态库。第二条如果输出opencv2目录,说明头文件齐了,注意这一层是opencv4而不是opencv2——源码里的写法是#include <opencv4/opencv2/opencv.hpp>,两段路径都要对上。第三条重点是libopencv_core.so这类库文件,以及opencv4.pc,后者决定了 Qt 那套pkgconfig方案能不能用。
三处只要有任意一处空目录,就不要继续往第三章走。空目录通常意味着两件事之一:.run安装被中断过,或者你正在看的 sysroot 不是刚装的那份工具链的 sysroot(比如系统里还留着一份别的板子的工具链)。重新跑一次安装并接受默认路径,比对着半份 sysroot 猜要快得多。
3. C++ 程序两种编译法:命令行手写与 Makefile 固化
程序本身很短,短到你会觉得“这也要写一章”。但嵌入式交叉编译的问题从来不发生在代码里,而发生在“这四个参数分别对应什么”。命令行版本的价值是让你看清每个参数;Makefile 版本的价值是让你不用每次重敲。两版都要走过一遍,跳过命令行直接抄 Makefile 的人,一旦 Makefile 报错就完全无从下手。
3.1 先写一个最小的 OpenCV 程序
在 Ubuntu 上建一个工作目录opencv,里面放opencv_test.cpp。程序干的事很简单:读一张图、开窗、显示、等按键。
#include <iostream> #include <opencv4/opencv2/opencv.hpp> int main() { // 读板端本地图片,换成自己的绝对路径即可 cv::Mat image = cv::imread("/opt/apps/src/logo/test_logo.png"); // 读失败必须拦一下,否则后面 imshow 会抛异常 if (image.empty()) { std::cout << "无法读取图片文件" << std::endl; return -1; } cv::namedWindow("Image", cv::WINDOW_AUTOSIZE); cv::imshow("Image", image); cv::waitKey(0); // 阻塞等按键,0 表示无限等待 cv::destroyAllWindows(); return 0; }两个和主机开发不一样的点。第一,图片路径必须是板端的绝对路径,因为程序跑在板子上,imread读的是板子文件系统;在主机上写./test_logo.png编出来的程序拷过去一定读不到。第二,WINDOW_AUTOSIZE在嵌入式 Weston 桌面上偶尔会撑满或超出可视区,换成WINDOW_NORMAL至少允许你手动缩放,调界面的时候省事。Python 版本逻辑一模一样,只是不需要编译。
3.2 命令行编译:把-I -L -l逐个拆开
这是整套流程的核心一条命令。注意它是一行,反斜杠只是换行。
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-g++ \ opencv_test.cpp -o opencv_test \ --sysroot=/opt/atk-dlrk356x-toolchain/aarch64-buildroot-linux-gnu/sysroot \ -I /opt/atk-dlrk356x-toolchain/aarch64-buildroot-linux-gnu/sysroot/usr/include/opencv4 \ -L /opt/atk-dlrk356x-toolchain/aarch64-buildroot-linux-gnu/sysroot/usr/lib \ -lopencv_core -lopencv_highgui -lopencv_imgproc \ -lopencv_videoio -lopencv_imgcodecs逐段说清。第一行用的是工具链里的g++,C 程序换成gcc,两者不能混。-o opencv_test指定输出文件名,不带路径就落在当前目录。--sysroot=是这次交叉编译的关键,它告诉编译器“所有头文件和库都在这个根下面找”,这样即使主机上装了 x86 的 OpenCV,也不会被误链进来。
-I是头文件搜索路径,指到.../usr/include/opencv4这一层。为什么是这一层而不是.../usr/include?因为源码里写的是<opencv4/opencv2/opencv.hpp>,编译器要在 include 根下面找到opencv2才能拼出完整路径。有人习惯写-I.../usr/include并在代码里改成#include <opencv2/opencv.hpp>,两种写法等价,但不要混用,混用的结果就是opencv2/opencv.hpp: No such file。
-L指链接库路径,--sysroot已经隐式包含了它,显式再写一遍是为了防止个别版本的工具链在链接阶段不认 sysroot。-l后面跟的是库名,不带lib前缀和.so后缀,-lopencv_core对应libopencv_core.so。库名不是猜的,去.../sysroot/usr/lib下ls | grep opencv看实际有哪些,用到哪个加哪个。
编译完成后,通过 scp 或 adb 把opencv_test传到板子上执行。执行前记得chmod +x opencv_test,从 Windows 或某些传输方式过来的文件会丢可执行位,报的是Permission denied,看着像权限问题,其实是传输过程丢位。
3.3 Makefile 版本:把路径收敛成变量
命令行跑通之后,把所有绝对路径提成变量写进Makefile。这样换工具链版本时只需要改一处。
# 工具链根目录,改这一行就够了 TOOLCHAIN_DIR := /opt/atk-dlrk356x-toolchain SYSROOT := ${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu/sysroot INC := ${SYSROOT}/usr/include GXX := ${TOOLCHAIN_DIR}/bin/aarch64-buildroot-linux-gnu-g++ # 头文件搜索路径,opencv4 单独列一行,避免和 sysroot 混在一起 CFLAGS := --sysroot=${SYSROOT} -I${INC} -I${INC}/opencv4 # 只链真正用到的模块,链多了会增加板端加载时间 LD_FLAGS := -L${SYSROOT}/usr/lib \ -lopencv_core -lopencv_highgui -lopencv_imgproc \ -lopencv_videoio -lopencv_imgcodecs all: $(GXX) opencv_test.cpp $(CFLAGS) $(LD_FLAGS) -o opencv_test @echo "Build Done ..." clean: rm -f opencv_test三个变量对应上一节的三个参数:TOOLCHAIN_DIR是工具链根,SYSROOT是--sysroot的值,INC是-I的父目录。写Makefile时$(GXX)和$(CFLAGS)之间的那行缩进必须是Tab,不是四个空格。这是 Makefile 的老坑,报错是missing separator,看字面完全猜不到是缩进问题。
在目录里直接make编译,make clean清掉产物。每次改完代码,先make clean再make,不要指望 Makefile 的依赖判断在交叉编译场景下永远正确——头文件换过版本、库路径挪过位置这类变化它感知不到,留着旧.o编出来的东西行为会很诡异。
4. CMake 与 Qt 工程接入 OpenCV:不手写库路径的两种做法
Makefile能用之后,问题会变成“工程里有十几个源文件怎么办”。CMake 的价值是把编译器、标准、链接库都写成声明,不用维护一长串命令;Qt 的.pro则是另一套思路,靠pkgconfig让 Qt 自己去问 OpenCV 要路径。两条路都能走通,区别在于你想不想在工程里看到绝对路径。
4.1 CMakeLists 里的交叉编译四件套
在源码目录建CMakeLists.txt。交叉编译场景下,这个文件比常规 CMake 多了四行“告诉 CMake 用哪个编译器、哪个根”。
cmake_minimum_required(VERSION 3.8) # 四件套之一:工具链根目录 set(TOOLCHAIN_DIR /opt/atk-dlrk356x-toolchain) set(SYSROOT ${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu/sysroot) # 四件套之二:显式指定 C/C++ 编译器,必须在 project() 之前 set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/aarch64-buildroot-linux-gnu-g++) set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/aarch64-buildroot-linux-gnu-gcc) # 四件套之三:sysroot,等价于命令行的 --sysroot set(CMAKE_SYSROOT ${SYSROOT}) # 四件套之四:C++ 标准,OpenCV 4.5.5 用 C++11 足够 set(CMAKE_CXX_STANDARD 11) include_directories(${SYSROOT}/usr/include) include_directories(${SYSROOT}/usr/include/opencv4) set(OPENCV_LIBS opencv_core opencv_highgui opencv_imgproc opencv_videoio opencv_imgcodecs) project(opencv) add_executable(opencv_test opencv_test.cpp) target_link_libraries(opencv_test ${OPENCV_LIBS})顺序很讲究。set(CMAKE_CXX_COMPILER ...)必须写在project()之前,写在后面 CMake 已经在配置阶段选定编译器了,这行会被忽略,编出来的仍然是 x86 二进制,而 CMake 不会给任何警告。这也是判断“我到底交叉编译成功没有”的第一道关:用file opencv_test看输出里是不是ARM aarch64,是x86-64就说明编译器没切过去。
include_directories里两行都要留着。第一行给--sysroot之外可能被引用的系统头,第二行专门给opencv4这一层。target_link_libraries用变量传库名列表,不要在这里写-l,CMake 会自己补前缀后缀。
4.2 build 目录、缓存与重新配置
如果目录里已经有Makefile(上一章留下的),CMake 会冲突。标准做法是另建build目录。
mkdir -p build cd build cmake .. # 依据上一级 CMakeLists.txt 生成 Makefile 等文件 make # 编译 make clean # 清理产物,不删 Makefilecmake ..会在build里生成CMakeCache.txt,这个文件缓存了上次探测到的编译器路径和库路径。改了CMakeLists.txt里的TOOLCHAIN_DIR之后,光重新make是不够的,缓存里还是旧路径,症状是新代码编出来行为没变。正确做法是删掉build目录整个重来,或者cmake ..时带-D覆盖。我一般直接rm -rf build && mkdir build && cd build && cmake ..,比研究缓存依赖快。
4.3 Qt 的.pro用 pkgconfig 接 OpenCV4
Qt 工程走的是另一条线,前提是 Qt 的交叉编译环境本身已经配好(工具链的qmake已经能编出板端程序)。在这个前提下,接 OpenCV 只需要两行。
QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++17 # 关键两行:开启 pkgconfig,并声明依赖 opencv4 CONFIG += link_pkgconfig PKGCONFIG += opencv4link_pkgconfig让 qmake 在构建时调用pkg-config,PKGCONFIG += opencv4指定去问opencv4.pc。Qt 拿到.pc里的Cflags和Libs之后自动补 include 和 link 参数,工程里不用出现任何绝对路径。这也是 2.3 节要检查pkgconfig/opencv4.pc是否存在的原因——那个文件不在,这两行就只是两行字。
代码侧只要在用到 OpenCV 的源文件里包含头文件即可,注意路径前缀和命令行版本保持一致:
#include "mainwindow.h" #include "ui_mainwindow.h" #include <opencv4/opencv2/opencv.hpp> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); // 仅验证能否链接成功,不做实际显示 cv::Mat image = cv::imread("/opt/apps/src/logo/test_logo.png"); Q_UNUSED(image); }Q_UNUSED是给编译器的信号:这个变量暂时不用,别报未使用警告。验证阶段这样就够,能编过就说明头文件和库都接上了,功能再往后加。构建用 Qt Creator 的 Ctrl+B,或者命令行qmake && make,构建成功的标志是链接阶段没有undefined reference to cv::...。
5. 避坑与排查:交叉编译里最容易翻车的五件事
下面五条都是我在装这套环境时真实撞过的,按撞到的频率排序。每条都按“现象 → 原因 → 解决”写,照着对号入座比从头读文档快。
第一条:编译通过,板端执行报error while loading shared libraries: libopencv_core.so.4.5。
现象是主机上make一路绿灯,scp 到板子上就报缺库。原因不是库没编进去,而是板端/usr/lib里的 OpenCV 版本和工具链 sysroot 里的不一致,或者程序链接的是某个板端没打包的模块(比如opencv_video)。解决分两步:先在板端ls /usr/lib | grep opencv看清楚实际有哪些.so,再把编译命令里的-l列表裁剪成板端真实存在的那些;确认版本用strings /usr/lib/libopencv_core.so.4.5 | grep 4.5。这一步不要靠猜,两边列出来对照最快。
第二条:所有命令都报No such file or directory,但目录明明存在。
现象是aarch64-buildroot-linux-gnu-g++: No such file or directory,ls过去又能看到文件。原因有两个:一是路径写成了atk-dlrk3568-toolchain,实际装的是atk-dlrk356x-toolchain,这两个名字在不同章节里都出现过;二是.run包没加可执行权限就跑了,安装其实没完成。解决是先ls /opt确认实际目录名,再ls /opt/<实际名>/bin | head确认编译器在;两处都对不上就重装一遍工具链,接受默认路径。
第三条:fatal error: opencv2/opencv.hpp: No such file or directory。
现象是编译第一行就挂。原因是-I指到了.../usr/include而不是.../usr/include/opencv4,但源码里写的是<opencv4/opencv2/opencv.hpp>,两段前缀叠起来就找不到了。解决是统一写法:要么-I.../include/opencv4配#include <opencv2/opencv.hpp>,要么-I.../include配#include <opencv4/opencv2/opencv.hpp>。工程里出现两种混写时,会出现“这个文件能编、那个文件不能编”的诡异现象。
第四条:Qt 构建报Package opencv4 was not found in the pkg-config search path。
现象是.pro里加了PKGCONFIG += opencv4之后构建失败,提示找不到包。原因有两个:sysroot 的usr/lib/pkgconfig下确实没有opencv4.pc;或者 qmake 调用的pkg-config是主机的那份,它去/usr/lib/pkgconfig找,自然找不到交叉编译的包。解决是先ls .../sysroot/usr/lib/pkgconfig | grep opencv4确认文件存在,再给pkg-config设PKG_CONFIG_SYSROOT_DIR和PKG_CONFIG_LIBDIR指向 sysroot。别指望改PKG_CONFIG_PATH就行,PATH类变量会被默认路径一起搜,容易搜到主机那份。
第五条:程序跑起来窗口不显示,或者只出现在屏幕中间一小块。
现象是 HDMI 显示器上 Weston 桌面只占中间区域,OpenCV 的窗口跟着缩在中间。原因不是 OpenCV,是显示输出配置——Weston 默认把某个输出当主屏,HDMI 接了但不是主显示时就会这样。解决在显示配置层面:把 HDMI 设为主屏,或者按屏幕方向旋转 90 度;具体改法在随板的显示相关文档里,跟 OpenCV 编译无关。这一条放在最后是因为它最容易被误判成“OpenCV 装坏了”,实际代码和库都是好的。
6. 装完怎么验证:从file到一帧图像上屏
验证不要只看“有没有报错”。编译通过只能说明头文件和链接参数对了,能不能跑是另一回事。我习惯按四层往下走,任何一层不过就停下来修,不带着问题往下叠。
第一层,看产物架构。在build目录里执行:
file opencv_test # 期望输出里出现:ELF 64-bit LSB, ARM aarch64 # 如果出现 x86-64,说明 CMAKE_CXX_COMPILER 没生效,回到 4.1 检查 set 的位置第二层,看动态依赖有没有交叉污染。
# 用工具链的 readelf 看依赖,不要用主机的 ldd /opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-readelf -d opencv_test | grep NEEDED # 期望看到 libopencv_core.so.4.5 等,且不应出现主机路径第三层,板端库版本对账。把可执行文件传到板子上,先别急着跑:
chmod +x opencv_test ls /usr/lib | grep opencv # 板端实际有的库 python3 -c "import cv2; print(cv2.__version__)" # Python 侧版本,应为 4.5.5两边的版本号一致,才说明工具链 sysroot 和板端镜像是配套的。第三层不过,说明这套工具链根本不是这块板子镜像对应的那份,后面所有编译都是白费。
第四层,跑起来看画面。./opencv_test执行后应该弹出一个窗口显示图片,按任意键关闭。能显示就全通了;报缺库回到第五章第一条;窗口位置不对回到第五章第五条。Python 版本更省事,把.py拷过去python3 opencv_test.py直接跑,不需要编译,也因此它更适合当“板端环境和图片路径对不对”的探针——Python 能跑而 C++ 不能跑,问题一定在编译环节,不在环境。
| 验证层 | 命令 | 通过标准 | 失败时先查 |
|---|---|---|---|
| 架构 | file opencv_test | 出现 ARM aarch64 | CMAKE_CXX_COMPILER位置 |
| 依赖 | readelf -d | 只有交叉库,无主机路径 | --sysroot/-L |
| 版本 | 板端ls /usr/lib+cv2.__version__ | 均为 4.5.5 | 工具链与镜像是否配套 |
| 运行 | ./opencv_test | 窗口显示图片 | 库缺失 / 图片路径 / 显示配置 |
有一个我踩过的顺序坑值得提一句:我曾经在 Python 版本还没跑通的情况下,直接去调 Qt 的pkgconfig,结果花了一个晚上在.pro和.pc文件之间来回改,最后发现是板端镜像的 OpenCV 版本和手上的工具链差了半个小版本。从那以后我每次配这套环境,都强制先跑一遍python3 -c "import cv2; print(cv2.__version__)"和file opencv_test这两条——一个查板端,一个查编译产物,两条命令十几秒,能省掉后面几小时的黑匣子式排查。希望这套流程能帮到你,少走一遍我走过的弯路。
本文还有配套的精品资源,点击获取