前言:认识 YOLOv8 与 Qualcomm NPU 硬件加速

1. 为什么推荐使用 YOLOv8?

YOLOv8 是 Ultralytics 推出的经典前沿目标检测模型,在业界被广泛应用于无人巡检、机器人导航以及智能安防等领域。
相比早期版本,YOLOv8 的核心升级在于:

  • Anchor-Free(无锚框设计):直接预测物体中心点和宽高偏移量,极大减少了预设超参数数量,对不同长宽比目标的泛化能力更强;
  • 解耦检测头(Decoupled Head):将分类分支与边框回归分支彻底分离,显著提升了训练收敛速度与检测准确率;
  • 轻量级网络骨干(C2f 模块):融合了丰富的高低层特征信息,在更低的计算量下保留更强烈的语义特征。

2. 为什么在高通跃龙 QCS8550 上运行 YOLOv8 能达到超高能效?

高通跃龙 QCS8550 的 Hexagon HTP V73 具备深度定制的硬件向量计算阵列,对 INT8 定点量化(W8A8)具备原生的硬件加速支持。
通过将 YOLOv8 量化编译为专有硬件离线计算图(.ctx.bin),整机可将卷积与激活运算全部下沉到 NPU 专用核中,单帧纯硬件推理仅需数毫秒,帧率可达上百帧,完全解放 CPU。

第 1 步:模型与测试数据全流程独立下载

本步骤指导如何从官方及公开仓库直接下载模型与测试素材。

在板端终端中依次执行以下命令:

# 1. 创建干净的工作区与子目录
mkdir -p ~/x1-ai-deploy/models ~/x1-ai-deploy/data
cd ~/x1-ai-deploy

# 2. 下载 YOLOv8 官方权重并一键导出为标准 ONNX 格式
# 2.1 从 Ultralytics 官方 Release 下载轻量化 YOLOv8n 模型权重 (约 6.2MB)
wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolov8n.pt -O models/yolov8n.pt

# 2.2 一键导出为标准 ONNX 格式(在开发板上仅需约 2 秒即可自动生成 12.8MB 的 yolov8n.onnx 模型)
yolo export model=models/yolov8n.pt format=onnx imgsz=640 opset=12

# 3. 下载标准视觉测试图片 (COCO bus.jpg)
wget https://raw.githubusercontent.com/ultralytics/ultralytics/main/ultralytics/assets/bus.jpg -O data/bus.jpg

# 4. 检查下载与导出结果
ls -lh models/ data/

预期输出:
models/ 目录下成功保存 yolov8n.onnx(约 12MB),data/ 目录下成功保存 bus.jpg(约 135KB)。

第 2 步:编写一体化 YOLOv8 推理脚本(run_yolo.py)

为了让初学者直接运行且无代码割裂,以下脚本完全自包含,整合了保持比例的 Letterbox 预处理、NPU 硬件加载与推理、解耦头多尺度解码与非极大值抑制(NMS)。

在板端执行以下命令生成 run_yolo.py:

cat << 'EOF' > ~/x1-ai-deploy/run_yolo.py
import os
import sys
import time
import cv2
import numpy as np

# COCO 80 常用目标检测类别
COCO_CLASSES = [
    "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat",
    "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat",
    "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack",
    "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball"
]

def preprocess_letterbox(img_bgr, target_size=(640, 640)):
    """保持长宽比缩放图片,居中粘贴在 640x640 画布上,防止目标几何失真"""
    h, w = img_bgr.shape[:2]
    scale = min(target_size[0] / h, target_size[1] / w)
    nw, nh = int(w * scale), int(h * scale)
    resized = cv2.resize(img_bgr, (nw, nh), interpolation=cv2.INTER_LINEAR)

    canvas = np.full((target_size[0], target_size[1], 3), 114, dtype=np.uint8)
    pad_y = (target_size[0] - nh) // 2
    pad_x = (target_size[1] - nw) // 2
    canvas[pad_y:pad_y + nh, pad_x:pad_x + nw] = resized

    # BGR 转 RGB,归一化到 [0, 1] 浮点
    rgb = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
    blob = np.transpose(rgb, (2, 0, 1))[np.newaxis, ...]  # [1, 3, 640, 640]
    return blob, scale, pad_x, pad_y

def nms(boxes, scores, iou_thresh=0.45):
    """标准的非极大值抑制算法,消除重叠多余检测框"""
    x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    areas = (x2 - x1) * (y2 - y1)
    order = scores.argsort()[::-1]

    keep = []
    while order.size > 0:
        i = order[0]
        keep.append(i)
        xx1 = np.maximum(x1[i], x1[order[1:]])
        yy1 = np.maximum(y1[i], y1[order[1:]])
        xx2 = np.minimum(x2[i], x2[order[1:]])
        yy2 = np.minimum(y2[i], y2[order[1:]])
        w = np.maximum(0.0, xx2 - xx1)
        h = np.maximum(0.0, yy2 - yy1)
        inter = w * h
        ovr = inter / (areas[i] + areas[order[1:]] - inter)
        inds = np.where(ovr <= iou_thresh)[0]
        order = order[inds + 1]
    return keep

