一、搭建交叉编译环境(以AArch64 Linux 为目标平台,以YOLOv5为例,操作大致相同)

本文为部署的全流程记录,有不妥之处请狠狠批判😭

需要工具:

sudo apt install cmake git g++ build-essential

(一)、获取交叉编译工具:

在此之前使用ldd --version查看适配的GCC版本,如果版本不对需要全部重新用对应版本工具链编译

  1. 在Ubuntu 系统可直接通过apt安装:
   sudo apt-get install gcc-aarch64-linux-gnu #apt获取
   sudo apt --fix-broken install #如果遇到依赖问题,可以运行该命令修复
  1. 从源码构建:

    (1)下载路径:

    Arm GNU 工具链下载 – Arm 开发者

选择合适版本下载,本文中使用的版本为arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu,在x86的Linux平台使用的交叉编译工具

​ (2)下载后解压:

cd 下载后存放的目录/
tar -xvf arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu.tar.xz -C 解压到哪个目录

​ (3)将工具链加入 PATH:

echo 'export PATH=$工具链目录的位置/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
aarch64-none-linux-gnu-gcc --version #输出类似gcc version 15.2.1 20251203 (Arm GNU Toolchain 15.2.Rel1 (Build arm-15.86)) 等信息证明工具链已正确安装

(二)、获取并编译合适架构的MNN

  1. 获取MNN源码:

    mkdir AArch64_Linux_code && cd AArch64_Linux_code
    git clone https://github.com/alibaba/MNN.git #大概需要翻墙
    #也可通过git clone https://gitee.com/mirrors/mnn.git下载,不需要翻墙
    

    对于编译MNN源码对于cmake(3.10 以上)protobuf (3.0 以上)有要求,需要先执行:

    sudo apt update
    sudo apt install protobuf-compiler libprotobuf-dev
    
  2. 构建MNN框架:

cd /path/to/MNN
./schema/generate.sh

./tools/script/get_model.sh # 这句可选,模型仅demo工程需要,需要先编译出可执行文件(执行步骤三后),目的是得到.mnn文件,电脑上执行这句需要x86编译而非arm编译
  1. 在/path/to/MNN/CMakeLists.txt下更改编译架构:

    加入:

    set(CMAKE_SYSTEM_NAME Linux)
    set(CMAKE_SYSTEM_PROCESSOR aarch64)
    
    set(CMAKE_C_COMPILER "交叉编译工具链目录的位置/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc")
    set(CMAKE_CXX_COMPILER "交叉编译工具链目录的位置/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-g++")
    

    位置如下

    请添加图片描述

  2. 准备KleidiAI文件:

    cd /path/to/MNN
    git clone https://github.com/ARM-software/kleidiai.git
    
  3. 编译:

    编译宏看:编译宏介绍 — MNN-Doc 2.1.1 documentation

    需要一些工具,此处先构建MNN的转换工具、测试工具、性能测试工具、量化工具,按需求看编译宏选择开启

    mkdir build && cd build #构建build目录
    cmake .. \
      -DMNN_BUILD_CONVERTER=ON \
      -DMNN_BUILD_TOOLS=ON \
      -DMNN_BUILD_BENCHMARK=ON \
      -DMNN_BUILD_QUANTOOLS=ON \
      -DMNN_EVALUATION=ON \
      -DKLEIDIAI_SRC_DIR=/path/to/MNN/kleidiai #kleidiai有的架构可能不支持
    
    make -j8 #选择用几个核进行编译,看自己需要,过多会卡死
    

​ cmake命令执行完后观察是否出现如:

-- Found assembler: /home/ykb/Raspberry_PI_code/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /home/ykb/Raspberry_PI_code/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /home/ykb/Raspberry_PI_code/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done

确认工具链选择正确即可

编译完成后执行:

file libMNN.so

观察到

libMNN.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (GNU/Linux), dynamically linked, with debug_info, not stripped

出现信息表示为arm架构即可

(三)、编译openCV arm架构并部署

下载openCV源码https://opencv.org/releases/

请添加图片描述

这里选择的是4.8.1版

更改opencv-4.8.1/platforms/linux/aarch64-gnu.toolchain.cmake:

set(CMAKE_SYSTEM_PROCESSOR aarch64)

set(GCC_COMPILER_VERSION "" CACHE STRING "GCC Compiler version")

set(GNU_MACHINE "aarch64-linux-gnu" CACHE STRING "GNU compiler triple")

include("${CMAKE_CURRENT_LIST_DIR}/arm.toolchain.cmake")

