YOLO11 物体検出システム -- モデルデプロイメント資料
概要
本資料では、YOLO 物体検出モデルをエッジデバイスにデプロイする手順を、前処理、モデル推論、後処理の実装を含めて詳しく説明します。本デプロイ方式は Horizon RDK-X5 などの NPU デバイス向けに最適化されており、3 クラス(オレンジ、緑、赤)の物体を効率的に検出します。
デプロイ手順の概要
输入图像 → 前处理 → 模型推理 → 后处理 → 检测结果
↓ ↓ ↓ ↓ ↓
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 スケール分のバウンディングボックスと分類に対応)
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_shape_detector.py:184-190 ):
# reshape到对应的尺寸 (根据实际模型输出调整顺序)
s_bboxes = outputs[0].reshape(-1, 64) # 40x40 -> 1600
m_bboxes = outputs[1].reshape(-1, 64) # 20x20 -> 400
l_bboxes = outputs[2].reshape(-1, 64) # 10x10 -> 100
s_clses = outputs[3].reshape(-1, 3) # 3个类别
m_clses = outputs[4].reshape(-1, 3) # 3个类别
l_clses = outputs[5].reshape(-1, 3) # 3个类别
マルチスケール構成:
- 小スケール(40x40):stride = 8、小さい物体を検出
- 中スケール(20x20):stride = 16、中くらいの物体を検出
- 大スケール(10x10):stride = 32、大きい物体を検出
出力順序の特徴:顔検出プロジェクトとは異なり、物体検出プロジェクトではバウンディングボックス出力が先、分類出力が後になります。
2. 信頼度によるフィルタリング
機能:信頼度のしきい値に基づいて有効な検出を抽出します。
実装方法( utils/yolo_shape_detector.py:193-211 ):
# 获取置信度最高的索引和分数
s_max_scores = np.max(s_clses, axis=1)
s_valid_indices = np.flatnonzero(s_max_scores >= self.conf_inverse)
s_ids = np.argmax(s_clses[s_valid_indices, :], axis=1)
s_scores = s_max_scores[s_valid_indices]
# Sigmoid激活函数计算置信度
s_scores = 1 / (1 + np.exp(-s_scores))
フィルタリングのロジック:
- 各位置の最大分類信頼度を計算(3 クラス中の最大値)
- 信頼度しきい値を用いて有効な位置を抽出
- 対応するクラス ID と元の信頼度を取得
- sigmoid 関数を適用して最終的な信頼度を算出
マルチクラスの特徴:オレンジ、緑、赤の 3 クラスの分類検出に対応
3. DFL によるバウンディングボックスのデコード
機能:Distribution Focal Loss を用いてバウンディングボックスの座標をデコードします
実装方法( utils/yolo_shape_detector.py:216-241 ):
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 デコードの手順:
- Softmax 計算:16 個のビンに softmax を適用して確率分布を求めます。
- 期待値の計算:バウンディングボックスの各辺までの距離の期待値を計算します
- 座標変換:アンカーポイントを基準に絶対座標を計算します
- スケール復元:対応する stride を掛けて最終的な座標を求めます。
4. 非最大値抑制(NMS)
機能:重複する検出を除去し、最適な検出枠を残します。
実装方法( utils/yolo_shape_detector.py:252-262 ):
# 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_shape_detector.py:87 ):
def detect_shapes(self, frame: np.ndarray) -> Tuple[List[List[int]], List[int], List[float], float, int]:
"""
检测物体
Args:
frame: 输入图像帧
Returns:
Tuple[List[List[int]], List[int], List[float], float, int]: (边界框列表, 类别ID列表, 置信度列表, 推理时间, 物体数量)
"""
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 shape_boxes, shape_class_ids, shape_confidences, inference_time, shape_count
except Exception as e:
logger.error(f"物体检测失败: {e}")
return [], [], [], 0.0, 0
アプリケーション層のカプセル化
1. ObjectDetector カプセル化クラス
機能:統一された物体検出インターフェースを提供します
実装方法( app/object_detector.py:58 ):
def detect(self, frame: np.ndarray) -> dict:
"""
检测物体
Args:
frame: 输入图像帧
Returns:
dict: 检测结果,包含 boxes, class_ids, confidences, inference_time, object_count
"""
if self.detector is None:
logger.warning("检测器未初始化")
return {
'boxes': [],
'class_ids': [],
'confidences': [],
'inference_time': 0.0,
'object_count': 0,
'success': False,
'error': '检测器未初始化'
}
try:
# 执行检测
boxes, class_ids, confidences, inference_time, object_count = self.detector.detect_shapes(frame)
return {
'boxes': boxes,
'class_ids': class_ids,
'confidences': confidences,
'inference_time': inference_time,
'object_count': object_count,
'success': True,
'error': None
}
except Exception as e:
logger.error(f"物体检测失败: {e}")
return {
'boxes': [],
'class_ids': [],
'confidences': [],
'inference_time': 0.0,
'object_count': 0,
'success': False,
'error': str(e)
}
カプセル化の利点:
- 統一された結果フォーマットとエラー処理
- 標準化された API インターフェース
- 充実した例外処理機構
2. ユーティリティ関数
最大の物体を取得( app/object_detector.py:150 ):
def get_largest_object(self, boxes: List[List[int]]) -> Optional[List[int]]:
"""
获取最大的物体框
Args:
boxes: 物体边界框列表
Returns:
Optional[List[int]]: 最大物体框,如果没有则返回None
"""
if not boxes:
return None
largest_area = 0
largest_box = None
for box in boxes:
x1, y1, x2, y2 = box
area = (x2 - x1) * (y2 - y1)
if area > largest_area:
largest_area = area
largest_box = box
return largest_box
物体の中心点を取得(app/object_detector.py:175 ):
def get_object_center(self, box: List[int]) -> Tuple[int, int]:
"""
获取物体框的中心点
Args:
box: 物体边界框 [x1, y1, x2, y2]
Returns:
Tuple[int, int]: 中心点坐标 (x, y)
"""
x1, y1, x2, y2 = box
center_x = (x1 + x2) // 2
center_y = (y1 + y2) // 2
return center_x, center_y
可視化機能
1. 結果の描画
機能:検出結果を画像上に描画します
実装方法( utils/yolo_shape_detector.py:287 ):
def draw_detections(self, frame: np.ndarray, shape_boxes: List[List[int]],
class_ids: List[int] = None,
confidences: List[float] = None,
show_boxes: bool = True,
show_center: bool = True,
show_stats: bool = True) -> np.ndarray:
"""
在图像上绘制检测结果
"""
# 类别颜色定义
class_colors = [
(0, 165, 255), # 橙色 - orange
(0, 255, 0), # 绿色 - green
(0, 0, 255) # 红色 - red
]
for i, bbox in enumerate(shape_boxes):
x1, y1, x2, y2 = bbox
# 获取类别ID和颜色
class_id = class_ids[i] if class_ids and i < len(class_ids) else 0
color = class_colors[class_id % len(class_colors)]
# 绘制边界框
cv2.rectangle(result_frame, (x1, y1), (x2, y2), color, 2)
# 绘制标签和置信度
confidence = confidences[i] if confidences and i < len(confidences) else 0.0
class_name = self.class_names[class_id] if class_id < len(self.class_names) else f"Class {class_id}"
label = f"{class_name}: {confidence:.2f}"
可視化の特徴:
- クラスごとに異なる色(オレンジ、緑、赤)
- クラス名と信頼度を表示
- 表示オプションを設定可能(枠線、中心点、統計情報)
性能の特徴
時間の統計
- 前処理:BGR から NV12 への変換時間を含む
- 推論:BPU ハードウェアアクセラレーションによる推論時間
- 後処理:マルチスケールデコード、NMS などの処理時間
- 合計時間:検出処理全体の時間
メモリ最適化
- 入出力バッファの再利用
- 静的データの事前計算(アンカーポイント、DFL 重み)
- numpy 配列による効率的な演算
フォールバック対応
- 開発・テスト用の CPU シミュレーションモード
- 充実した例外処理機構
- 詳細なログ出力
デプロイ要件
ハードウェア要件
- Horizon RDK-X5 または互換 BPU デバイス
- INT8 量子化に対応した NPU アクセラレータ
ソフトウェアの依存関係
# 核心依赖
hobot_dnn # 地平线BPU推理库
opencv-python # 图像处理
numpy # 数值计算
scipy # 数学函数(softmax)
モデルファイル
- フォーマット:Horizon BPU 量子化モデル(.bin)
- 入力:NV12 形式、320x320
- 出力:6 つのテンソル(3 スケール分のバウンディングボックス+分類)
検出クラス
- クラス 0:オレンジ(orange)
- クラス 1:緑(green)
- クラス 2:赤(red)
使用例
from app.object_detector import ObjectDetector
# 初始化检测器
detector = ObjectDetector(
model_path="models/object_detect.bin",
conf_threshold=0.7,
iou_threshold=0.45
)
# 读取图像
frame = cv2.imread("test.jpg")
# 执行检测
result = detector.detect(frame)
if result['success']:
print(f"检测到 {result['object_count']} 个物体")
print(f"耗时: {result['inference_time']:.3f}秒")
# 获取最大物体
largest_box = detector.get_largest_object(result['boxes'])
if largest_box:
center = detector.get_object_center(largest_box)
print(f"最大物体中心点: {center}")
# 绘制结果
result_frame = detector.draw_results(frame, result)
cv2.imwrite("result.jpg", result_frame)
else:
print(f"检测失败: {result['error']}")
顔検出プロジェクトとの違い
1. モデル出力構造の違い
顔検出プロジェクト:
- 出力順序:[分類, バウンディングボックス]
- クラス次元:単一チャンネル(2 クラス)
物体検出プロジェクト:
- 出力順序:[バウンディングボックス, 分類]
- 分類次元:3 チャンネル(オレンジ/緑/赤)
2. 後処理の違い
顔検出プロジェクト:単一の信頼度計算
s_max_scores = np.max(s_clses, axis=1) # 单分类
物体検出プロジェクト:マルチクラスの信頼度計算
s_max_scores = np.max(s_clses, axis=1) # 多分类取最大值
s_ids = np.argmax(s_clses[s_valid_indices, :], axis=1) # 获取类别ID
3. アプリケーション層の違い
顔検出プロジェクト:検出器を直接使用 物体検出プロジェクト:ObjectDetector カプセル化層を追加
- 統一されたエラー処理
- 標準化された結果フォーマット
- ユーティリティ関数の追加
まとめ
本デプロイ方式は、YOLO によるマルチクラス物体検出の完全なパイプラインを実装しており、特に 3 種類の物体(オレンジ、緑、赤)の検出向けに最適化されています。プロジェクトはモジュール設計を採用し、BaseModel 基底クラスを継承して固有の後処理ロジックを実装するとともに、アプリケーション層のカプセル化を追加して、より使いやすい API を提供しています。顔検出プロジェクトと同じ前処理・推論の流れを維持しつつ、マルチクラス検出の特性に合わせて調整を加えることで、効率的で信頼性の高い物体検出システムを構成しています。
ドキュメント