3. 开发环境搭建:交叉编译工具链、ONNX Runtime、TensorRT、OpenCV 的嵌入式部署

说实话,嵌入式开发的第一个坎,往往不是算法本身,而是环境搭建。我见过不少同事,模型在 PC 上跑得飞起,一到板子上就各种报错,最后发现是交叉编译工具链没配好。这一章,我就带你一步步把地基打牢。

3.1 交叉编译工具链:打通 PC 与板子的桥梁

你想想看,你的开发机是 x86 架构,而嵌入式板子通常是 ARM 或 RISC-V。直接在 PC 上编译出来的程序,板子根本认不了。这时候就需要交叉编译——在 PC 上生成目标平台的可执行文件。

3.1.1 工具链的选择

我个人习惯用 Linaro 提供的 GCC 工具链,稳定且社区活跃。以 ARM64 为例,我通常这样配置:

# 下载并解压
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz
tar -xf gcc-linaro-*.tar.xz -C /opt/

# 设置环境变量
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH
export CC=aarch64-linux-gnu-gcc
export CXX=aarch64-linux-gnu-g++
注意: 我曾经因为 GCC 版本不匹配,折腾了整整两天。板子的内核用 4.14,我配了个 9.2 的工具链,结果 glibc 版本冲突,程序一跑就段错误。建议工具链版本与板子 BSP 推荐的保持一致。

3.1.2 验证工具链

配好之后,写个简单的 Hello World 测试一下:

# test.c
#include <stdio.h>
int main() {
    printf("Hello from ARM64!\n");
    return 0;
}

# 编译
aarch64-linux-gnu-gcc test.c -o test_arm

# 查看文件信息
file test_arm
# 输出: test_arm: ELF 64-bit LSB executable, ARM aarch64, ...

看到 ARM aarch64 就对了。把这个文件传到板子上,./test_arm 能打印出信息,说明工具链没问题。

3.2 ONNX Runtime:跨平台推理引擎的部署

ONNX Runtime 的好处是,你训练时用 PyTorch 还是 TensorFlow 都无所谓,导出 ONNX 模型后,它都能跑。在嵌入式端,我主要用它来做 CPU 推理,或者作为 TensorRT 的备选方案。

3.2.1 交叉编译 ONNX Runtime

官方提供了预编译包,但嵌入式平台往往需要自己编译。我建议用 CMake 交叉编译,步骤大致如下:

# 设置交叉编译工具链
cmake -DCMAKE_TOOLCHAIN_FILE=<path_to_toolchain.cmake> \
      -DCMAKE_INSTALL_PREFIX=<install_path> \
      -Donnxruntime_BUILD_SHARED_LIB=ON \
      -Donnxruntime_USE_CUDA=OFF \
      -Donnxruntime_USE_OPENMP=ON \
      ..

make -j4
make install
小技巧: 如果板子内存有限,可以关掉一些不需要的算子优化,比如 -Donnxruntime_USE_MKLDNN=OFF,能减少不少体积。

3.2.2 部署验证

编译完成后,把 libonnxruntime.so 和头文件拷贝到板子上。写个简单的推理程序:

#include <onnxruntime_cxx_api.h>
int main() {
    Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "test");
    Ort::SessionOptions session_options;
    session_options.SetIntraOpNumThreads(4);

    const char* model_path = "yolov8n.onnx";
    Ort::Session session(env, model_path, session_options);

    printf("ONNX Runtime loaded successfully!\n");
    return 0;
}

编译时链接 -lonnxruntime,运行前记得 export LD_LIBRARY_PATH=./。嗯,这里要注意,ONNX Runtime 的版本要和模型导出时的 opset 版本匹配,否则会报算子不支持的错误。

3.3 TensorRT:NVIDIA 平台的性能利器

如果你用的是 Jetson 系列板子,那 TensorRT 几乎是必选项。它能将 ONNX 模型优化成 TensorRT 引擎,推理速度能提升 2-5 倍。

3.3.1 环境准备

Jetson 板子出厂一般自带 JetPack,里面包含了 TensorRT。但版本可能比较旧,我建议手动更新:

# 查看当前版本
dpkg -l | grep tensorrt

# 安装最新版(以 JetPack 5.1 为例)
sudo apt-get update
sudo apt-get install nvidia-tensorrt