改为:

set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(GCC_COMPILER_VERSION "" CACHE STRING "GCC Compiler version")
set(CMAKE_C_COMPILER 交叉编译工具链目录的位置/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER 交叉编译工具链目录的位置/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-linux-gnu-g++)
set(GNU_MACHINE "aarch64-linux-gnu" CACHE STRING "GNU compiler triple")
include("${CMAKE_CURRENT_LIST_DIR}/arm.toolchain.cmake")

然后在终端中:

cd openCV源码目录\

mkdir build && mkdir arm_openCV_lib

cd build

cmake -DCMAKE_MAKE_PROGRAM:PATH=/usr/bin/make \
-DCMAKE_INSTALL_PREFIX=../arm_openCV_lib  \
-DWITH_CUDA=OFF \
-DENABLE_PRECOMPILED_HEADERS=OFF \
-DCMAKE_TOOLCHAIN_FILE=../platforms/linux/aarch64-gnu.toolchain.cmake \
..

观察到

......

--     C++ Compiler:                /home/ykb/Raspberry_PI_code/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-g++  (ver 15.2.1)

......

--     C Compiler:                  /home/ykb/Raspberry_PI_code/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc

......

证明成功

然后

make -j8

等待编译

完成后

file bin/opencv_test_core 
输出类似bin/opencv_test_core: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, with debug_info, not stripped
表示成功

然后输入:

make install

完成之后将arm_openCV_lib传到板卡上

scp -r arm_openCV_lib  板卡用户名@板卡ip:板卡的目标目录#或者压缩后Windows端用软件传入
#配置库路径
cd /etc/
#看一下有没有ld.so.conf.d目录,没有就创建
#mkdir ld.so.conf.d
cd ld.so.conf.d
sudo touch opencv.conf
sudo vim opencv.conf
#在opencv.conf输入arm_openCV_lib的绝对路径/lib,如/home/root/arm_openCV_lib/lib
sudo ldconfig
#没有报错就说明库的配置完成

(四)、编译pymnn并构建包,需要python时使用(可选)

pymnn是mnn的python API

在当前的build目录

cd ../pymnn/pip_package/

注意:找到MNN/package_scripts/linux/build_whl.sh,根据需要修改其中的内容

重要:对于大部分虚拟机,将31行

cmake $CMAKE_ARGS .. && make MNN MNNTrain MNNConvert MNNOpenCV -j24

改为

cmake $CMAKE_ARGS .. && make MNN MNNTrain MNNConvert MNNOpenCV -j8

不然容易卡死

【Conda】超详细的linux-conda环境安装教程_linux 安装conda-CSDN博客

建议使用Conda,防止冲突,板卡上使用miniConda

python build_deps.py
#python setup.py install --version {MNN版本} #这个是本地构建x86架构并安装,此处为arm架构无法安装
#python setup.py install --version 3.3.1 
#目前下载的版本都为3.3.1,具体版本在x86版本的build目录下执行./MNNConvert --version可知

# 这里选择only CPU后端
cd mnn的arm架构根目录/
./package_scripts/linux/build_whl.sh -v 3.3.1  -o MNN-CPU/py_whl

官方示例:Pymnn构建 — MNN-Doc 2.1.1 documentation

#本地安装
cd /path/to/MNN/pymnn/pip_package
python build_deps.py {MNN依赖包组合} #internal,cuda,trt,cuda_tune,opencl,vulkan,render,no_sse,torch,openmp,llm这几个字符串的任意组合,例如字符串可为:"cuda,reder,no_sse"
python setup.py install --version {MNN版本} --deps {MNN依赖包组合}

#构建Python Wheel包
# only CPU后端
./package_scripts/linux/build_whl.sh -v {MNN版本} -o MNN-CPU/py_whl
# CPU+OpenCL后端
./package_scripts/linux/build_whl.sh -v {MNN版本} -o MNN-CPU-OPENCL/py_whl -b

之后把这个包搬到板卡上就行

(五)、转换模型

先构建x86架构的MNN源码:

mkdir x86_MNN_code && cd x86_MNN_code
git clone https://github.com/alibaba/MNN.git #大概需要翻墙
#也可通过git clone https://gitee.com/mirrors/mnn.git下载,不需要翻墙
cd MNN
./schema/generate.sh
mkdir build && cd build #构建build目录
cmake .. \
  -DMNN_BUILD_CONVERTER=ON \
  -DMNN_BUILD_TOOLS=ON \
  -DMNN_BUILD_BENCHMARK=ON \
  -DMNN_BUILD_QUANTOOLS=ON \
  -DMNN_EVALUATION=ON 
