ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

RK3568嵌入式OpenCV4实战:C++/Python交叉编译与Qt集成

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 集成,到最终在板子上跑出画面,整条链路捋了一遍,默认系统是 Buildroot,OpenCV 版本 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 位 ARM,buildroot-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_core;imread、imwrite在opencv_imgcodecs;imshow、namedWindow、waitKey在opencv_highgui;cvtColor、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里指定编译器是不够规范的,资料里的写法是在文件里直接 set:

cmake_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 file,cmake -DCMAKE_TOOLCHAIN_FILE=xxx.cmake ..的方式传入。原因是set(CMAKE_CXX_COMPILER ...)必须在project()之前生效,顺序一乱就悄悄用了宿主机的 g++,编出来的程序架构不对,file opencv_test一看是 x86-64,白编译半天。真踩过这个坑之后,我习惯每次编完都跑一遍file命令确认架构,两秒钟的事。

3.4 Qt 版:pkgconfig 一行接进去

带界面的应用通常会选 Qt。Qt 工程里接 OpenCV,最省事的做法是用 pkg-config,不要手动写一堆-I和-l:

QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++17 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 += target

link_pkgconfig让 qmake 去调pkg-config --cflags --libs opencv4,头文件路径和库列表由.pc文件提供,不用手写。前提是工具链的 sysroot 里确实存在opencv4.pc,而且PKG_CONFIG_PATH指到了它。构建时 Qt Creator 里 Ctrl+B 就能编,控制台里能看到它自动展开的 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 root@192.168.1.100:/root/ # 板子上执行 ssh root@192.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默认 BGR,numpy 数组也是 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_highgui,imread在opencv_imgcodecs,cvtColor在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 搬到板子上,都会强制走一遍「架构确认、依赖确认、中间结果比对」这三步,不管项目多小。吃过一次编译半天、拷过去跑不起来、回头发现是拿错工具链的亏,就再也不想省这两分钟了。希望帮到你。

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

返回列表