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()
精度与速度对比方法
- 精度对比:使用相同100张COCO验证图片,分别运行YOLOv5s和YOLOv8n的RKNN模型,保存检测结果(JSON格式),用COCO官方评估脚本计算mAP@0.5。
- 速度对比:对同一张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张以上代表性图片进行量化校准。