4. 开发环境搭建:交叉编译工具链、OpenCV、ONNX Runtime、TensorRT、NCNN的嵌入式移植
说实话,嵌入式AI开发最磨人的不是算法本身,而是环境搭建。我见过太多同学在PC上跑得飞起,一上板子就各种链接错误、段错误。这一章,咱们就把这些坑一个个填平。
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。能正常输出版本号,说明工具链活了。
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
编译产物在 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
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!”,恭喜你,环境搭建成功了。
嗯,到这里,环境搭建的核心内容就讲完了。记住一句话:交叉编译没有捷径,每一步都得亲自踩一遍。我当年第一次搭环境,整整折腾了三天。但只要你按照上面的步骤来,应该能控制在半天内搞定。