4. 开发环境搭建

说实话,嵌入式AI开发最磨人的不是算法本身,而是环境搭建。我见过太多同学在PC上跑得飞起,一上板子就各种链接错误、段错误。这一章,咱们就把这些坑一个个填平。

核心思路:嵌入式移植的本质是“交叉编译”——在x86主机上编译出ARM架构的可执行文件。所有库都得走这个流程,一个都不能少。

4.1 交叉编译工具链的选择与配置

工具链是地基。地基歪了,后面全白搭。我个人习惯用Linaro提供的GCC工具链,稳定且社区活跃。

目标平台 工具链名称 下载来源
ARMv7 (Cortex-A7/A9) gcc-arm-linux-gnueabihf Linaro Releases
ARMv8 (Cortex-A53/A72) aarch64-linux-gnu-gcc Linaro Releases
RISC-V (K210等) riscv64-unknown-linux-gnu-gcc SiFive / T-Head
我的习惯:下载后解压到 /opt/toolchains/ 目录,然后配置环境变量。别放系统目录,方便多版本切换。
# 解压工具链
tar -xvf gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf.tar.xz -C /opt/toolchains/

# 配置环境变量(写入 ~/.bashrc)
export PATH=/opt/toolchains/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin:$PATH
export CC=arm-none-linux-gnueabihf-gcc
export CXX=arm-none-linux-gnueabihf-g++
export LD=arm-none-linux-gnueabihf-ld

验证一下:arm-none-linux-gnueabihf-gcc --version。能正常输出版本号,说明工具链活了。

我曾经踩过的坑:工具链的glibc版本必须与板子上的系统匹配。有一次我用了高版本glibc的工具链,编译出来的程序在旧版系统上直接报“FATAL: kernel too old”。后来我学乖了,先查板子的glibc版本:ldd --version。

4.2 OpenCV的嵌入式移植

OpenCV在嵌入式上是个“大家伙”。你想想看,完整的OpenCV编译出来几百兆,板子根本放不下。所以我们要做的是——模块化裁剪。

我一般只保留这几个模块:core、imgproc、imgcodecs、highgui(可选)、dnn(如果不用ONNX Runtime替代)。

# 交叉编译OpenCV 4.8.0
cmake -DCMAKE_TOOLCHAIN_FILE=../platforms/linux/arm-gnueabi.toolchain.cmake \
      -DCMAKE_INSTALL_PREFIX=/opt/opencv-arm \
      -DBUILD_opencv_world=OFF \
      -DBUILD_opencv_imgproc=ON \
      -DBUILD_opencv_imgcodecs=ON \
      -DBUILD_opencv_highgui=OFF \
      -DBUILD_opencv_dnn=OFF \
      -DWITH_JPEG=ON \
      -DWITH_PNG=ON \
      -DBUILD_JPEG=ON \
      -DBUILD_PNG=ON \
      -DCMAKE_BUILD_TYPE=MinSizeRel \
      ..

make -j$(nproc)
make install
关键参数说明:
  • BUILD_opencv_world=OFF:不生成大而全的world库,按需编译
  • CMAKE_BUILD_TYPE=MinSizeRel:优化体积,比Release小30%左右
  • BUILD_JPEG/BUILD_PNG=ON:静态编译图片编解码库,避免依赖系统库版本冲突

编译完成后,检查一下产物大小:du -sh /opt/opencv-arm/lib/。我一般控制在5MB以内,超过这个数就得再砍模块。

4.3 ONNX Runtime的嵌入式移植

ONNX Runtime是YOLO推理的核心引擎。嵌入式移植时,我建议用静态链接。为什么?动态库在板子上容易出“找不到符号”的问题,排查起来很痛苦。

# 下载ONNX Runtime源码
git clone --recursive https://github.com/microsoft/onnxruntime.git -b v1.16.3

# 配置交叉编译
./build.sh --config MinSizeRel \
           --build_shared_lib \
           --arm \
           --arm64 \
           --parallel \
           --cmake_extra_defines CMAKE_TOOLCHAIN_FILE=/path/to/arm64.toolchain.cmake \
           --cmake_extra_defines onnxruntime_ENABLE_LANGUAGE_INTEROP_OPS=OFF \
           --cmake_extra_defines onnxruntime_BUILD_UNIT_TESTS=OFF
注意:ONNX Runtime的交叉编译依赖protobuf。你得先交叉编译protobuf,再编译ONNX Runtime。顺序不能乱。我曾经因为偷懒,直接用了主机上的protobuf,结果链接时一堆ABI不兼容的错误。

编译产物在 build/Linux/MinSizeRel/ 下。核心文件就两个:libonnxruntime.so 和 onnxruntime.h。我习惯把这两个文件直接拷到项目目录里,跟源码一起管理。

