YOLO11 顔検出システム -- モデルデプロイメント資料
概要
本資料では、YOLO 顔検出モデルを RDK デバイスにデプロイする手順を、前処理、モデル推論、後処理の実装を含めて詳しく説明します。本デプロイ方式は Horizon RDK-X5 などの NPU デバイス向けに最適化されており、効率的な顔検出を実現します。
デプロイ手順の概要
输入图像 → 前处理 → 模型推理 → 后处理 → 检测结果
↓ ↓ ↓ ↓ ↓
BGR图像 NV12格式 BPU推理 解码NMS 人脸框坐标
モデルをデプロイする前に、学習済みの onnx モデルを RDK が提供する BPU 変換ツールチェーンで変換・量子化・コンパイルし、BPU 上で動作する専用の bin モデルファイルを取得する必要があります。
参考資料
YOLO 検出モデルの ONNX から BIN への変換について詳しくは、以下の優れたチュートリアルを参照してください。
- RDK-X5 における YOLO11 のデプロイテスト
- 出典:CSDN-YOLO11 デプロイ実践
- 内容:RDK-X5 プラットフォームでのデプロイ手順、性能テスト、幾何形状検出の実例
- ONNX から BIN へのモデル変換の詳細手順
- 出典:博客園-BPU モデル変換
- 内容:モデル変換ツールチェーンの使い方、設定ファイルの詳細解説、幾何形状向けの量子化最適化
- 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
: 画像のリサイズ
- : resizer
を呼び出して画像を 320x320 にリサイズBGR から YUV_I420 へ - OpenCV を使って色空間を変換 YUV の分離
- :Y チャンネルと UV チャンネルを分離 UV の並べ替え
- UV 成分をプレーナー形式からインターリーブ形式に変換 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 スケール分の分類とバウンディングボックスに対応)
-
- モデル推論
機能
:ハードウェアアクセラレーションによる推論を実行します 実装方法
( 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 エミュレーションモードに対応
-
- 出力フォーマットの変換
機能
推論出力を 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 []
: 各位置の最大分類信頼度を計算
- 信頼度しきい値を用いて有効な位置を抽出
- 対応するクラス ID と元の信頼度を取得
- sigmoid 関数を適用して最終的な信頼度を算出
-
- DFL によるバウンディングボックスのデコード
機能
Distribution Focal Loss を用いてバウンディングボックスの座標をデコードします 実装方法
( utils/yolo_detector.py:217-242 ): DFL デコードの手順
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
: Softmax 計算
- :16 個のビンに softmax を適用して確率分布を求めます。 期待値の計算
- :バウンディングボックスの各辺までの距離の期待値を計算します 座標変換
- :アンカーポイントを基準に絶対座標を計算します スケール復元
- :対応する stride を掛けて最終的な座標を求めます。 4. 非最大値抑制(NMS)
機能
:重複する検出を除去し、最適な検出枠を残します。 実装方法
( utils/yolo_detector.py:252-263 ): NMS 処理
# 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
: OpenCV の
- dnn.NMSBoxes
関数を使用パラメータ:信頼度しきい値(conf = 0.6)、IOU しきい値(iou = 0.45) - 座標復元:検出結果を入力画像のサイズに復元
-
- 検出処理全体の流れ
メイン検出関数
( 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 などの処理時間
- 合計時間:検出処理全体の時間
- メモリ最適化:入出力バッファの再利用
静的データの事前計算(アンカーポイント、DFL 重み)
- numpy 配列による効率的な演算
- フォールバック対応
- 開発・テスト用の CPU シミュレーションモード
充実した例外処理機構
- 詳細なログ出力
- デプロイ要件
ハードウェア要件 Horizon RDK-X5 または互換 BPU デバイス
INT8 量子化に対応した NPU アクセラレータ
- ソフトウェアの依存関係
- モデルファイル
フォーマット:Horizon BPU 量子化モデル(.bin)
# 核心依赖
hobot_dnn # 地平线BPU推理库
opencv-python # 图像处理
numpy # 数值计算
scipy # 数学函数(softmax)
入力: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 顔検出の完全なパイプラインを実装し、効率的で信頼性の高い顔検出システムを構成しています。本方式は特にエッジコンピューティングのシーンに適しており、検出精度を確保しながらリアルタイム性能の要件を満たすことができます。 まとめ
本デプロイ方式は、前処理でのデータフォーマット変換から、BPU ハードウェアアクセラレーションによる推論、マルチスケール特徴マップの後処理デコードまで、YOLO 顔検出の完全なパイプラインを実装し、効率的で信頼性の高い顔検出システムを構成しています。本方式は特にエッジコンピューティングのシーンに適しており、検出精度を確保しながらリアルタイム性能の要件を満たすことができます。
ドキュメント