RK3568 OpenCV交叉编译与Qt集成避坑手册

发布时间:2026/11/3 14:01:32
RK3568 OpenCV交叉编译与Qt集成避坑手册

简介:《ATK-DLRK3568_OpenCV4使用手册》面向具备一定Linux基础、希望在嵌入式平台上开展计算机视觉开发的工程师与学习者,围绕正点原子ATK-DLRK3568(四核A55@2.0GHz)开发板,讲解OpenCV 4.5.5在Buildroot出厂系统中的实际使用路径。压缩包内为1个PDF文件,约1.41MB,篇幅精炼,先说明库文件位置与Python接口调用方式,再逐步展开交叉编译工具链安装、C++程序编写(含命令行、Makefile、CMakeLists、Qt四种工程形态)、Python版本编译运行,并附常见问题解答,可当作配置与排错清单随时查阅。文档中的示例代码与编译流程能帮助读者完成从环境搭建到程序跑通的全过程,Qt集成部分尤其适合需要图形界面的视觉项目参考。目前已有120人学习下载,适合想快速上手嵌入式OpenCV开发、减少环境踩坑的开发者。

1. 四核 A55 上跑 OpenCV:这份手册解决的是哪一段路

手上有一块 RK3568 开发板,四核 A55@2.0GHz,出厂 Buildroot 镜像里已经躺着一份 OpenCV 4.5.5,库文件就在/usr/lib下。这是很多人把计算机视觉从 PC 往嵌入式搬时最省事的起点——不用自己裁库、不用调依赖,插上电就能imread。这份《OpenCV4 使用手册》讲的正是这条链路的落地段:在现成环境上写 C++ 和 Python 程序,用交叉编译把 x86 主机上的源码变成板子能跑的 aarch64 可执行文件,再把 OpenCV 接进 Qt 工程。它不教算法,教的是工具链怎么装、头文件和库怎么指、四种构建方式各自的边界在哪。适合已经能在 Linux 下敲命令、但还没把嵌入式交叉编译这条路完整走通过的人。

2. 交叉编译工具链先立住:三元组、sysroot 和那份 .run 安装包

2.1 为什么不在板子上直接编译

板子的 A55 跑得动 OpenCV 的推理和图像处理,但跑不动编译。一个中等规模的 C++ 视觉工程,在板子上make一次可能要十几分钟,而且板端 rootfs 里通常没有装 g++、cmake、make 这一整套。更现实的问题是磁盘和内存:交叉编译工具链本身就有几个 GB,塞进 eMMC 之后留给应用和数据的空间会很紧张。

交叉编译的思路是把「编译」这一步留在 x86 主机上,用一套专门为目标架构生成的编译器产出 aarch64 指令。这套编译器不能随便找,它必须和板子上运行的 C 库版本对齐,否则编出来的程序在板子上要么找不到符号,要么直接段错误。厂商随 SDK 一起打包的那份.run,就是把这套对齐关系固化下来的产物——它由 Buildroot 构建系统在生成 rootfs 的同一次流程里产出,glibc 版本、动态链接器路径、库的 SONAME 都和出厂镜像一致。这也解释了为什么手册里反复强调「不建议修改安装路径」:后面所有的-I、-L、--sysroot都硬编码在这条路径上。

常见做法是把它固定装到/opt下,和系统自带的工具链隔离,升级或换版本时整目录删掉重装即可,不会污染/usr/bin。我一般会在/opt下留一个软链接指向当前使用的版本,比如/opt/rk3568-toolchain,这样工程里的路径写软链接,换工具链时只改链接不动工程文件。

2.2 安装 .run 包并确认三元组

工具链压缩包在随板资料的工具目录里,文件名形如rk3568-toolchain-aarch64-buildroot-linux-gnu-x86_64-<版本号>.run。先把它从 Windows 传到 Ubuntu 主机,用 WinSCP 或者scp都行,然后给执行权限并运行。

# 传上来之后先看文件类型,确认是 shell 自解压包而不是压缩包 file rk3568-toolchain-aarch64-buildroot-linux-gnu-x86_64.run # 加可执行权限,777 是手册的写法,实际 755 就够 chmod 755 rk3568-toolchain-aarch64-buildroot-linux-gnu-x86_64.run # 执行安装,交互式会问你安装路径,直接回车走默认 /opt/rk3568-toolchain ./rk3568-toolchain-aarch64-buildroot-linux-gnu-x86_64.run

