RK3568嵌入式OpenCV4实战:C++/Python交叉编译与Qt集成
简介面向正点原子ATK-DLRK3568开发板的OpenCV4使用手册适合具备Linux基础、希望用C、Python或Qt编写计算机视觉程序的工程师与学习者重点解决开发板上的环境配置、交叉编译和程序运行问题。压缩包内含1个PDF文件约1.41MB体积轻便便于在开发过程中随时查阅。手册基于Buildroot系统与OpenCV 4.5.5依次介绍OpenCV4基本概念、简单示例、交叉编译工具链安装以及C命令行、Makefile、CMakeLists、Qt集成和Python版本程序的编写与编译流程并附常见问题解答。对于需要把视觉程序从主机迁移到开发板的读者文档强调工具链使用顺序与项目配置差异并给出排错思路可帮助按自身语言栈选择学习路径减少环境搭建、编译链接与Qt集成中的试错成本。已有120人学习下载适合作为该开发板视觉应用开发的案头参考。1. 先说清楚这份资料解决的是哪一类麻烦很多人在 RK3568 这类嵌入式板子上第一次跑 OpenCV流程都差不多抄个imread加imshow的 demo交叉编译拷到板子上然后屏幕没反应或者终端里回你一句看得不太懂的报错。这份《OpenCV4 使用手册》的价值就在这儿它把嵌入式开发场景下用 C 和 Python 写计算机视觉程序从工具链安装、命令行编译、Makefile、CMake、Qt 集成到最终在板子上跑出画面整条链路捋了一遍默认系统是 BuildrootOpenCV 版本 4.5.5。它不适合零 Linux 基础的人从头读但如果你手里正好有一块 A55 级别的板子想把自己的视觉算法从 PC 搬到设备上这份资料能省掉不少自己摸索的工夫。2. 环境底座出厂系统里有什么、工具链怎么落地嵌入式做视觉第一步在于确认板子上已经有什么编译工具反而排在后面。很多新手一上来就装环境、pip install opencv结果发现板子上的系统和 PC 上的根本不是一个东西最后只能反复折腾。2.1 出厂系统里预先装好的 OpenCV这块 RK3568 板子默认跑 Buildroot 构建的系统四核 Cortex-A55 主频 2.0GHz。这个算力做日常的图像处理、简单的目标检测推理是够的跑几百兆的深度学习模型会吃力但传统 CV 算子滤波、边缘、轮廓、模板匹配完全没问题。系统镜像里预置了 OpenCV 4.5.5动态库平铺在/usr/lib下面头文件在/usr/include/opencv4。它编译时打开了 freetype 支持这一点很实用——cv::putText画中文会显示成问号这是 OpenCV 自带 Hershey 字体的老毛病而 freetype 模块可以直接渲染 TTF 字体做产品界面时能省掉自己接一套字体渲染引擎的活。Python 侧同样预装了cv2模块路径在 Python 的 site-packages 下板子上python3 -c import cv2; print(cv2.__version__)能直接打印出版本号不需要额外安装。这里有个认知上的分水岭板子上的 OpenCV 是给目标机用的运行时PC 上的 OpenCV 是给开发机用的两者版本、ABI、编译选项都不一定一致。你可以在 Ubuntu 上用 apt 装一个 OpenCV 来写代码、试算法但绝不能拿 PC 上的.so去链接板子的程序。交叉编译时链接的库必须是工具链 sysroot 里那一份跟板子上的运行时严格对应。2.2 交叉工具链的安装与验证资料提供的工具链是一个自解压的.run包由 SDK 里的 Buildroot 生成后打包出来的好处是不用自己完整编译一遍 SDK——SDK 编译一次动辄几个小时中途还容易因为网络、依赖挂掉很多人第一次就失败在这一步。把.run文件传到 Ubuntu 上scp、共享目录、U 盘都行赋权后执行# 赋予可执行权限 chmod 777 rk3568-toolchain-aarch64-buildroot-linux-gnu.run # 执行自解压安装安装过程会提示确认安装路径 sudo ./rk3568-toolchain-aarch64-buildroot-linux-gnu.run安装脚本会问你装到哪个目录默认是/opt/rk3568-toolchain。这里建议不要改因为后面所有脚本、Makefile、CMakeLists 里的路径都是按这个写的改了就得一处一处跟着改改漏一处就报找不到编译器。确认路径后大约 1~2 分钟装完速度取决于硬盘。装完之后别急着写代码先验证# 检查交叉编译器是否存在且可执行 /opt/rk3568-toolchain/bin/aarch64-buildroot-linux-gnu-g --version # 确认 sysroot 里确实有 opencv4 的头文件和库 ls /opt/rk3568-toolchain/aarch64-buildroot-linux-gnu/sysroot/usr/include/opencv4 ls /opt/rk3568-toolchain/aarch64-buildroot-linux-gnu/sysroot/usr/lib/libopencv_core*两条命令都能出东西说明工具链是可用的。--version打印的是 aarch64 版本的 GCC不是宿主机的 x86 GCC这个要看清装错包的情况不罕见——有人下载了宿主机的工具链编译出来的程序在板子上cannot execute binary file就是这一条没核对。2.3 最小可运行程序C 与 Python 各一份在动手编译之前先写一个能跑通的最小样例功能只有一件读一张图、显示出来、等按键关闭。这个程序的作用在于验证整条链路通不通功能本身倒是次要的。C 版本#include opencv4/opencv2/opencv.hpp #include iostream int main() { // 读取板子上的一块图片路径按自己板子的实际文件改 cv::Mat image cv::imread(/opt/apps/logo/test_logo.png); // imread 失败不会抛异常只会返回空 Mat必须自己判空 if (image.empty()) { std::cout 无法读取图片文件 std::endl; return -1; } // WINDOW_AUTOSIZE 表示窗口大小跟随图片不允许手动缩放 cv::namedWindow(Image, cv::WINDOW_AUTOSIZE); cv::imshow(Image, image); // 参数 0 表示无限等待按键单位是毫秒 cv::waitKey(0); cv::destroyAllWindows(); return 0; }几点说明。头文件写成opencv4/opencv2/opencv.hpp而不是opencv2/opencv.hpp是因为头文件被装在include/opencv4这一层编译时的-I如果指向include而不是include/opencv4就必须带上前缀两种写法选一种别混着来。cv::imread返回空Mat是常态而不是异常路径错、文件损坏、权限不足都会走到empty()分支判空这行别省。cv::waitKey(0)里的 0 是无限阻塞如果写成waitKey(30)窗口会 30 毫秒后就关在板子上基本看不到画面。Python 版本更短import cv2 def main(): # 路径与 C 版本保持一致 image cv2.imread(/opt/apps/logo/test_logo.png) # Python 侧读失败返回 None不是空数组 if image is None: print(无法读取图片文件) return # WINDOW_NORMAL 允许拖动改变窗口大小适合小屏或远程桌面 cv2.namedWindow(Image, cv2.WINDOW_NORMAL) cv2.imshow(Image, image) # 同样 0 表示一直等待 cv2.waitKey(0) cv2.destroyAllWindows() if __name__ __main__: main()Python 版把窗口设成WINDOW_NORMAL是因为板子接小屏或者通过远程方式看画面时AUTOSIZE出来的窗口可能超出可视区域用户看不到关不掉只能杀进程。这是个细节但第一次跑的时候卡在这儿的人不少。程序写完了接下来才是真正的分歧点怎么编译它。3. C 程序的四种编译方式从一行命令到工程化同一份opencv_test.cpp在这份资料里给了四种编译方式命令行、Makefile、CMake、Qt。看起来是重复劳动实际对应四种不同阶段验证环境、写小工具、做正经工程、接界面。搞清楚每种的定位比死记命令有用。3.1 命令行版把 -I、-L、-l 三个参数吃透最原始也最不容易被黑箱掩盖问题的方式就是直接调交叉编译器# 一条命令完成编译反斜杠是续行实际是一行 /opt/rk3568-toolchain/bin/aarch64-buildroot-linux-gnu-g opencv_test.cpp -o opencv_test \ -I /opt/rk3568-toolchain/aarch64-buildroot-linux-gnu/sysroot/usr/include/opencv4 \ -L /opt/rk3568-toolchain/aarch64-buildroot-linux-gnu/sysroot/usr/lib \ -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_videoio -lopencv_imgcodecs逐段拆开看aarch64-buildroot-linux-gnu-g这是交叉编译器本体前缀里的aarch64说明目标架构是 64 位 ARMbuildroot-linux-gnu说明目标系统的 C 库是 glibc 且由 Buildroot 构建。C 程序把g换成gcc。-o opencv_test指定输出文件名不写默认是a.out在板子上放几个 demo 之后你就分不清谁是谁了。-I头文件搜索路径指向 sysroot 里的include/opencv4。-L库文件搜索路径指向 sysroot 里的usr/lib。-l要链接的具体库名注意写的是opencv_core而不是libopencv_core.so编译器会自动补lib前缀和.so后缀。链接哪些库不是拍脑袋决定的。cv::Mat、基本数据类型在opencv_coreimread、imwrite在opencv_imgcodecsimshow、namedWindow、waitKey在opencv_highguicvtColor、resize、threshold这些图像处理算子在opencv_imgproc读写视频、打开摄像头在opencv_videoio。少链接一个就会在链接阶段报一堆undefined reference to cv::xxx看到这种错误第一反应应该是去查这个符号属于哪个模块而不是怀疑代码写错了。按模块拆库的设计是 OpenCV 的一贯做法好处是能按需裁剪体积一个只做图像解码的小工具没必要把 video、dnn、calib3d 全拖进来嵌入式设备 Flash 空间紧能省则省。3.2 Makefile 版把一堆参数收进变量命令行方式的问题在于稍微多几个源文件就没法维护了。这时候上 Makefile# 工具链根目录改这里就能整体切换 TOOLCHAIN_DIR : /opt/rk3568-toolchain GXX : ${TOOLCHAIN_DIR}/bin/aarch64-buildroot-linux-gnu-g SYSROOT_INC : ${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu/sysroot/usr/include SYSROOT : ${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu/sysroot # --sysroot 让编译器自动去目标系统根目录找标准库避免误链宿主机 glibc CFLAGS -I${SYSROOT_INC} CFLAGS -I${SYSROOT_INC}/opencv4 CFLAGS --sysroot${SYSROOT} # 用到的 OpenCV 模块用不到的别加减小体积 LD_FLAGS : -lopencv_core -lopencv_imgcodecs -lopencv_imgproc \ -lopencv_highgui -lopencv_videoio all: $(GXX) opencv_test.cpp $(CFLAGS) $(LD_FLAGS) -o opencv_test echo Build Done ... clean: rm -f opencv_test两个关键点。第一--sysroot${SYSROOT}这行很容易被忽略但它是交叉编译正确性的保险。不带 sysroot 时编译器可能去宿主机的/usr/include和/usr/lib找标准头和 libc链接出来的二进制在板子上要么跑不起来要么跑起来行为诡异。第二Makefile 里all和clean下面的命令行必须是 Tab 开头不能是空格这个坑二十年没变过编辑器自动把 Tab 转成空格之后报missing separator看半天看不出哪里错。用的时候就是make和make clean两条命令生成的opencv_test拷到板子上执行。3.3 CMake 版交叉编译该怎么配工程变大之后 Makefile 也要跟着膨胀这时候换 CMake。但 CMake 做交叉编译光在CMakeLists.txt里指定编译器是不够规范的资料里的写法是在文件里直接 setcmake_minimum_required(VERSION 3.8) # 工具链相关路径集中定义方便整体替换 set(TOOLCHAIN_DIR /opt/rk3568-toolchain) set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/aarch64-buildroot-linux-gnu-gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/aarch64-buildroot-linux-gnu-g) set(SYSROOT_INC ${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu/sysroot/usr/include) set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu/sysroot) set(CMAKE_CXX_STANDARD 11) # 需要的 OpenCV 模块列在这里 set(OPENCV_LIBS opencv_core opencv_imgcodecs opencv_imgproc opencv_highgui opencv_videoio) include_directories(${SYSROOT_INC}) include_directories(${SYSROOT_INC}/opencv4) project(opencv_test) add_executable(opencv_test opencv_test.cpp) target_link_libraries(opencv_test ${OPENCV_LIBS})写完之后不能直接在当前目录cmake .因为上一节留下的 Makefile 还在会打架。标准做法是建独立构建目录mkdir -p build cd build cmake .. # 根据上一级 CMakeLists.txt 生成 Makefile 和缓存文件 make # 真正编译产物在 build 目录下 ls -l opencv_test外置构建目录的好处是生成物和源码物理隔离删掉build就等于清理干净不会出现改了CMakeLists.txt但缓存没刷新导致行为不对的情况。我这里补一个实操经验上面这种「在 CMakeLists 里 set 编译器」的写法能跑但严格来说应该用 toolchain filecmake -DCMAKE_TOOLCHAIN_FILExxx.cmake ..的方式传入。原因是set(CMAKE_CXX_COMPILER ...)必须在project()之前生效顺序一乱就悄悄用了宿主机的 g编出来的程序架构不对file opencv_test一看是 x86-64白编译半天。真踩过这个坑之后我习惯每次编完都跑一遍file命令确认架构两秒钟的事。3.4 Qt 版pkgconfig 一行接进去带界面的应用通常会选 Qt。Qt 工程里接 OpenCV最省事的做法是用 pkg-config不要手动写一堆-I和-lQT core gui greaterThan(QT_MAJOR_VERSION, 4): QT widgets CONFIG c17 SOURCES \ main.cpp \ mainwindow.cpp HEADERS \ mainwindow.h FORMS \ mainwindow.ui # 关键两行打开 pkgconfig 支持并声明依赖 opencv4 CONFIG link_pkgconfig PKGCONFIG opencv4 qnx: target.path /tmp/$${TARGET}/bin else: unix:!android: target.path /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS targetlink_pkgconfig让 qmake 去调pkg-config --cflags --libs opencv4头文件路径和库列表由.pc文件提供不用手写。前提是工具链的 sysroot 里确实存在opencv4.pc而且PKG_CONFIG_PATH指到了它。构建时 Qt Creator 里 CtrlB 就能编控制台里能看到它自动展开的 include 和 lib 路径。业务代码里验证能不能用加一行就够#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/logo/test_logo.png); if (!image.empty()) { qDebug() image size: image.cols image.rows; } }注意 Qt 工程的交叉编译环境要先配好qmake 本身得是目标平台版本的用宿主机 qmake 编出来的 Qt 程序一样跑不了。这块和 OpenCV 无关但十有八九的「Qt 里用不了 OpenCV」最后查出来是 qmake 用错了。4. Python 版不用编译但坑换了地方C 那一套工具链、sysroot、链接库在 Python 这边统统不需要。Python 是解释执行板子上有解释器和cv2模块就能跑。这也意味着调试节奏完全不同——改一行存一行直接跑没有编译等待。4.1 把脚本传上去直接执行板子上预装了 Python3 和cv2把 2.3 节的脚本传过去执行即可# 板子终端里执行注意是 python3 不是 python python3 opencv_test.py如果画面正常弹出说明cv2模块可用、GUI 后端可用、图片路径也对。三件事任何一件不成立都不会有画面。传文件的方式板子和 PC 在同一网段时用 scp 最直接# 在 Ubuntu 上执行把脚本推到板子的 /root 目录 scp opencv_test.py root192.168.1.100:/root/ # 板子上执行 ssh root192.168.1.100 cd /root python3 opencv_test.py板子支持 adb 的话用adb push也行特别是 USB 调试线连着的时候比配网络快。4.2 import cv2 失败时该看什么Python 侧最常见的报错是ModuleNotFoundError: No module named cv2但它的成因不止一种得分清楚第一种板子上确实没装 OpenCV 的 Python 绑定。虽然出厂系统一般带了但如果刷过别的镜像或者自己裁过 rootfs可能就没了。用python3 -c import sys; print(sys.path)看解释器的搜索路径再去这些路径下找有没有cv2相关目录通常是cv2.cpython-*.so或cv2/包目录。第二种装是装了但装在了另一个 Python 版本下。板子上同时存在python和python3、或者 3.8 和 3.10 并存的时候很容易出现pip install装到 A 解释器、实际运行用的是 B 解释器。判断方法很直接which python3和python3 -m pip --version对比一下看是不是同一个环境。第三种import cv2成功了但一调imshow就崩报 GUI 相关错误。这是 OpenCV 的 highgui 后端没编进去或者没找到显示服务。板子上跑的是 Weston 桌面DISPLAY或WAYLAND_DISPLAY环境变量没设对窗口就创建不出来。这种情况下先跑一个不带 GUI 的测试import cv2 # 只做读写不涉及窗口用来隔离是不是 GUI 后端的问题 img cv2.imread(/opt/apps/logo/test_logo.png) print(img.shape if img is not None else read failed)能打印出 shape 说明 OpenCV 本体没问题问题在显示层。4.3 Python 和 C 怎么选这不是喜好问题是场景问题。Python 开发快改算法不用等编译numpy 生态齐全适合算法验证、参数调试、快速出原型。缺点是解释执行有开销逐像素循环在 Python 里慢得离谱一个 1080p 图像的逐点处理能跑出秒级延迟。C 反过来编译慢、内存管理要自己操心但运行效率和内存占用是实打实的优势。同样是逐像素遍历C 版本可能是几十毫秒Python 版本几百毫秒甚至更久。一个比较务实的做法是用 Python 调参定方案用 C 落最终产品。算法逻辑在 Python 里反复试确定下来要用的算子组合和参数之后翻译成 C。翻译过程中最容易出问题的是数据类型和通道顺序——OpenCV 的Mat默认 BGRnumpy 数组也是 BGR这点倒是一致的但 Python 里的int是任意精度C 的uchar一溢出就回绕颜色直接变了样。如果一定要在板子上跑 Python 又嫌慢重点优化方向是别写循环。OpenCV 的算子内部是 C 实现并且做了 SIMD 优化能用cv2.cvtColor、cv2.resize、cv2.threshold解决的别用 numpy 手写循环更别用 Python for 循环遍历像素。5. 避坑与排查出错时最该先看这几处前面几章的流程照着走多数情况下能一次跑通。但真实开发里总有跑不通的时候这一章把高频翻车点集中列一下按「现象 → 原因 → 解决」对照着看。5.1 编译期和运行期的五类典型故障第一条编译时报opencv2/opencv.hpp: No such file or directory。现象是编译器找不到头文件哪怕-I已经写了。原因通常是路径层级写错了——头文件实际在sysroot/usr/include/opencv4/opencv2/opencv.hpp如果-I只指到include代码里就必须写opencv4/opencv2/opencv.hpp如果-I指到include/opencv4代码里写opencv2/opencv.hpp。这两种组合只能对上一半。解决方式是先把ls一层层看清楚实际目录再决定代码里怎么写别两边各对一半。第二条链接时报一堆undefined reference to cv::imshow。现象是编译过了链接挂了。原因是-l里少了对应该符号的模块。imshow在opencv_highguiimread在opencv_imgcodecscvtColor在opencv_imgproc摄像头在opencv_videoio。解决方式是看报错里的符号名对照模块补-l别一股脑把所有opencv_*都加上——虽然能编过但体积和启动时间都会变差。第三条程序拷到板子上执行报No such file or directory但文件明明在。这个最迷惑人。现象是ls能看到文件chmod x也做了一执行就说找不到。原因有两种一是文件本身是 x86 的二进制板子的动态加载器找不到对应的解释器报错信息会误导性地指向「文件不存在」二是动态库缺失同样报这种错。解决方式是在 PC 上用file opencv_test确认架构是ARM aarch64然后在板子上用ldd opencv_test看依赖的.so是否齐全缺哪个补哪个。第四条画面出来了但只占屏幕中间一小块或者方向是横的。现象是在 HDMI 显示器上运行图形程序窗口缩在中间周围一圈黑。原因是 Weston 显示合成器的输出配置和实际屏幕不匹配。解决方式有两条路把 HDMI 输出旋转 90 度或者把 HDMI 设为主显示输出。配置改完要重启显示服务或者重启板子才生效。这个问题跟 OpenCV 一点关系都没有纯粹是显示层配置别在代码里找原因。第五条waitKey(0)之后窗口不刷新、程序像卡死。现象是程序跑起来了但窗口一片灰或者没内容键盘也没反应。原因是waitKey依赖 GUI 事件循环而某些情况下窗口创建和图像显示之间有竞态或者窗口管理器没把焦点给它。解决方式是在imshow之后立刻waitKey(1)让事件循环转一圈再调waitKey(0)等待按键同时确认焦点在窗口上可以用鼠标点一下窗口再按键。提示这五条里第三条和第四条最容易让人在错误的方向上排查很久一个是运行环境问题伪装成文件问题一个是显示服务问题伪装成程序问题。养成先file、再ldd、最后看显示环境的顺序能少走弯路。6. 二次开发把一个 demo 变成能持续迭代的工程把显示图片的 demo 改造成实际项目中间隔的不是算法能力而是工程习惯。我一般按这个顺序做改造。第一步把零散的源文件收进src/头文件进include/CMakeLists.txt放到根目录形成最小可维护结构。构建目录固定叫build加进.gitignore。这样任何人拿到代码三条命令就能编出来mkdir -p build cd build cmake .. make -j$(nproc)第二步把编译参数里的路径抽成变量或者缓存变量别硬编码在源码和 CMake 里。工具链路径、sysroot 路径、OpenCV 模块列表各占一个变量换板子或者换工具链只改一处。第三步也是最多人跳过的一步在 CI 或者本地加一个冒烟脚本每次编完自动跑file检查架构、跑ldd检查依赖完整性。这两个检查加起来不到两秒但能拦住绝大多数「编出来了但板子上跑不了」的情况。验证方法上我习惯把「能不能跑」拆成三层编译通过算第一层ldd依赖齐全算第二层板子上真的出画面或者真的输出正确结果算第三层。三层任何一层没过都不算这个功能做完了。很多人的问题在于只做到第一层就以为完事了后面两层全靠运气。有一个具体的技巧值得说算法从 Python 往 C 迁移时别整段翻译。先在 C 里用cv::imwrite把中间结果比如二值化后的图、找出的轮廓画在原图上写到文件然后跟 Python 版本的同名中间结果做逐像素对比。差异超过阈值就说明数据类型、边界条件或者通道顺序出了问题定位范围瞬间缩小到某一个函数。这个对比脚本我用了好几年比盯着屏幕看两张图猜哪里不一样靠谱得多。从那以后我每次把算法从 PC 搬到板子上都会强制走一遍「架构确认、依赖确认、中间结果比对」这三步不管项目多小。吃过一次编译半天、拷过去跑不起来、回头发现是拿错工具链的亏就再也不想省这两分钟了。希望帮到你。本文还有配套的精品资源点击获取