昇腾边缘 AI 实战:PC 端交叉编译 OpenCV 源码并同步至 310B4 驱动 Python 推理

📝 场景描述

在 Ascend 310B4 部署中,为了追求极致的运行效率和环境纯净度,我们采用“PC 端交叉编译、板端直接运行”的模式。本文将演示如何使用 aarch64-target-linux-gnu 工具链定制 OpenCV 库,并在 Jupyter Lab 中直接加载 .om 模型完成姿态检测任务。

🛠️ 第一步:获取 OpenCV 源码(国内高速镜像)

针对 GitHub 访问不畅的问题,我们使用 GitCode 镜像。

# 进入工作目录
cd /mnt/d/work/Ascend/

# 使用 GitCode 高速下载 OpenCV 源码
git clone https://gitcode.com/opencv/opencv.git
cd opencv

# 切换到稳定的 4.5.4 版本
git checkout 4.5.4

# 创建构建目录
mkdir build && cd build

image


⚙️ 第二步:核心配置(PC 交叉编译魔法)

我们需要将编译器指向图片中的 aarch64-target-linux-gnu 工具链。这一步的关键是精简模块,只保留核心视觉功能。

# 1. 设置工具链根目录 (根据您的 D 盘路径映射)
export TOOLCHAIN_HOME=/mnt/d/work/Ascend/CANN/toolchain
export PATH=$TOOLCHAIN_HOME/bin:$PATH

# 2. 执行 CMake 交叉编译配置
cmake .. \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_SYSTEM_PROCESSOR=aarch64 \
-DCMAKE_C_COMPILER=aarch64-target-linux-gnu-gcc \
-DCMAKE_CXX_COMPILER=aarch64-target-linux-gnu-g++ \
-DCMAKE_FIND_ROOT_PATH=$TOOLCHAIN_HOME/sysroot \
-DCMAKE_INSTALL_PREFIX=./install \
-DBUILD_opencv_world=ON \
-DBUILD_SHARED_LIBS=ON \
-DWITH_GTK=OFF \
-DWITH_QT=OFF \
-DBUILD_EXAMPLES=OFF \
-DBUILD_TESTS=OFF \
-DBUILD_PERF_TESTS=OFF \
-DWITH_IPP=OFF \
-DVIDEOIO_ENABLE_PLUGINS=OFF

image

💡 配置解析: BUILD_opencv_world 将所有库合并为一个 .so,极大简化了后续在板子上的库挂载。

🔨 第三步:编译与同步

在 PC 上利用多核优势快速完成编译:

# 编译
make -j8

# 将编译好的头文件和库提取到 install 文件夹 
make install

# 将生成的 install 文件夹打包
tar -czvf opencv_arm64.tar.gz install/

image

image

image

随后将 opencv_arm64.tar.gz 拷贝到 310B4 开发板的 ~/userdata/ 目录下。

🚀 第四步:开发板项目构建与一键部署

在 PC 端交叉编译完成后,我们得到的是一个 opencv_arm64.tar.gz。为了实现“解压即用”,我们需要在板子上进行一次标准化的目录对齐。

1. 准备工作:解压与目录对齐

首先,将压缩包上传至板子的 ~/userdata/(即 /root/userdata/)目录下,然后执行以下指令:

# 进入工作目录
cd /root/userdata/

# 创建项目根文件夹
mkdir -p yolo26

# 将压缩包解压到指定位置(重点:将 install 改名为 opencv_install 保持结构清晰)
tar -zxvf opencv_arm64.tar.gz -C ./yolo26/
mv ./yolo26/install ./yolo26/opencv_install

# 创建其他必要文件夹
mkdir -p /root/userdata/yolo26/{model,scripts}
/root/userdata/yolo26/
├── model/
│   └── yolo26s-pose_aipp.om      # 第一阶段生成的模型文件
├── opencv_install/               # PC端交叉编译出来的 install 文件夹
│   ├── include/
│   └── lib/                      # 存放 libopencv_world.so
├── scripts/
│   └── pose_inference.py         # 核心 Python 推理逻辑
├── test.jpg                      # 测试图片
├── start_jupyter.sh              # 自动化启动脚本
└── yolo26_demo.ipynb             # Jupyter Notebook 演示文件

2. 编写一键启动脚本 (start_jupyter.sh)

在嵌入式端手动配路径非常麻烦。编写此脚本可以自动挂载你交叉编译的 OpenCV 库并启动服务:

cat << 'EOF' > /root/userdata/yolo26/start_jupyter.sh
#!/bin/bash
# 自动定位项目根目录
PROJECT_DIR=$(cd $(dirname $0); pwd)
cd $PROJECT_DIR

echo "--- 正在初始化 yolo26 开发环境 ---"

# 1. 挂载交叉编译的 OpenCV 库
export LD_LIBRARY_PATH=$PROJECT_DIR/opencv_install/lib:$LD_LIBRARY_PATH

# 2. 激活昇腾 CANN 环境变量
if [ -f "/usr/local/Ascend/ascend-toolkit/set_env.sh" ]; then
    source /usr/local/Ascend/ascend-toolkit/set_env.sh
fi

echo " 环境初始化完成,准备启动 Jupyter Lab..."

# 3. 启动服务 (允许远程 IP 访问)
jupyter lab --ip=0.0.0.0 --allow-root --no-browser --port=8888 --notebook-dir=$PROJECT_DIR
EOF

🚀 第五步:远程调试与推理验证

1. 启动服务

在开发板终端执行以下命令:

chmod +x /root/userdata/yolo26/start_jupyter.sh
./root/userdata/yolo26/start_jupyter.sh

image