安装过程大约一到两分钟,速度取决于主机磁盘。脚本会先把内容解压到一个临时目录,再整体搬到目标路径,所以中间那段时间du -sh看到目录大小在跳是正常的。安装完成后,第一件事不是急着编译,而是确认三元组前缀和版本。

# 确认编译器能被调用,并且目标架构是 aarch64 /opt/rk3568-toolchain/bin/aarch64-buildroot-linux-gnu-g++ -v # 看一眼 gcc 版本和配置参数,重点确认 --with-sysroot 指向哪里 /opt/rk3568-toolchain/bin/aarch64-buildroot-linux-gnu-gcc -print-sysroot

-print-sysroot输出的那条路径就是这套工具链认定的根文件系统镜像目录。如果它输出的不是/opt/rk3568-toolchain/aarch64-buildroot-linux-gnu/sysroot,说明安装路径被改过或者包解压不完整,后面所有编译都会在找头文件时失败。这一步花十秒钟,能省掉后面半小时的排查。

提示:/opt目录默认属于 root,普通用户执行安装脚本时要加sudo,但装完之后要把目录的属主改回自己,否则后面make生成文件时会出现权限错误。

2.3 sysroot 里到底有什么

很多人把交叉编译工具链理解成「一个能编 aarch64 的 g++」,这个理解会让人在后面反复踩坑。工具链是三层结构:bin/下是编译器、链接器、objdump、readelf这些宿主机上运行的程序;aarch64-buildroot-linux-gnu/下是目标平台的运行时;而sysroot/才是真正的「板子文件系统的镜像」,里面有/usr/include、/usr/lib、/lib,和板子上的目录结构一一对应。

OpenCV 的头文件在sysroot/usr/include/opencv4/opencv2/下,注意多出来的那一层opencv4——这是 OpenCV 4.x 相对 3.x 最大的目录变化,也是新手第一个fatal error的来源。库文件在sysroot/usr/lib下,命名形如libopencv_core.so.4.5.5,同时有libopencv_core.so这个开发用软链接指向它,-lopencv_core找的就是这个短名。pkg-config 的描述文件在sysroot/usr/lib/pkgconfig/opencv4.pc,Qt 那套构建系统就是靠它自动推导头文件和库路径的。

SYSROOT=/opt/rk3568-toolchain/aarch64-buildroot-linux-gnu/sysroot # 确认 OpenCV 版本和头文件位置 ls $SYSROOT/usr/include/opencv4/opencv2/ | head grep -m1 "OpenCV_VERSION" $SYSROOT/usr/lib/pkgconfig/opencv4.pc # 列出所有可链接的 OpenCV 模块 ls $SYSROOT/usr/lib/libopencv_*.so | sed 's#.*/lib##; s#\.so##'

第二条命令能直接吐出OpenCV_VERSION=4.5.5之类的内容,这个版本号必须和板子上ls /usr/lib/libopencv_core.so*看到的一致。手册里提到出厂镜像里还带了 freetype 相关支持,用来在图像上绘制文字——中文渲染在嵌入式视觉项目里是个高频需求,cv::putText只支持 ASCII,要写中文得走 contrib 里的 freetype 模块,链接时多一个-lopencv_freetype。这一点在 sysroot 里能直接验出来:ls $SYSROOT/usr/lib/libopencv_freetype*有输出就说明库在,没有就得自己裁一份 contrib 编进去。

3. 第一支 OpenCV 程序:C++ 与 Python 两条路的最小闭环

3.1 C++ 版本与它真正依赖的模块

先写一个不涉及任何算法的程序:读一张图、看一下尺寸、显示出来。选这个而不是边缘检测,是因为它能一次性验证三件事——文件系统路径对不对、imgcodecs模块在不在、图形后端能不能起来。任何一件不对,你都能从输出上立刻区分开。

