【上篇回顾】
上一篇我们完成了开发环境搭建,验证了QNN EP可以正常调用NPU,并了解了QNN SDK v3.0的新特性。本篇将把这一切用到真实场景中——让X2 Elite的摄像头“看懂”世界。

一、场景描述

在骁龙X2 Elite平台上实现实时目标检测 + 语义分割流水线,目标:

  • 摄像头输入 1080P @ 30fps
  • YOLOv8n目标检测 + 轻量语义分割模型
  • 端到端延迟 < 33ms(保证实时性)
  • NPU推理,CPU做前后处理

二、环境准备

请确保已按照第二篇完成环境搭建,并额外安装以下依赖:

# 安装ONNX Runtime with QNN EP
pip install onnxruntime-qnn

# 安装OpenCV (ARM64 native)
pip install opencv-python

# 安装模型转换工具
pip install ultralytics onnx onnxsim

验证NPU可用性:

import onnxruntime as ort
print(ort.get_available_providers())
# 应包含 'QNNExecutionProvider'

三、模型准备与INT8量化

3.1 导出ONNX模型

from ultralytics import YOLO

# Step 1: 导出ONNX模型
model = YOLO('yolov8n.pt')
model.export(format='onnx', imgsz=640, opset=17, simplify=True)

3.2 生成校准数据(静态量化)

import numpy as np
import cv2
import glob

def create_calibration_data(image_dir, num_samples=100):
    """生成NPU量化校准数据"""
    calib_data = []
    images = glob.glob(f"{image_dir}/*.jpg")[:num_samples]
    
    for img_path in images:
        img = cv2.imread(img_path)
        img = cv2.resize(img, (640, 640))
        img = img.astype(np.float32) / 255.0
        img = np.transpose(img, (2, 0, 1))  # HWC -> CHW
        calib_data.append(np.expand_dims(img, axis=0))
    
    return calib_data

3.3 使用QNN工具链进行INT8量化

qnn-onnx-converter \
    --input_network yolov8n.onnx \
    --output_path yolov8n_qnn.bin \
    --input_layout NCHW \
    --quantization_overrides quantization_config.json

其中 quantization_config.json 示例:

{
    "quantization_mode": "static",
    "activation_bit_width": 8,
    "weight_bit_width": 8,
    "calibration_data_dir": "./calib_images",
    "calibration_method": "percentile",
    "percentile_value": 99.99
}

四、NPU推理Pipeline完整实现

以下是完整的 X2EliteVisionPipeline 类,包含 CPU预处理线程池NPU推理后处理 以及 实时循环

import onnxruntime as ort
import cv2
import numpy as np
import time
from concurrent.futures import ThreadPoolExecutor