make -j8

以YOLO5为例:

1.获取yolo5源码:

git clone https://github.com/ultralytics/yolov5.git

获取.pt文件Release v7.0 - YOLOv5 SOTA Realtime Instance Segmentation · ultralytics/yolov5选一个喜欢的:

请添加图片描述
这里选择了yolov5x(因为最大),其他的也行,然后丢到yolo目录下
请添加图片描述
2.转化模型

先从pt转化为onnx:

cd yolo根目录
pip install -r requirements.txt 
python export.py --weights=./yolov5x.pt --include=onnx --img=640 --batch=1 --opset=12 --simplify

出现:

请添加图片描述
请添加图片描述

从onnx转化为mnn:

cd yolov5
./x86的mnn目录/build/MNNConvert -f ONNX --modelFile yolo根目录/yolov5.onnx --MNNModel ./yolov5.mnn

请添加图片描述

(六)、部署MNN框架

在板卡上创建目录:

mkdir -p ~/MNN_lib/express
mkdir -p ~/onnxruntime_lib/lib
mkdir ~/test_code

将动态库传到板卡上:

scp -r arm架构MNN目录/MNN/build/libMNN.so 板卡用户名@板卡ip:板卡的目标目录
scp -r arm架构MNN目录/MNN/build/express/libMNN_Express.so 板卡用户名@板卡ip:板卡的目标目录
#例:scp -r /home/ykb/Raspberry_PI_code/MNN/build/libMNN.so linaro@192.168.0.51:~/MNN_lib/

设置MNN动态库:

sudo vim /etc/ld.so.conf.d/MNN_lib.conf

写入库路径,如,即库的绝对路径:

/home/linaro/MNN_lib/

/home/linaro/MNN_lib/express/

然后终端输入:

sudo ldconfig
#没有报错就说明库的配置完成

(七)、交叉编译并运行代码:

在Ubuntu中,执行

mkdir -p test_code/build/

创建文件infer_mnn.cpp

#include <iostream>
#include <chrono>
#include <opencv2/opencv.hpp>
#include <MNN/Interpreter.hpp>
#include <MNN/ImageProcess.hpp>

using namespace std;
using namespace MNN;

int main() {
    // 1. 加载模型
    const char* model_path = "yolov5.mnn";
    std::shared_ptr<Interpreter> net(Interpreter::createFromFile(model_path));
    if (net == nullptr) {
        cerr << "无法加载 MNN 模型" << endl;
        return -1;
    }

    // 2. 配置 Session
    ScheduleConfig config;
    config.type = MNN_FORWARD_CPU; // 在树莓派上使用 CPU
    config.numThread = 4;          // 树莓派通常是4核
    Session* session = net->createSession(config);

    // 获取输入输出 Tensor
    Tensor* input_tensor = net->getSessionInput(session, nullptr);
    // 假设输入尺寸为 640x640,如果模型是动态的,需要 resize
    net->resizeTensor(input_tensor, {1, 3, 640, 640});
    net->resizeSession(session);

    // 3. 图像预处理 (使用 MNN::CV::ImageProcess 加速)
    cv::Mat img = cv::imread("data.jpg");
    if (img.empty()) {
        cerr << "无法读取 data.jpg" << endl;
        return -1;
    }

    // 配置预处理参数 (YOLOv5: RGB, 0-255 -> 0-1, mean=[0,0,0], normal=[1/255, 1/255, 1/255])
    CV::ImageProcess::Config process_config;
    process_config.filterType = CV::BILINEAR;
    // BGR 转 RGB
    process_config.sourceFormat = CV::BGR; 
    process_config.destFormat = CV::RGB;
    
    // Normalization: (x - mean) * normal
    ::memcpy(process_config.mean, new float[3]{0.0f, 0.0f, 0.0f}, 3 * sizeof(float));
    ::memcpy(process_config.normal, new float[3]{1.0f/255.0f, 1.0f/255.0f, 1.0f/255.0f}, 3 * sizeof(float));

    std::shared_ptr<CV::ImageProcess> pretreat(CV::ImageProcess::create(process_config));
    
    // 执行预处理:从 cv::Mat 转换并拷贝到 MNN input_tensor
    // MNN 的 ImageProcess 会自动处理 Resize
    pretreat->convert(img.data, 640, 640, 0, input_tensor);

    // 4. 执行推理并计时
    cout << "开始 MNN 推理..." << endl;
    auto start = chrono::high_resolution_clock::now();

    net->runSession(session);

    auto end = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::milliseconds>(end - start);
    cout << "MNN 处理时间: " << duration.count() << " ms" << endl;

    // 5. 获取输出
    Tensor* output_tensor = net->getSessionOutput(session, nullptr);
    
    // 如果输出在 Device 上(例如 GPU),需要拷贝到 Host
    std::shared_ptr<Tensor> output_host(new Tensor(output_tensor, Tensor::CAFFE));
    output_tensor->copyToHostTensor(output_host.get());

    auto shape = output_host->shape();
    cout << "推理成功完成,输出维度: ";
    for (int s : shape) cout << s << " ";
    cout << endl;

    // 此处 output_host->host<float>() 即为推理结果数组指针
    
    return 0;
}