// opencv_test.cpp #include <opencv4/opencv2/opencv.hpp> #include <iostream> int main() { // 板子上的示例图路径,换成自己的图片也可以 cv::Mat image = cv::imread("/opt/apps/res/sample.png"); // 读失败一定要判空,否则后面 imshow 会直接抛异常 if (image.empty()) { std::cerr << "read image failed, check the path" << std::endl; return -1; } // 先把尺寸打出来,避免把"图读进来了但内容不对"误判成显示问题 std::cout << "width: " << image.cols << " height: " << image.rows << " channels: " << image.channels() << std::endl; cv::namedWindow("Image", cv::WINDOW_AUTOSIZE); cv::imshow("Image", image); // 参数 0 表示无限等待,单位是毫秒 cv::waitKey(0); cv::destroyAllWindows(); return 0; }

头文件的写法<opencv4/opencv2/opencv.hpp>是嵌入式场景里最省事的一种,它把opencv4这一层目录直接写进 include 路径,编译时只要把sysroot/usr/include加进去就行。另一种写法是<opencv2/opencv.hpp>,配合-I.../usr/include/opencv4,两种都能用,但同一份代码里不要混用,混着写迟早会在某个子头文件上翻车。

这个程序用到了五个模块,少一个都链接不过:core提供Mat和基础数据结构,imgcodecs提供imread/imwrite,highgui提供namedWindow/imshow/waitKey,imgproc提供图像处理的基础函数(这个例子里没直接调,但highgui内部会用到颜色空间转换),videoio提供视频采集和编解码后端。手册给出的链接列表正好是这五个,不是随手写的。我一般会在这五个之外再补一个-lopencv_imgproc的显式声明——它虽然被隐式依赖,但显式写出来在换 OpenCV 版本时更不容易出问题。

3.2 Python 版本:不用编译,但解释器要对

Python 这条路省掉了整个交叉编译环节,因为 CPython 解释器和 cv2 模块都已经在板子的 rootfs 里了。手册里给的解释器路径在 sysroot 下也有一份,但对使用者来说更关心的是板子上那份。

# opencv_test.py import cv2 def main(): # 和 C++ 版本读同一张图,方便对照两边结果 image = cv2.imread('/opt/apps/res/sample.png') # Python 里读失败返回 None,不是抛异常 if image is None: print('read image failed, check the path') return print('width: {}, height: {}, channels: {}'.format( image.shape[1], image.shape[0], image.shape[2] if len(image.shape) == 3 else 1)) # WINDOW_NORMAL 允许拖拽缩放,嵌入式屏幕上比 AUTOSIZE 好用 cv2.namedWindow('Image', cv2.WINDOW_NORMAL) cv2.imshow('Image', image) cv2.waitKey(0) cv2.destroyAllWindows() if __name__ == '__main__': main()

两个版本在显示行为上有一处细节差异:C++ 用的WINDOW_AUTOSIZE会让窗口严格等于图像尺寸,小分辨率屏幕上一张大图会超出屏幕;Python 用的WINDOW_NORMAL允许后续手动调整。在只有 HDMI 输出、没有触摸屏的调试环境里,这个差别会直接影响你能不能看到完整画面。新手常犯的错误是把 C++ 里的WINDOW_AUTOSIZE原样搬到板子上,然后以为程序卡死了——其实是窗口大到屏幕外面去了。

还有一点:Python 版的cv2是预编译进 rootfs 的,不能指望pip install opencv-python在板子上重新装一遍,板子上的 pip 装的是源码包,没有预编译轮子,编译过程会跑几十分钟然后因为内存不足挂掉。要用 Python,就用 rootfs 自带的那个。

3.3 把可执行文件送到板子上并跑起来

C++ 程序编出来之后是个 ELF 文件,得先确认架构对,再传到板子。

# 主机端:编好的文件应该是 ARM aarch64 file opencv_test # 期望输出类似:ELF 64-bit LSB pie executable, ARM aarch64, ... # 用 scp 传,前提是板子和主机在同一网段且 ssh 服务已起 scp opencv_test root@192.168.1.100:/root/ # 或者用 adb,板子插 USB OTG 口时更省事 adb push opencv_test /root/

