HumansXドキュメントセンター 公式サイトへ ↗

YOLO11顔検出システム -- モデルデプロイドキュメント

概要

本ドキュメントでは、YOLO 顔検出モデルを RDK デバイスへデプロイする手順について、前処理・モデル推論・後処理の具体的な実装を含めて詳しく説明します。このデプロイ方式は Horizon RDK-X5 などの NPU デバイス向けに最適化されており、効率的な顔検出機能を実現します。

🔧 デプロイ手順の概要

输入图像 → 前处理 → 模型推理 → 后处理 → 检测结果
    ↓         ↓         ↓         ↓         ↓
  BGR图像   NV12格式   BPU推理   解码NMS   人脸框坐标

モデルのデプロイを行う前に、学習で得られた onnx モデルに対して RDK が提供する BPU 変換ツールチェーンを使用し、モデルの変換・量子化・コンパイルを行って、BPU 上で動作する専用の bin モデルファイルを生成する必要があります。

参考資料

YOLO 幾何形状検出の ONNX から BIN へのモデル変換の詳細については、以下の優れたチュートリアルを参照してください:

  1. RDK-X5 における YOLO11 のデプロイテスト
  • 出典:CSDN - YOLO11デプロイ実践
  • 内容:RDK-X5 プラットフォームでのデプロイ手順、性能テスト、幾何形状検出の実例
  1. ONNX から BIN モデルへの変換方法の詳細
  • 出典:博客园 - BPUモデル変換
  • 内容:モデル変換ツールチェーンの使い方、設定ファイルの詳細、幾何形状の量子化最適化
  1. YOLO BPU モデル変換完全ガイド
  • 出典:個人技術ブログ
  • 内容:ONNX から BIN への完全な変換手順、幾何形状検出のベストプラクティス

📊 前処理の流れ

1. 画像サイズの調整

機能: 入力画像をモデルが必要とするサイズに縮小・拡大します

実装方法 (utils/base_model.py:76):

def resizer(self, img: np.ndarray) -> np.ndarray:
    """
    将输入图像resize到模型输入大小,并记录缩放比例。
    Args:
        img (np.ndarray): 输入图像
    Returns:
        np.ndarray: resize后的图像
    """
    img_h, img_w = img.shape[0:2]
    self.y_scale, self.x_scale = img_h/self.model_input_height, img_w/self.model_input_weight
    return cv2.resize(img, (self.model_input_height, self.model_input_weight), interpolation=cv2.INTER_NEAREST)

主な特長:

  • 目標サイズ: 320x320 ピクセル
  • 後で座標を復元するためにスケール比を記録
  • 処理速度を確保するため INTER_NEAREST 補間を使用

2. カラーフォーマット変換

機能: BGR 形式の画像を NPU デバイスが対応する NV12 形式に変換します

実装方法 (utils/base_model.py:88):