class X2EliteVisionPipeline:
    """基于X2 Elite NPU的实时视觉推理管线"""
    
    def __init__(self, model_path: str, conf_threshold: float = 0.5):
        self.conf_threshold = conf_threshold
        
        # 配置QNN Execution Provider (NPU加速)
        sess_options = ort.SessionOptions()
        sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
        
        # X2 Elite 专属 QNN 配置
        self.qnn_options = {
            "backend_path": "QnnHtp.dll",           # NPU后端
            "htp_performance_mode": "burst",        # 高性能模式
            "htp_graph_finalization_optimization_mode": "3",
            "enable_htp_fp16_precision": "1",       # FP16精度
            "qnn_context_cache_enable": "1",        # 启用缓存
            "qnn_context_cache_path": "./cache/yolov8n_cache.bin",
            "htp_arch": "77",                       # Hexagon V77
        }
        providers = [
            ("QNNExecutionProvider", self.qnn_options),
            "CPUExecutionProvider"                   # 回退到CPU
        ]
        
        self.session = ort.InferenceSession(
            model_path, sess_options, providers=providers
        )
        self.input_name = self.session.get_inputs()[0].name
        self.input_shape = self.session.get_inputs()[0].shape
        
        # 预处理线程池(CPU并行处理,实现流水线)
        self.preprocess_pool = ThreadPoolExecutor(max_workers=2)
        
        print(f"[X2 Elite] 模型加载完成,推理后端:{self.session.get_providers()}")
        print(f"[X2 Elite] 输入尺寸:{self.input_shape}")
    
    def preprocess(self, frame: np.ndarray) -> np.ndarray:
        """图像预处理 - 在Oryon CPU上执行"""
        img = cv2.resize(frame, (640, 640))
        img = img.astype(np.float32) / 255.0
        img = np.transpose(img, (2, 0, 1))  # HWC -> CHW
        return np.expand_dims(img, axis=0)
    
    def postprocess(self, outputs, original_shape):
        """后处理 - NMS和坐标映射"""
        predictions = outputs[0][0]  # [num_detections, 6]
        
        boxes = []
        h_orig, w_orig = original_shape[:2]
        scale_x, scale_y = w_orig / 640, h_orig / 640
        
        for pred in predictions.T:
            x, y, w, h = pred[:4]
            scores = pred[4:]
            class_id = np.argmax(scores)
            confidence = scores[class_id]
            
            if confidence > self.conf_threshold:
                x1 = int((x - w/2) * scale_x)
                y1 = int((y - h/2) * scale_y)
                x2 = int((x + w/2) * scale_x)
                y2 = int((y + h/2) * scale_y)
                boxes.append({
                    'bbox': [x1, y1, x2, y2],
                    'class_id': int(class_id),
                    'confidence': float(confidence)
                })
        
        return boxes
    
    def infer(self, frame: np.ndarray) -> dict:
        """单帧推理 - NPU加速,CPU预处理/后处理"""
        t0 = time.perf_counter()
        
        # CPU预处理
        input_tensor = self.preprocess(frame)
        t1 = time.perf_counter()
        
        # NPU推理
        outputs = self.session.run(None, {self.input_name: input_tensor})
        t2 = time.perf_counter()
        
        # CPU后处理
        detections = self.postprocess(outputs, frame.shape)
        t3 = time.perf_counter()
        
        return {
            'detections': detections,
            'timing': {
                'preprocess_ms': (t1 - t0) * 1000,
                'inference_ms': (t2 - t1) * 1000,
                'postprocess_ms': (t3 - t2) * 1000,
                'total_ms': (t3 - t0) * 1000
            }
        }
    
    def run_realtime(self, camera_id: int = 0):
        """实时推理循环 - 显示FPS和延迟"""
        cap = cv2.VideoCapture(camera_id)
        cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
        cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
        cap.set(cv2.CAP_PROP_FPS, 30)
        
        fps_counter = []
        
        while True:
            ret, frame = cap.read()
            if not ret:
                break
            
            result = self.infer(frame)
            
            # 渲染检测结果
            for det in result['detections']:
                x1, y1, x2, y2 = det['bbox']
                cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
                label = f"Class {det['class_id']}: {det['confidence']:.2f}"
                cv2.putText(frame, label, (x1, y1-10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
            
            # 显示性能数据
            timing = result['timing']
            fps_counter.append(1000.0 / max(timing['total_ms'], 1))
            if len(fps_counter) > 30:
                fps_counter.pop(0)
            avg_fps = np.mean(fps_counter)
            
            info = (f"FPS: {avg_fps:.1f} | "
                    f"Pre: {timing['preprocess_ms']:.1f}ms | "
                    f"NPU: {timing['inference_ms']:.1f}ms | "
                    f"Post: {timing['postprocess_ms']:.1f}ms")
            cv2.putText(frame, info, (10, 30),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
            
            cv2.imshow('X2 Elite Vision AI', frame)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
        
        cap.release()
        cv2.destroyAllWindows()

# 启动实时推理
if __name__ == '__main__':
    pipeline = X2EliteVisionPipeline('yolov8n_quantized.onnx')
    pipeline.run_realtime(camera_id=0)

五、性能优化技巧

优化策略 效果 适用场景
INT8量化 推理速度提升2-3x,内存减少50% 检测/分类模型
HTP Burst模式 NPU全速运行,延迟最低 实时场景
双缓冲预处理 CPU/NPU流水并行,吞吐提升40% 视频流
Graph缓存 首次编译后缓存,后续启动加速 生产部署
FP16混合精度 精度损失<0.5%,速度提升1.5x 语义分割

六、实测性能

在骁龙X2 Elite Extreme(SC8480XP)上的典型性能表现:

模型 精度 后端 延迟 吞吐
YOLOv8n (640x640) INT8 NPU ~5ms 200+ FPS
ResNet50 INT8 NPU ~1.2ms
MobileNetV2 INT8 NPU ~0.8ms

另外,ONNX Runtime 常见模型推理延迟:

模型 X2 Elite NPU 延迟
MobileNetV2 0.8 ms
ResNet50 1.2 ms
YOLOv8n (640) 2.5 ms
YOLOv8s (640) 4.5 ms
all-MiniLM-L12 1.0 ms

注:YOLOv8n 的延迟数值在不同来源中略有差异(2.5ms vs 5ms),可能是由于预处理开销或测量方式不同,实际应用中请以实测为准。

七、性能优化检查清单

在实际部署时,请确认以下优化项:

  • 模型已转换为 INT8 量化格式
  • QNN EP 配置中 htp_performance_mode = "burst"
  • 启用了 qnn_context_cache_enable 并指定了缓存路径
  • 使用了 ThreadPoolExecutor 实现预处理并行
  • 电源模式设为“最佳性能”
  • 关闭不必要的后台程序释放 NPU 带宽

【下篇预告】
视觉有了,接下来让X2 Elite“听懂”并“说话”。下一篇我们将构建一个完全离线的端侧智能语音助手,串联VAD、Whisper、Phi-3-mini、VITS四个模型,全部运行在NPU上。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