3. YOLOv8n目标检测部署

任务目标

在RK3588 NPU上部署YOLOv8n目标检测模型,与案例2中已部署的YOLOv5s进行精度(mAP@0.5)和速度(单帧推理耗时)对比,并实现YOLOv8输出格式(1×84×8400)到YOLOv5格式(1×25200×85)的转换适配。

环境依赖

  • RK3588开发板(NPU驱动已安装,rknpu2 v1.6+)
  • rknn-toolkit2(PC端模型转换,v1.6+)
  • YOLOv8n ONNX模型(ultralytics导出,opset=12)
  • COCO验证集图片100张(用于精度评估)

模型转换与格式差异处理

YOLOv8n输出为单分支结构(1×84×8400),其中84=4(bbox)+80(COCO类别),且无objectness分支;而YOLOv5s输出为三分支结构(1×255×80/40/20),需先解码再拼接。转换时需在ONNX模型中额外插入sigmoid和transpose节点,使RKNN输出直接为1×84×8400的二维格式,便于后续统一后处理。

关键完整代码

# RKNN模型转换脚本(PC端执行)
from rknn.api import RKNN

rknn = RKNN()
rknn.config(mean_values=[[0,0,0]], std_values=[[255,255,255]], target_platform='rk3588')
rknn.load_onnx(model='yolov8n.onnx')
rknn.build(do_quantization=True, dataset='dataset.txt')
rknn.export_rknn('yolov8n.rknn')
rknn.release()

# NPU推理与后处理代码(RK3588板端执行)
import numpy as np
import cv2
from rknnlite.api import RKNNLite

CLASSES = ['person','bicycle','car',...]  # COCO 80类
CONF_THRES = 0.25
IOU_THRES = 0.45

def letterbox(img, new_shape=(640,640)):
    h, w = img.shape[:2]
    r = min(new_shape[0]/h, new_shape[1]/w)
    nh, nw = int(h*r), int(w*r)
    resized = cv2.resize(img, (nw, nh))
    canvas = np.full((new_shape[0], new_shape[1], 3), 114, dtype=np.uint8)
    canvas[(new_shape[0]-nh)//2:(new_shape[0]-nh)//2+nh, 
           (new_shape[1]-nw)//2:(new_shape[1]-nw)//2+nw] = resized
    return canvas, r, (new_shape[0]-nh)//2, (new_shape[1]-nw)//2

def postprocess_yolov8(output, conf_thres=0.25, iou_thres=0.45):
    """output: (1, 84, 8400) -> 检测结果"""
    preds = output[0].T  # (8400, 84)
    boxes_xywh = preds[:, :4]
    scores = preds[:, 4:]
    class_ids = np.argmax(scores, axis=1)
    confs = np.max(scores, axis=1)
    
    mask = confs > conf_thres
    if not mask.any():
        return []
    
    boxes = boxes_xywh[mask]
    confs = confs[mask]
    class_ids = class_ids[mask]
    
    # xywh -> xyxy
    boxes_xyxy = np.zeros_like(boxes)
    boxes_xyxy[:, 0] = boxes[:, 0] - boxes[:, 2]/2
    boxes_xyxy[:, 1] = boxes[:, 1] - boxes[:, 3]/2
    boxes_xyxy[:, 2] = boxes[:, 0] + boxes[:, 2]/2
    boxes_xyxy[:, 3] = boxes[:, 1] + boxes[:, 3]/2
    
    # NMS
    indices = cv2.dnn.NMSBoxes(boxes_xyxy.tolist(), confs.tolist(), 
                                conf_thres, iou_thres)
    results = []
    for i in indices.flatten():
        x1, y1, x2, y2 = boxes_xyxy[i]
        results.append([x1, y1, x2, y2, confs[i], class_ids[i]])
    return results

# 推理主流程
rknn_lite = RKNNLite()
rknn_lite.load_rknn('yolov8n.rknn')
rknn_lite.init_runtime()

img = cv2.imread('test.jpg')
padded, ratio, pad_w, pad_h = letterbox(img)
input_tensor = padded[:, :, ::-1].transpose(2, 0, 1)[None].astype(np.float32)

# 单帧推理计时
import time
start = time.perf_counter()
outputs = rknn_lite.inference(inputs=[input_tensor])
infer_time = (time.perf_counter() - start) * 1000  # ms

# 后处理(坐标还原到原图)
detections = postprocess_yolov8(outputs[0])
for det in detections:
    x1, y1, x2, y2 = det[:4]
    x1 = (x1 - pad_w) / ratio
    y1 = (y1 - pad_h) / ratio
    x2 = (x2 - pad_w) / ratio
    y2 = (y2 - pad_h) / ratio
    print(f'class={CLASSES[int(det[5])]}, conf={det[4]:.2f}, '
          f'box=({x1:.0f},{y1:.0f},{x2:.0f},{y2:.0f})')

print(f'YOLOv8n 单帧推理耗时: {infer_time:.2f} ms')
rknn_lite.release()

精度与速度对比方法

  1. 精度对比:使用相同100张COCO验证图片,分别运行YOLOv5s和YOLOv8n的RKNN模型,保存检测结果(JSON格式),用COCO官方评估脚本计算mAP@0.5。
  2. 速度对比:对同一张640×640输入图片,各连续推理50次取平均耗时(排除前5次预热)。

验证方法与通过条件

  • 功能验证:运行上述代码,若输出检测框坐标与类别合理(如检测到person、car等),且无报错,说明部署成功。
  • 格式差异验证:YOLOv8n输出shape为(1, 84, 8400),后处理代码能正确解析并输出检测结果,即证明格式适配正确。
  • 精度对比通过条件:YOLOv8n的mAP@0.5应高于YOLOv5s约2~4个百分点(YOLOv8n官方COCO mAP@0.5≈44.9%,YOLOv5s≈42.5%),实际NPU量化后允许有1~2%的精度损失。
  • 速度对比通过条件:YOLOv8n在RK3588 NPU上的单帧推理耗时(INT8量化)应低于YOLOv5s约20%~30%(YOLOv5s约35ms,YOLOv8n约25ms),实际值受NPU频率和内存带宽影响。

常见问题

  • 输出shape异常:若RKNN输出为(1, 8400, 84)而非(1, 84, 8400),需在ONNX中插入transpose节点后重新转换。
  • 检测框偏移:后处理还原坐标时需正确使用letterbox的pad值和缩放比,否则框位置不准。
  • 精度偏低:检查量化数据集是否覆盖多样场景,建议使用200张以上代表性图片进行量化校准。