def bgr2nv12(self, bgr_img: np.ndarray) -> np.ndarray:
    """
    将BGR格式图像转换为NV12格式(YUV420半平面),用于模型推理。
    Args:
        bgr_img (np.ndarray): BGR格式图像
    Returns:
        np.ndarray: NV12格式一维数组
    """
    begin_time = time.time()
    bgr_img = self.resizer(bgr_img)  # 先调整尺寸
    height, width = bgr_img.shape[0], bgr_img.shape[1]
    area = height * width
    yuv420p = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2YUV_I420).reshape((area * 3 // 2,))
    y = yuv420p[:area]
    uv_planar = yuv420p[area:].reshape((2, area // 4))
    uv_packed = uv_planar.transpose((1, 0)).reshape((area // 2,))
    nv12 = np.zeros_like(yuv420p)
    nv12[:height * width] = y
    nv12[height * width:] = uv_packed

    process_time = time.time() - begin_time
    logger.debug(f"BGR转NV12耗时: {process_time*1000:.2f}ms")
    return nv12

変換の流れ:

  1. 画像のスケーリング: resizer を呼び出して画像を 320x320 にスケーリング
  2. BGR から YUV_I420 へ: OpenCV を使用して色空間を変換
  3. YUV の分離: Y チャンネルと UV チャンネルを分離
  4. UV の並べ替え: UV 成分をプレーナー形式からインターリーブ形式に変換
  5. NV12 の合成: 最終的な NV12 形式のデータに合成

パフォーマンス最適化:

  • 最適化分析のためのパフォーマンス計測を含む
  • numpy 配列演算を使用して効率を向上

⚡ モデル推論の流れ

1. モデルの読み込み

機能: Horizon BPU 量子化モデルを読み込みます

実装方法 (utils/base_model.py:47):

def _load_hobot_model(self, model_file: str):
    """加载地平线BPU模型"""
    try:
        begin_time = time.time()
        self.quantize_model = dnn.load(model_file)
        load_time = time.time() - begin_time
        logger.info(f"✅ 模型加载成功,耗时: {load_time:.2f}秒")

        # 打印输入张量信息
        logger.info("-> 输入张量信息")
        for i, quantize_input in enumerate(self.quantize_model[0].inputs):
            logger.info(f"input[{i}]: name={quantize_input.name}, "
                       f"type={quantize_input.properties.dtype}, "
                       f"shape={quantize_input.properties.shape}")

        # 打印输出张量信息
        logger.info("-> 输出张量信息")
        for i, quantize_output in enumerate(self.quantize_model[0].outputs):
            logger.info(f"output[{i}]: name={quantize_output.name}, "
                       f"type={quantize_output.properties.dtype}, "
                       f"shape={quantize_output.properties.shape}")

        # 记录模型输入的高和宽
        self.model_input_height, self.model_input_weight = self.quantize_model[0].inputs[0].properties.shape[2:4]

    except Exception as e:
        logger.error(f"❌ 模型加载失败: {e}")
        raise

モデル情報:

  • 対応形式: Horizon BPU 量子化モデル (.bin)
  • 入力テンソル: NV12 形式、320x320 サイズ
  • 出力テンソル: 6 つの出力層(3 つのスケールの分類と境界ボックスに対応)

2. モデル推論

機能: ハードウェアアクセラレーションによる推論を実行します

実装方法 (utils/base_model.py:112):

def forward(self, input_tensor: np.array):
    """
    执行模型推理。
    Args:
        input_tensor (np.array): 输入张量
    Returns:
        推理输出张量列表
    """
    if not HAS_HOBOT_DNN or self.quantize_model is None:
        logger.warning("模型未加载或运行在CPU模拟模式下")
        # CPU模拟模式,返回模拟输出
        return self._simulate_inference(input_tensor)

    begin_time = time.time()
    quantize_outputs = self.quantize_model[0].forward(input_tensor)
    inference_time = time.time() - begin_time
    logger.debug(f"模型推理耗时: {inference_time*1000:.2f}ms")
    return quantize_outputs

推論の特長:

  • ハードウェアアクセラレーション: Horizon BPU を利用した NPU 推論
  • パフォーマンス監視: 推論時間の統計を含む
  • フォールバック対応: 開発テスト用の CPU シミュレーションモードに対応

3. 出力形式の変換

機能: 推論出力を numpy 配列に変換します

実装方法 (utils/base_model.py:137):

def c2numpy(self, outputs):
    """
    将C类型的推理输出转为numpy数组。
    Args:
        outputs: 推理输出张量列表
    Returns:
        list[np.array]: numpy数组列表
    """
    if not HAS_HOBOT_DNN or not outputs:
        return []

    begin_time = time.time()
    numpy_outputs = [dnnTensor.buffer for dnnTensor in outputs]
    convert_time = time.time() - begin_time
    logger.debug(f"C转numpy耗时: {convert_time*1000:.2f}ms")
    return numpy_outputs

🎯 後処理の流れ

1. マルチスケール特徴マップの処理

機能: モデルが出力するマルチスケール特徴マップを処理します

実装方法 (utils/yolo_detector.py:173-181):

# reshape到对应的尺寸
s_clses = outputs[0].reshape(-1, 1)    # 40x40 -> 1600
s_bboxes = outputs[1].reshape(-1, 64)

m_clses = outputs[2].reshape(-1, 1)    # 20x20 -> 400
m_bboxes = outputs[3].reshape(-1, 64)

l_clses = outputs[4].reshape(-1, 1)    # 10x10 -> 100
l_bboxes = outputs[5].reshape(-1, 64)

マルチスケール構成:

  • 小スケール (40x40): stride=8、小さな顔を検出
  • 中スケール (20x20): stride=16、中程度の顔を検出
  • 大スケール (10x10): stride=32、大きな顔を検出

2. 信頼度によるフィルタリング

機能: 信頼度しきい値に基づいて有効な検出を選別します

実装方法 (utils/yolo_detector.py:184-207):

# 计算每个尺度的最大置信度及有效索引
s_max_scores = np.max(s_clses, axis=1)
s_valid_indices = np.flatnonzero(s_max_scores >= self.conf_inverse)

# 获取有效目标的类别ID和置信度
s_ids = np.argmax(s_clses[s_valid_indices, :], axis=1) if len(s_valid_indices) > 0 else []
s_scores = s_max_scores[s_valid_indices] if len(s_valid_indices) > 0 else []

# Sigmoid计算
s_scores = 1 / (1 + np.exp(-np.array(s_scores))) if len(s_scores) > 0 else []

フィルタリングのロジック:

  1. 各位置における最大分類信頼度を計算
  2. 信頼度しきい値を用いて有効な位置を選別
  3. 対応するクラス ID と元の信頼度を取得
  4. sigmoid 関数を適用して最終的な信頼度を算出

3. DFL 境界ボックスのデコード

機能: Distribution Focal Loss を用いて境界ボックスの座標をデコードします

実装方法 (utils/yolo_detector.py:217-242):

if len(s_valid_indices) > 0:
    s_bboxes_float32 = s_bboxes[s_valid_indices, :]
    # DFL解码: 使用softmax计算期望值
    s_ltrb_indices = np.sum(softmax(s_bboxes_float32.reshape(-1, 4, 16), axis=2) * self.weights_static, axis=2)
    s_anchor_indices = self.s_anchor[s_valid_indices, :]
    # 基于anchor点计算边界框坐标
    s_x1y1 = s_anchor_indices - s_ltrb_indices[:, 0:2]
    s_x2y2 = s_anchor_indices + s_ltrb_indices[:, 2:4]
    # 乘以stride还原到原始尺度
    s_dbboxes = np.hstack([s_x1y1, s_x2y2]) * 8

DFL デコードの手順:

  1. Softmax 計算: 16 個の bin に softmax を適用して確率分布を求める
  2. 期待値計算: 境界ボックス各辺までの距離の期待値を計算
  3. 座標変換: anchor 点を基準に絶対座標を計算
  4. スケール復元: 対応する stride を掛けて最終座標を得る

4. 非最大値抑制 (NMS)

機能: 重複した検出を除去し、最適な検出枠を残します

実装方法 (utils/yolo_detector.py:252-263):

# NMS非极大值抑制
if len(dbboxes) > 0:
    indices = cv2.dnn.NMSBoxes(dbboxes, scores, self.conf, self.iou)
    if len(indices) > 0:
        # 还原到原始图像尺度
        bboxes = dbboxes[indices] * np.array([self.x_scale, self.y_scale, self.x_scale, self.y_scale])
        bboxes = bboxes.astype(np.int32)

        process_time = time.time() - begin_time
        logger.debug(f"后处理耗时: {process_time*1000:.2f}ms")

        return ids[indices], scores[indices], bboxes

NMS 処理:

  • OpenCV の dnn.NMSBoxes 関数を使用
  • パラメータ: 信頼度しきい値 (conf=0.6)、IOU しきい値 (iou=0.45)
  • 座標復元: 検出結果を入力画像のサイズに復元

5. 検出処理の全体フロー

メイン検出関数 (utils/yolo_detector.py:83):

def detect_faces(self, frame: np.ndarray) -> Tuple[List[List[int]], List[float], float, int]:
    """
    检测人脸
    Args:
        frame: 输入图像帧
    Returns:
        Tuple[List[List[int]], List[float], float, int]: (边界框列表, 置信度列表, 推理时间, 人脸数量)
    """
    start_time = time.time()

    try:
        # 1. 预处理:BGR转NV12
        input_tensor = self.bgr2nv12(frame)

        # 2. 模型推理
        outputs = self.forward(input_tensor)

        # 3. 转换输出格式
        numpy_outputs = self.c2numpy(outputs)

        # 4. 后处理:解码、NMS等
        if not numpy_outputs:
            return self._simulate_detection(frame)

        class_ids, confidences, bboxes = self.postProcess(numpy_outputs)

        # 5. 结果格式化
        inference_time = time.time() - start_time
        # ... 格式化输出

        return face_boxes, face_confidences, inference_time, face_count

    except Exception as e:
        logger.error(f"人脸检测失败: {e}")
        return [], [], 0.0, 0

📈 パフォーマンス特性

時間計測

  • 前処理: BGR から NV12 への変換時間を含む
  • 推論: BPU ハードウェアアクセラレーションによる推論時間
  • 後処理: マルチスケールデコード、NMS などの処理時間
  • 合計時間: 検出処理全体の時間

メモリ最適化

  • 入力/出力バッファの再利用
  • 静的データ(anchor 点、DFL 重み)の事前計算
  • numpy 配列による効率的な演算

フォールバック対応

  • 開発テスト用の CPU シミュレーションモード
  • 充実した例外処理機構
  • 詳細なログ記録

🔧 デプロイ要件

ハードウェア要件

  • Horizon RDK-X5 または互換性のある BPU デバイス
  • INT8 量子化に対応した NPU アクセラレータ

ソフトウェア依存関係

# 核心依赖
hobot_dnn        # 地平线BPU推理库
opencv-python    # 图像处理
numpy            # 数值计算
scipy            # 数学函数(softmax)

モデルファイル

  • 形式: Horizon BPU 量子化モデル (.bin)
  • 入力: NV12 形式、320x320
  • 出力: 6 つのテンソル(3 スケールの分類 + 境界ボックス)

📝 使用例

from utils.yolo_detector import YOLO11FaceDetect

# 初始化检测器
detector = YOLO11FaceDetect(
    model_file="models/face_detect.bin",
    conf=0.6,
    iou=0.45
)

# 读取图像
frame = cv2.imread("test.jpg")

# 执行检测
face_boxes, confidences, inference_time, face_count = detector.detect_faces(frame)

# 绘制结果
result_frame = detector.draw_detections(frame, face_boxes, confidences)

print(f"检测到 {face_count} 个人脸,耗时: {inference_time:.3f}秒")

🎯 まとめ

本デプロイ方式は、前処理でのデータ形式変換から、BPU ハードウェアアクセラレーションによる推論、マルチスケール特徴マップの後処理デコードまでを含む、完全な YOLO 顔検出パイプラインを実現しており、効率的で信頼性の高い顔検出システムを構成しています。この方式は特にエッジコンピューティングの用途に適しており、検出精度を確保しながらリアルタイム性能の要件を満たすことができます。