4.4 TensorRT的嵌入式移植

TensorRT是NVIDIA的专属优化器。说白了,只有Jetson系列才用得上。移植方式跟其他库不一样——直接在板子上编译。

为什么不在主机上交叉编译?因为TensorRT的优化器会检测GPU架构,交叉编译出来的库没法在板子上做engine序列化。

# 在Jetson板子上操作
# 安装依赖
sudo apt-get install libcudnn8 libcudnn8-dev libcublas-11-4

# 下载TensorRT deb包
wget https://developer.nvidia.com/compute/machine-learning/tensorrt/secure/8.6.1/tars/TensorRT-8.6.1.6.Ubuntu20.04.aarch64-gnu.cuda-11.8.tar.gz

# 解压并配置环境变量
tar -xzvf TensorRT-8.6.1.6.Ubuntu20.04.aarch64-gnu.cuda-11.8.tar.gz
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/TensorRT-8.6.1.6/lib
我的经验:TensorRT版本必须跟CUDA、cuDNN版本严格对应。NVIDIA官方有个兼容性矩阵表,每次升级前我都去查一遍。版本不对应,轻则推理结果错误,重则直接crash。

4.5 NCNN的嵌入式移植

NCNN是腾讯开源的推理框架,对ARM平台优化得特别好。我特别喜欢它的轻量级——编译出来才几百KB,非常适合资源受限的板子。

# 交叉编译NCNN
git clone https://github.com/Tencent/ncnn.git
cd ncnn
mkdir build-arm && cd build-arm

cmake -DCMAKE_TOOLCHAIN_FILE=../toolchains/arm-linux-gnueabihf.toolchain.cmake \
      -DCMAKE_INSTALL_PREFIX=/opt/ncnn-arm \
      -DNCNN_BUILD_EXAMPLES=OFF \
      -DNCNN_BUILD_TESTS=OFF \
      -DNCNN_BUILD_TOOLS=OFF \
      -DNCNN_OPENMP=ON \
      -DNCNN_VULKAN=OFF \
      -DNCNN_ARM=ON \
      ..

make -j4
make install
关键优化开关:
  • NCNN_OPENMP=ON:开启多线程加速,YOLO推理能快2-3倍
  • NCNN_VULKAN=OFF:嵌入式板子一般没有GPU,关掉Vulkan减少体积
  • NCNN_ARM=ON:启用ARM汇编优化,这是NCNN的精髓

编译完成后,用 ncnnoptimize 工具对模型做优化。这个工具能把YOLO的卷积层融合、BN层合并,推理速度提升30%以上。

4.6 整体移植流程与验证

上面这些库都移植完后,怎么验证它们能协同工作?我一般写一个最小验证程序:加载一张图片,跑一次YOLO推理,输出结果。

// test_pipeline.c
#include <opencv2/opencv.hpp>
#include <onnxruntime_cxx_api.h>

int main() {
    // 1. 用OpenCV读取图片
    cv::Mat img = cv::imread("test.jpg");
    
    // 2. 用ONNX Runtime加载模型
    Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "test");
    Ort::Session session(env, "yolov8n.onnx", Ort::SessionOptions{nullptr});
    
    // 3. 推理
    // ... (省略具体推理代码)
    
    printf("Pipeline test passed!\n");
    return 0;
}
编译命令: arm-none-linux-gnueabihf-g++ test_pipeline.c -o test_pipeline -I/opt/opencv-arm/include -I/opt/onnxruntime-arm/include -L/opt/opencv-arm/lib -L/opt/onnxruntime-arm/lib -lopencv_core -lopencv_imgproc -lopencv_imgcodecs -lonnxruntime

把编译好的 test_pipeline 拷到板子上,运行。如果输出“Pipeline test passed!”,恭喜你,环境搭建成功了。

嵌入式YOLO开发环境搭建流程 主机(x86 Ubuntu) 交叉编译工具链 OpenCV (裁剪) ONNX Runtime NCNN 目标板(ARM Linux) TensorRT (Jetson专用) OpenCV + ONNX Runtime + NCNN 最小验证程序 SCP传输 关键注意事项 1. 工具链glibc版本必须与板子系统匹配 2. OpenCV必须裁剪,只保留必要模块 3. ONNX Runtime需先编译protobuf依赖 4. TensorRT只能在Jetson板子上本地编译

嗯,到这里,环境搭建的核心内容就讲完了。记住一句话:交叉编译没有捷径,每一步都得亲自踩一遍。我当年第一次搭环境,整整折腾了三天。但只要你按照上面的步骤来,应该能控制在半天内搞定。