传到板子上之后,先chmod +x opencv_test再加执行权限,然后直接./opencv_test跑。如果屏幕是 HDMI 且系统跑的是 Weston,你会看到窗口出现,可能不在屏幕正中——手册的常见问题里提到过这个现象,原因是 Weston 默认把 HDMI 当扩展屏而不是主屏,需要在显示配置里把它设成主显示或者旋转 90 度。这不是 OpenCV 的问题,是显示合成器的问题,改配置即可。

如果程序跑起来但提示cannot open display或者Failed to open X11 display,说明图形后端没对上。Buildroot 镜像里 OpenCV 的 highgui 通常编的是 GTK 或 Qt 后端,跑在 Wayland 上时需要环境变量指路:XDG_RUNTIME_DIR=/run/user/0和WAYLAND_DISPLAY=wayland-0是常见的一组。实在懒得折腾显示,就先用imwrite把结果写回文件再拉回主机看,验证逻辑比验证显示重要得多。

4. 四种构建方式怎么选:命令行、Makefile、CMake 与 Qt 的落地写法

4.1 命令行一把梭:-I / -L / -l 三件套

单文件小程序用不着构建系统,一条命令编完最直接。这条命令值得背下来,因为后面三种方式本质上都是它的封装。

SYSROOT=/opt/rk3568-toolchain/aarch64-buildroot-linux-gnu/sysroot CXX=/opt/rk3568-toolchain/bin/aarch64-buildroot-linux-gnu-g++ $CXX opencv_test.cpp -o opencv_test \ --sysroot=$SYSROOT \ -I$SYSROOT/usr/include/opencv4 \ -L$SYSROOT/usr/lib \ -lopencv_core -lopencv_highgui -lopencv_imgproc \ -lopencv_videoio -lopencv_imgcodecs

逐段拆开看:--sysroot是最容易被漏掉的一个,它告诉编译器和链接器「把 $SYSROOT 当成根目录」,之后找libc、找启动文件crt1.o都在这个虚拟根下进行。漏了它,编译器会去主机的/usr/lib里找 x86 的crt文件,报cannot find crt1.o,或者更隐蔽地——找到了但架构不对,链接阶段才报错。-I指定头文件搜索路径,-L指定库搜索路径,-l指定链接哪个库,-o决定输出文件名。这四组参数的顺序有讲究:-l必须写在源文件之后,因为链接器是按顺序解析的,放在前面会找不到符号引用。

如果你要链接 freetype 做中文绘制,在这条命令后面追加-lopencv_freetype,注意这个模块属于 contrib,不是所有预编译镜像都带。

4.2 Makefile:把路径收敛成变量

一条命令能解决的事,一旦文件多了就不行了。Makefile 的价值在于把重复的路径和库列表收敛成变量,改一处全局生效。

TOOLCHAIN_DIR := /opt/rk3568-toolchain CXX := $(TOOLCHAIN_DIR)/bin/aarch64-buildroot-linux-gnu-g++ SYSROOT := $(TOOLCHAIN_DIR)/aarch64-buildroot-linux-gnu/sysroot # 头文件路径集中在这里,新增依赖目录只改这一行 INCLUDES := -I$(SYSROOT)/usr/include/opencv4 CFLAGS := --sysroot=$(SYSROOT) $(INCLUDES) -O2 -Wall # 库列表,顺序影响链接结果,被依赖的放后面 LDLIBS := -L$(SYSROOT)/usr/lib \ -lopencv_core -lopencv_imgproc -lopencv_imgcodecs \ -lopencv_highgui -lopencv_videoio TARGET := opencv_test SRCS := opencv_test.cpp $(TARGET): $(SRCS) $(CXX) $(CFLAGS) $^ -o $@ $(LDLIBS) clean: rm -f $(TARGET) .PHONY: clean

$^是 Make 的自动变量,展开成所有依赖文件;$@展开成目标名。这两个符号在单文件工程里看不出好处,文件一多就能省掉大量重复书写。-O2在嵌入式上建议默认开着,OpenCV 的很多函数有 SIMD 优化分支,不开优化性能差一大截;-Wall用来提前发现隐式类型转换,Mat的尺寸参数是int,和size_t混用时的截断问题在板子上会变成花屏。