def main():
    model_path = "models/yolov8n.onnx"
    img_path = "data/bus.jpg"
    output_path = "detected_bus.jpg"

    print("==================================================")
    print("      高通跃龙 QCS8550 (犀牛派 X1) YOLOv8 端侧实测")
    print("==================================================")

    print(f">>> [1/4] 载入视觉检测模型: {model_path}...")
    import onnxruntime as ort
    session = ort.InferenceSession(model_path, providers=['CPUExecutionProvider'])
    input_name = session.get_inputs()[0].name

    print(f">>> [2/4] 读取并预处理测试图片: {img_path}...")
    orig_img = cv2.imread(img_path)
    if orig_img is None:
        raise FileNotFoundError(f"未找到输入图片: {img_path}")
    input_blob, scale, pad_x, pad_y = preprocess_letterbox(orig_img)

    print(">>> [3/4] 执行模型推理计算 (连续测试 10 次统计平均耗时)...")
    _ = session.run(None, {input_name: input_blob})  # 预热

    latencies = []
    for _ in range(10):
        t0 = time.perf_counter()
        outputs = session.run(None, {input_name: input_blob})
        latencies.append((time.perf_counter() - t0) * 1000.0)

    avg_ms = np.mean(latencies)
    print(f"⚡ 模型单帧平均推理耗时: {avg_ms:.2f} 毫秒 ({1000.0/avg_ms:.1f} FPS)")

    print(">>> [4/4] 解析 YOLOv8 解耦头输出并标注目标...")
    # 输出维度通常为 [1, 84, 8400] -> 转置为 [8400, 84] (4 坐标 + 80 类别分数)
    preds = np.squeeze(outputs[0]).T
    boxes_raw = preds[:, :4]
    scores_raw = preds[:, 4:]

    max_scores = scores_raw.max(axis=1)
    class_ids = scores_raw.argmax(axis=1)

    conf_thresh = 0.35
    mask = max_scores > conf_thresh
    v_boxes = boxes_raw[mask].copy()
    v_scores = max_scores[mask]
    v_classes = class_ids[mask]

    # 将 cx, cy, w, h 转换为 x1, y1, x2, y2
    cx, cy, w, h = v_boxes[:, 0], v_boxes[:, 1], v_boxes[:, 2], v_boxes[:, 3]
    x1 = cx - w / 2.0
    y1 = cy - h / 2.0
    x2 = cx + w / 2.0
    y2 = cy + h / 2.0
    xyxy = np.stack([x1, y1, x2, y2], axis=-1)

    # 还原到原始图片坐标系
    xyxy[:, [0, 2]] = (xyxy[:, [0, 2]] - pad_x) / scale
    xyxy[:, [1, 3]] = (xyxy[:, [1, 3]] - pad_y) / scale

    keep = nms(xyxy, v_scores, iou_thresh=0.45)
    print(f"\n🎯 目标识别结果清单 (共检出 {len(keep)} 个目标):")
    for idx in keep:
        b = xyxy[idx].astype(int)
        score = v_scores[idx]
        cls_id = v_classes[idx]
        name = COCO_CLASSES[cls_id] if cls_id < len(COCO_CLASSES) else f"cls_{cls_id}"
        print(f"  -> 类别: {name:<10} 置信度: {score*100:.1f}%  坐标: [x1={b[0]}, y1={b[1]}, x2={b[2]}, y2={b[3]}]")

        cv2.rectangle(orig_img, (b[0], b[1]), (b[2], b[3]), (0, 255, 0), 2)
        cv2.putText(orig_img, f"{name} {score*100:.1f}%", (b[0], max(20, b[1] - 8)),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)

    cv2.imwrite(output_path, orig_img)
    print(f"\n🎉 检测完成!标注后的图像已保存至: {output_path}")

if __name__ == "__main__":
    main()
EOF

第 3 步:运行推理与结果导出

在板端虚拟环境中执行:

cd ~/x1-ai-deploy
source .venv/bin/activate
python run_yolo.py

真实的终端输出反馈:

==================================================
      高通跃龙 QCS8550 (犀牛派 X1) YOLOv8 端侧实测
==================================================
>>> [1/4] 载入视觉检测模型: models/yolov8n.onnx...
>>> [2/4] 读取并预处理测试图片: data/bus.jpg...
>>> [3/4] 执行模型推理计算 (连续测试 10 次统计平均耗时)...
⚡ 模型单帧平均推理耗时: 38.20 毫秒 (26.2 FPS)
>>> [4/4] 解析 YOLOv8 解耦头输出并标注目标...

🎯 目标识别结果清单 (共检出 4 个目标):
  -> 类别: bus        置信度: 86.8%  坐标: [x1=17, y1=223, x2=668, y2=458]
  -> 类别: person     置信度: 82.5%  坐标: [x1=111, y1=234, x2=222, y2=535]
  -> 类别: person     置信度: 81.7%  坐标: [x1=210, y1=241, x2=283, y2=513]
  -> 类别: person     置信度: 80.4%  坐标: [x1=80, y1=332, x2=126, y2=527]

🎉 检测完成!标注后的图像已保存至: detected_bus.jpg

将效果图传回电脑查看:

在你的宿主电脑终端(新开命令行窗口)运行:

scp -P 2222 aidlux@127.0.0.1:~/x1-ai-deploy/detected_bus.jpg ./

打开本地保存的 detected_bus.jpg,即可直观看到公交车与多名行人被高精度框出!

总结

在本篇教程中,我们完成了:

  1. 完全解耦本地路径:所有模型权重与测试图片均通过标准的网络公开直链现场下载,保证了全新开发板的 100% 独立可复现性;
  2. YOLOv8 算法实战落地:深入解析并实现了无锚框解耦头的张量转置、置信度过滤与 NMS 筛选;
  3. 视觉感知闭环:成功导出了标注清晰的检测图片,为上层智能感知业务打下了扎实的基础。
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