3.3.2 ONNX 转 TensorRT 引擎

我最常用的方式是使用 trtexec 工具,简单直接:

# 将 ONNX 转为 FP16 引擎
trtexec --onnx=yolov8n.onnx \
        --saveEngine=yolov8n_fp16.engine \
        --fp16 \
        --workspace=1024

# 测试推理性能
trtexec --loadEngine=yolov8n_fp16.engine --duration=10
关键点: TensorRT 的引擎文件是跟硬件绑定的。你在 Jetson Orin 上生成的引擎,不能直接拿到 Jetson Nano 上用。每次换板子都得重新转一次。

3.3.3 动态形状处理

YOLO 的输入尺寸有时会变化。TensorRT 支持动态形状,但需要在转换时指定范围:

trtexec --onnx=yolov8n.onnx \
        --saveEngine=yolov8n_dynamic.engine \
        --minShapes=input:1x3x320x320 \
        --optShapes=input:1x3x640x640 \
        --maxShapes=input:1x3x1280x1280 \
        --fp16

我个人建议,如果应用场景固定,尽量用静态形状。动态形状会引入额外的优化开销,推理速度会慢 10%-20%。

3.4 OpenCV:图像预处理与后处理的基础库

YOLO 的推理流程中,图像读取、缩放、归一化、NMS 等操作都离不开 OpenCV。嵌入式端的 OpenCV 需要裁剪,去掉不必要的模块。

3.4.1 交叉编译 OpenCV

我一般只保留 core、imgproc、imgcodecs 这几个模块:

cmake -DCMAKE_TOOLCHAIN_FILE=<toolchain.cmake> \
      -DCMAKE_INSTALL_PREFIX=<install_path> \
      -DBUILD_opencv_core=ON \
      -DBUILD_opencv_imgproc=ON \
      -DBUILD_opencv_imgcodecs=ON \
      -DBUILD_opencv_highgui=OFF \
      -DBUILD_opencv_videoio=OFF \
      -DWITH_JPEG=ON \
      -DWITH_PNG=ON \
      -DWITH_TIFF=OFF \
      ..

make -j4
make install
优化建议: 如果板子支持 NEON 指令集,记得开启 -DENABLE_NEON=ON,图像缩放和颜色转换能快 30% 以上。

3.4.2 常用操作示例

在板子上,我经常这样处理图像:

cv::Mat img = cv::imread("input.jpg");
cv::Mat resized, blob;

// 缩放并保持宽高比
cv::resize(img, resized, cv::Size(640, 640));

// 归一化并转为浮点
resized.convertTo(blob, CV_32FC3, 1.0 / 255.0);

// 后处理:NMS
std::vector<cv::Rect> boxes;
std::vector<float> scores;
cv::dnn::NMSBoxes(boxes, scores, 0.5, 0.45);

注意,OpenCV 的 dnn::NMSBoxes 在 ARM 上性能一般,我后来改用手写的 NMS,速度提升了 2 倍。

3.5 整体环境结构图

下面这张图,帮你理清各个组件之间的关系:

嵌入式 YOLO 开发环境结构图 嵌入式 YOLO 开发环境结构 开发机(x86) 交叉编译工具链 编译产物 嵌入式板子(ARM) 运行环境 Linux / JetPack ONNX Runtime(CPU 推理) TensorRT(GPU 推理) OpenCV(图像预处理 / 后处理) 硬件平台(CPU / GPU / NPU) 说明 • 开发机编译 • 板子运行 • 按需选择引擎 • OpenCV 通用

3.6 环境验证清单

配完环境后,我建议按这个清单逐项验证,避免遗漏:

组件 验证方法 预期结果
交叉编译工具链 aarch64-linux-gnu-gcc --version 显示版本信息
ONNX Runtime 运行测试程序加载 ONNX 模型 加载成功,无报错
TensorRT trtexec --version 显示版本号
OpenCV 运行图像读取 + 缩放程序 输出正确尺寸的图像
我的经验: 环境搭建最忌讳「一步到位」。我每次都是先配工具链,编译一个 Hello World 跑通;再配 OpenCV,能读写图片;最后才上 ONNX Runtime 和 TensorRT。这样出了问题,能快速定位是哪一层的问题。