一个常见误用是make clean之后忘了重新make,直接拿磁盘上残留的旧可执行文件去跑,然后奇怪为什么改了代码没生效。clean删的是产物,不是中间状态,删完必须重编。另一个误用是库顺序写反,-lopencv_highgui放在-lopencv_imgproc前面,链接时报 undefined reference,把依赖方放在被依赖方前面就好了。

4.3 CMake:build 目录与 toolchain 变量的取舍

CMake 的抽象层级比 Makefile 高,代价是多了一层配置。交叉编译场景下,最稳的写法不是去折腾toolchain.cmake,而是在CMakeLists.txt里直接写死编译器和 sysroot,简单直接。

cmake_minimum_required(VERSION 3.10) project(opencv_demo CXX) set(TOOLCHAIN_DIR /opt/rk3568-toolchain) set(SYSROOT ${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu/sysroot) # 显式指定交叉编译器,避免 cmake 自动探测到主机的 gcc set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/aarch64-buildroot-linux-gnu-g++) set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/aarch64-buildroot-linux-gnu-gcc) set(CMAKE_SYSROOT ${SYSROOT}) # 交叉编译时必须关掉 cmake 在主机目录里找库的行为 set(CMAKE_FIND_ROOT_PATH ${SYSROOT}) 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_CXX_STANDARD 14) add_executable(opencv_test opencv_test.cpp) target_include_directories(opencv_test PRIVATE ${SYSROOT}/usr/include/opencv4) target_link_directories(opencv_test PRIVATE ${SYSROOT}/usr/lib) target_link_libraries(opencv_test PRIVATE opencv_core opencv_imgproc opencv_imgcodecs opencv_highgui opencv_videoio)

CMAKE_FIND_ROOT_PATH那三行是交叉编译 CMake 工程的关键。不加的话,find_package和find_library会在主机的/usr/lib里翻出一个 x86 的libopencv,链接时才发现架构不对,报错信息还特别绕。加了MODE_LIBRARY ONLY之后,所有库搜索都被限制在 sysroot 内,主机上装没装 OpenCV 完全不影响结果。

构建流程按手册走就行,重点是不要在源码目录里直接cmake ..:

mkdir -p build && cd build cmake .. make -j$(nproc) ls -l opencv_test

-j$(nproc)按主机核数并行编译,单文件工程看不出差别,文件多了能快好几倍。build目录的作用是隔离中间产物,删掉它等于彻底 clean,比make clean干净。我一般会在.gitignore里把build/排除掉,避免中间文件混进版本库。

注意:如果之前在同一目录用主机 gcc 跑过一次cmake,缓存里会记着主机编译器,换个交叉编译器也不会自动更新。换工具链之后必须删掉整个build目录重新配置。

4.4 Qt:用 pkg-config 让 qmake 自己找库

Qt 工程里不需要手写-I和-L,只要.pro里挂上 pkg-config,qmake 会去读opencv4.pc,把里面Cflags和Libs的全部内容展开。

QT += core gui widgets CONFIG += c++17 SOURCES += main.cpp mainwindow.cpp HEADERS += mainwindow.h FORMS += mainwindow.ui # 关键两行:打开 pkg-config 支持,并声明依赖 opencv4 CONFIG += link_pkgconfig PKGCONFIG += opencv4

link_pkgconfig是一个 qmake 特性,打开之后PKGCONFIG里列的每个包都会被pkg-config --cflags --libs展开。前提是 qmake 知道去哪找.pc文件——如果 Qt 的 kit 已经按手册配好了交叉编译环境,sysroot 会被自动带上,pkg-config会去 sysroot 里查。没有的话就手动设两个环境变量:

SYSROOT=/opt/rk3568-toolchain/aarch64-buildroot-linux-gnu/sysroot export PKG_CONFIG_SYSROOT_DIR=$SYSROOT export PKG_CONFIG_LIBDIR=$SYSROOT/usr/lib/pkgconfig

PKG_CONFIG_LIBDIR和PKG_CONFIG_PATH的区别值得记一下:前者是「覆盖」,把默认搜索路径整个替换掉;后者是「追加」,在默认路径后面加。交叉编译必须用前者,否则主机的/usr/lib/pkgconfig里那份 x86 的opencv4.pc会先被找到,编出来的 Qt 程序一上板就报架构错误。

Qt 侧的代码只需要在文件头加 include,然后在构造函数里试一下imread即可:

#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/res/sample.png"); if (!image.empty()) { qDebug() << "opencv ok, size =" << image.cols << "x" << image.rows; } }

构建时用 Ctrl+B 或qmake && make。如果 Qt Creator 里能看到编译命令中自动带上了-I.../include/opencv4和一堆-lopencv_*,说明 pkg-config 生效了,一个都不用手填。

5. 避坑与排查:交叉编译 OpenCV 时最容易翻车的五个现场

5.1 编译期:头文件、符号与 ABI

现象一:fatal error: opencv2/opencv.hpp: No such file or directory。原因基本只有两种:include 路径里少了opencv4这一层,或者源码里写的是<opencv2/opencv.hpp>但-I只指到了usr/include。OpenCV 4.x 把原来的opencv目录整体挪进了opencv4下,为了和 3.x 共存,这是有意为之。 解决方式二选一:源码改成<opencv4/opencv2/opencv.hpp>,或者把-I改成-I$SYSROOT/usr/include/opencv4。前者不依赖额外的 include 路径,在 Qt 这种不便控制编译参数的工程里更好用;后者在纯 Makefile 工程里更清爽。不要两边都加,会出现同一份头文件被解析两次、宏重定义的问题。

现象二:undefined reference to 'cv::imread(std::string const&, int)'。编译过了但链接挂了,说明头文件找到了、库没链上。imread属于imgcodecs,imshow属于highgui,Mat的基础操作在core里,三者是分开的库,少一个都会在链接阶段报符号缺失。 解决方式是补-lopencv_imgcodecs。判断该补哪个库有个笨办法:错误信息里的cv::后面的函数名,去sysroot/usr/lib下用nm -D逐个 grep,哪个库里有这个符号就链哪个。实际项目里我一般把常用的六个库一次性全链上,多链不会出错,只会在体积上多一点,strip 之后影响很小。

现象三:链接报skipping incompatible .../libopencv_core.so。这是最隐蔽的一个。报错说明链接器找到了一个库,但判定它架构不匹配——通常是主机上装过 OpenCV,/usr/lib里的 x86 版本被-L后面的默认搜索路径捡到了。交叉编译时如果没有--sysroot,-L只指定了 OpenCV 的位置,libc和依赖库还是会去主机目录找。 解决方式是检查命令里有没有--sysroot=,以及-L的顺序是否把 sysroot 的usr/lib放在了前面。用$CXX -print-search-dirs能看到编译器实际的搜索路径,主机路径出现在里面就说明 sysroot 没生效。

5.2 运行期:动态库、显示与信号

现象四:板子上运行报error while loading shared libraries: libopencv_core.so.4.5。程序传上去了、权限也加了,起不来。原因是板子的 rootfs 里这份 OpenCV 库不在动态链接器的默认搜索路径上,或者干脆没装。 解决分两步。先在板子上find / -name "libopencv_core.so*"确认库在哪,出厂镜像里应该在/usr/lib,那是默认路径,能找到却加载失败说明是LD_LIBRARY_PATH被别的程序改坏了。库在非标准路径时,临时跑一次用LD_LIBRARY_PATH=/opt/lib ./opencv_test,长期用就在/etc/ld.so.conf.d/下加一行路径再ldconfig。如果压根找不到库,说明这个 rootfs 不是你编译时用的那个镜像,得把 sysroot 里对应的.so一起拷过去。

现象五:程序能跑,但imshow之后屏幕没反应或者报could not connect to display。这一类几乎全是图形后端的问题,和 OpenCV 本身没关系。出厂镜像是 Buildroot + Weston,imshow走的是 Wayland 通道,需要XDG_RUNTIME_DIR和WAYLAND_DISPLAY两个环境变量。从 ssh 登录进去执行时,这两个变量通常不在环境里,程序就找不到会话。 解决方式是先echo $XDG_RUNTIME_DIR看一眼,为空就export XDG_RUNTIME_DIR=/run/user/0;WAYLAND_DISPLAY一般是wayland-0,ls /run/user/0/能看到实际名字。另一个高频现象是窗口出现了但缩在屏幕中间一小块,那是 Weston 把 HDMI 当成次级显示器的默认行为,按手册里的办法把它设成主屏,或者直接旋转输出方向。调试阶段不想纠缠显示,就把imshow换成imwrite,把结果图拉回主机看,效率高得多。

现象六:编译一切正常,上板一跑就Illegal instruction或者秒段错误。这种情况八成是链接阶段混进了主机库,或者编译时加了-march=native。A55 支持的指令集和主机 x86 完全不同,-march=native会让编译器按宿主机 CPU 生成指令,编出来的二进制在板子上第一条不认识的指令就崩。还有一个可能是 C++ 标准库版本不匹配:主机上的libstdc++版本比板子上的新,用到了新 ABI 符号,板子上没有。 排查手段是aarch64-buildroot-linux-gnu-readelf -d opencv_test | grep NEEDED,把依赖列表打出来逐个看,出现libc.so.6之外的陌生库、或者版本号明显偏高的libstdc++.so.6,就要追一下它是从哪来的。编译选项里去掉-march=native,换成-march=armv8-a,绝大多数 RK3568 场景下够用。

6. 把依赖钉死:上板前的三项自检与一次瘦身

程序编出来别急着拷,先做三项自检,能省掉大量「上板才发现」的时间。第一项看架构和解释器路径,第二项看动态依赖列表,第三项用 pkg-config 反向校验编译参数是否和.pc文件一致。

SYSROOT=/opt/rk3568-toolchain/aarch64-buildroot-linux-gnu/sysroot CROSS=/opt/rk3568-toolchain/bin/aarch64-buildroot-linux-gnu- # 1. 架构与动态链接器,interpreter 必须是 aarch64 那一条 file opencv_test ${CROSS}readelf -l opencv_test | grep -i interpreter # 2. 动态依赖,逐个确认板子上都有 ${CROSS}readelf -d opencv_test | grep NEEDED # 3. 用 sysroot 里的 pc 文件反查,对比和你手写的参数是否一致 PKG_CONFIG_SYSROOT_DIR=$SYSROOT \ PKG_CONFIG_LIBDIR=$SYSROOT/usr/lib/pkgconfig \ pkg-config --cflags --libs opencv4

第一项里如果 interpreter 显示的是/lib64/ld-linux-x86-64.so.2,说明整个编译过程根本没走交叉工具链,回头查CXX变量。第二项输出里出现libopencv_*.so.4.5之外的库名(比如某个你没主动链接的第三方库),要确认它在目标 rootfs 里存在,否则上板必然报not found。第三项是给自己写的 Makefile 做体检——pkg-config输出的Libs里带的库如果比你写的多,说明你漏了;比你少,说明你多链了没用的东西。

瘦身这一步看项目阶段。调试期不要 strip,保留符号才能在上板崩溃时用addr2line定位到行号。定版前再跑一次:

${CROSS}strip --strip-unneeded opencv_test ls -lh opencv_test

--strip-unneeded只删对动态链接无用的符号,比--strip-all安全,后者会把动态符号表也一起削掉,程序直接起不来。一个典型的 OpenCV 小工具从 200KB 出头能压到 40KB 左右,对 eMMC 空间紧张的板子有意义。

常用模块和链接名之间的对应关系,我整理成一张表放在项目 README 里,换工程时直接抄:

模块链接参数什么时候必须加
core-lopencv_core只要用了cv::Mat就离不开
imgcodecs-lopencv_imgcodecs读写图片文件,imread/imwrite
imgproc-lopencv_imgproc缩放、滤波、颜色空间转换
highgui-lopencv_highgui窗口显示、按键等待
videoio-lopencv_videoio摄像头、视频文件、GStreamer 后端
freetype-lopencv_freetype图上绘制中文,属 contrib

最后说一个习惯。早些年我图省事,编完直接scp上板,跑不起来就反复改代码,有一次折腾了两个小时,最后发现是主机/usr/lib里那份 x86 的 OpenCV 被链接进去了,代码一个字都没错。从那以后我每次编完必走一遍file+readelf -d这两条命令,三十秒的事,比在板子上瞎猜快得多。希望帮到你。

本文还有配套的精品资源,点击获取