此时终端会显示一个带有 token 的链接,记录下这个 token

2. PC 端浏览器访问

打开 Windows 浏览器,输入:http://[开发板IP]:8888,输入 token 登录。

比如  http://127.0.0.1:8888/lab?token=fd1abf93ab3608181dd043c11777b67566938ab34aa65de3

就是输入 http://192.168.137.100:8888/lab?token=fd1abf93ab3608181dd043c11777b67566938ab34aa65de3

3. 编写 Notebook 推理代码

yolo26_demo.ipynb 中运行以下代码,验证 NPU 推理与定制化 OpenCV 的联动:

import acl
import cv2
import numpy as np
import matplotlib.pyplot as plt
import os

# ================= 1. 环境初始化 =================
ret = acl.init()
acl.rt.set_device(0)
context, _ = acl.rt.create_context(0)

current_dir = os.getcwd()
MODEL_PATH = os.path.join(current_dir, "model/yolo26s-pose.om") 
model_id, ret = acl.mdl.load_from_file(MODEL_PATH)
model_desc = acl.mdl.create_desc()
acl.mdl.get_desc(model_desc, model_id)

# ================= 2. 预处理  =================
frame = cv2.imread("test.jpg")
h, w = frame.shape[:2]
ratio = min(640/w, 640/h)
new_w, new_h = int(w * ratio), int(h * ratio)
resized = cv2.resize(frame, (new_w, new_h))

canvas = np.full((640, 640, 3), 114, dtype=np.uint8)
pad_w, pad_h = (640 - new_w) // 2, (640 - new_h) // 2
canvas[pad_h:pad_h+new_h, pad_w:pad_w+new_w] = resized

# 归一化与维度转置
img = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
img = np.ascontiguousarray(img.transpose(2, 0, 1))
input_bytes = img.tobytes()
input_size = len(input_bytes)

# ================= 3. NPU 推理 =================
dev_ptr_in, _ = acl.rt.malloc(input_size, 0)
acl.rt.memcpy(dev_ptr_in, input_size, acl.util.bytes_to_ptr(input_bytes), input_size, 1)

dataset_in = acl.mdl.create_dataset()
acl.mdl.add_dataset_buffer(dataset_in, acl.create_data_buffer(dev_ptr_in, input_size))

out_size = acl.mdl.get_output_size_by_index(model_desc, 0)
dev_ptr_out, _ = acl.rt.malloc(out_size, 0)
dataset_out = acl.mdl.create_dataset()
acl.mdl.add_dataset_buffer(dataset_out, acl.create_data_buffer(dev_ptr_out, out_size))

acl.mdl.execute(model_id, dataset_in, dataset_out)

host_ptr_out, _ = acl.rt.malloc_host(out_size)
acl.rt.memcpy(host_ptr_out, out_size, dev_ptr_out, out_size, 2)
output_array = np.frombuffer(acl.util.ptr_to_bytes(host_ptr_out, out_size), dtype=np.float32)

# ================= 4. 骨架连线与渲染 =================
if output_array.size > 0:
    output = output_array.reshape(300, -1)
    valid_poses = output[output[:, 4] > 0.3]
    display_img = canvas.copy()
    
    # 定义 COCO 17点骨架连接关系
    SKELETON = [[16, 14], [14, 12], [17, 15], [15, 13], [12, 13], [6, 12], [7, 13], [6, 7],
                [6, 8], [7, 9], [8, 10], [9, 11], [2, 3], [1, 2], [1, 3], [2, 4], [3, 5], [4, 6], [5, 7]]

    for person in valid_poses:
        offset = 6 if output.shape[1] == 57 else 5
        kpts_flat = person[offset : offset + 51]
        kpts = [[kpts_flat[i*3], kpts_flat[i*3+1], kpts_flat[i*3+2]] for i in range(17)]
        
        # 1. 绘制骨架连线 (关键补全)
        for conn in SKELETON:
            i1, i2 = conn[0]-1, conn[1]-1
            if kpts[i1][2] > 0.5 and kpts[i2][2] > 0.5:
                pt1 = (int(kpts[i1][0]), int(kpts[i1][1]))
                pt2 = (int(kpts[i2][0]), int(kpts[i2][1]))
                cv2.line(display_img, pt1, pt2, (0, 255, 0), 2)
        
        # 2. 绘制关键点圆点
        for kp in kpts:
            if kp[2] > 0.5:
                cv2.circle(display_img, (int(kp[0]), int(kp[1])), 4, (0, 255, 255), -1)

    plt.figure(figsize=(8, 8))
    plt.imshow(cv2.cvtColor(display_img, cv2.COLOR_BGR2RGB))
    plt.title(f"Pose Estimation Result (Detected: {len(valid_poses)})")
    plt.show()

# ================= 5. 资源释放 =================
acl.rt.free(dev_ptr_in)
acl.rt.free(dev_ptr_out)
acl.rt.free_host(host_ptr_out)
acl.mdl.unload(model_id)
print("渲染完成")

image


🚀 总结:为什么这套方案更优?

  • 隔离性:通过 LD_LIBRARY_PATH 局部挂载,不修改板子全局库,避免污染系统环境。

  • 便携性yolo26 文件夹就是一个完整的“离线工作站”,拷贝即运行。

  • 交互性:利用 Jupyter Lab 在 PC 端浏览器直接看 310B4 的推理画面,调试效率比纯命令行提升数倍。


💡 技术贴士: 如果 import cv2 报错,请务必检查 start_jupyter.sh 中的路径是否与你解压的 lib 目录绝对一致。这种“库随项目走”的模式是目前嵌入式 AI 开发的最优实践!

请登录后发表评论

    没有回复内容