创建文件CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

# =========================================================
# 1. 交叉编译配置 (必须在 project() 之前!)
# =========================================================
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

#交叉编译工具链路径
set(TOOLCHAIN_HOME "/home/ykb/Raspberry_PI_code/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu")
set(CMAKE_C_COMPILER "${TOOLCHAIN_HOME}/bin/aarch64-none-linux-gnu-gcc")
set(CMAKE_CXX_COMPILER "${TOOLCHAIN_HOME}/bin/aarch64-none-linux-gnu-g++")

# 强制 cmake 不去宿主机 /usr/lib 找库
set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_HOME})
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_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

# =========================================================
# 2. 项目定义
# =========================================================
project(YOLOv5_Inference)

set(CMAKE_CXX_STANDARD 14) # MNN和ORT通常C++14或17都可以

# =========================================================
# 3. 依赖库路径设置
# =========================================================

# --- A. OpenMP (MNN 需要) ---
find_package(OpenMP REQUIRED)

# --- B. OpenCV (ARM64版本) ---
# 这里的路径要指向编译安装的 opencv-4.8.1/build 目录
set(OpenCV_DIR "/home/ykb/Raspberry_PI_code/opencv-4.8.1/build")
find_package(OpenCV REQUIRED)
message(STATUS ">> OpenCV Found: ${OpenCV_DIR}")

# --- C. MNN (ARM64版本) ---
# [TODO] 指向 MNN 根目录
set(MNN_ROOT "/home/ykb/Raspberry_PI_code/MNN")

# MNN 头文件
include_directories(${MNN_ROOT}/include)

# MNN 库文件路径 (假设你是在 build 目录下编译生成的 libMNN.so)
# 如果是静态库可能是 libMNN.a
link_directories(${MNN_ROOT}/build) 


# =========================================================
# 4. 编译目标
# =========================================================

# --- 目标 1: MNN 推理 ---
add_executable(yolo_mnn infer_mnn.cpp)
target_link_libraries(yolo_mnn
    OpenMP::OpenMP_CXX
    MNN            
    ${OpenCV_LIBS}
    pthread
    dl
)

在终端:

cd build
cmake ..
make
scp 生成的二进制文件名 板卡用户名@板卡ip:板卡的目标目录
#例scp yolo_* linaro@192.168.0.51:/home/linaro/test_code/
scp .mnn文件(之前放在yolo根目录下) 板卡用户名@板卡ip:板卡的目标目录

至此全部完成

(base) linaro@linaro-alip:~/MN_IN/cpp_test$ ./yolo_mnn
CPU Group: [ 0  1  2  3 ], 408000 - 1800000
CPU Group: [ 6  7 ], 408000 - 2256000
CPU Group: [ 4  5 ], 408000 - 2304000
The device supports: i8sdot:1, fp16:1, i8mm: 0, sve2: 0, sme2: 0
开始 MNN 推理...
MNN 处理时间: 1607 ms
推理成功完成,输出维度: 1 25200 85

(base) linaro@linaro-alip:~/MN_IN/cpp_test$ ./yolo_onnx
2026-02-12 10:16:08.298970182 [W:onnxruntime:YOLOv5_ONNX, device_discovery.cc:211 DiscoverDevicesForPlatform] GPU device discovery failed: device_discovery.cc:91 ReadFileContents Failed to open file: "/sys/class/drm/card1/device/vendor"
开始 ONNX 推理...
ONNX 处理时间: 8045 ms
推理成功完成,输出维度: 1x25200x85

实测使用mnn可提高5倍左右的推理速度,相当可观了
上方是部署到RK3588上的结果,未使用npu,仅cpu跑的结果,本意是想试试部署到树莓派上的效果的,当时没找着树莓派,不过也一样证明了mnn的有效性

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