From 0d63b2ef6d2147db8e4762c89308331d7688e5f3 Mon Sep 17 00:00:00 2001 From: Hare Date: Mon, 16 Feb 2026 16:08:22 +0900 Subject: [PATCH] =?UTF-8?q?=E6=97=A7debug=E8=B3=87=E7=94=A3=E3=81=AE?= =?UTF-8?q?=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 +--- debug_detector.py | 267 --------------------------------------------- docs/debugging.md | 151 ------------------------- test_quick.sh | 73 ------------- test_server_api.py | 224 ------------------------------------- 5 files changed, 1 insertion(+), 733 deletions(-) delete mode 100755 debug_detector.py delete mode 100644 docs/debugging.md delete mode 100755 test_quick.sh delete mode 100755 test_server_api.py diff --git a/README.md b/README.md index 0f64a10..bb7b101 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ python server/main.py # サーバーのGPU状態を確認 -python test_server_api.py --status +curl -s http://127.0.0.1:8181/status | jq ``` 出力例: @@ -40,20 +40,3 @@ python test_server_api.py --status ROCm Version (HIP): 7.0.51831 ====================================================================== ``` - -### 処理プロセスの単体デバッグ - -顔検出処理をBlenderから独立してテストできます。 - -```bash -# 画像ファイルでテスト -python debug_detector.py --image test.jpg - -# 動画ファイルでテスト -python debug_detector.py --video test.mp4 --frame 0 - -# クイックテスト(簡易版) -./test_quick.sh test.jpg -``` - -詳細は [docs/debugging.md](docs/debugging.md) を参照してください。 \ No newline at end of file diff --git a/debug_detector.py b/debug_detector.py deleted file mode 100755 index f2aa549..0000000 --- a/debug_detector.py +++ /dev/null @@ -1,267 +0,0 @@ -#!/usr/bin/env python3 -""" -顔検出処理の単体デバッグスクリプト - -Usage: - # 画像ファイルで検出をテスト - python debug_detector.py --image path/to/image.jpg - - # 動画ファイルで検出をテスト(指定フレームのみ) - python debug_detector.py --video path/to/video.mp4 --frame 100 - - # 動画ファイルで複数フレームをテスト - python debug_detector.py --video path/to/video.mp4 --start 0 --end 10 - - # 結果を保存 - python debug_detector.py --image test.jpg --output result.jpg -""" - -import argparse -import sys -from pathlib import Path -import cv2 -import numpy as np - -# プロジェクトルートをパスに追加 -project_root = Path(__file__).parent -sys.path.insert(0, str(project_root)) - -from server.detector import YOLOFaceDetector - - -def draw_detections(image: np.ndarray, detections, mask=None): - """ - 検出結果を画像に描画 - - Args: - image: 元画像(BGR) - detections: 検出結果のリスト [(x, y, w, h, conf), ...] - mask: マスク画像(オプション) - - Returns: - 描画済み画像 - """ - output = image.copy() - - # マスクをオーバーレイ - if mask is not None: - # マスクを3チャンネルに変換 - mask_colored = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR) - # 赤色でオーバーレイ(半透明) - mask_overlay = np.zeros_like(output) - mask_overlay[:, :, 2] = mask # 赤チャンネル - output = cv2.addWeighted(output, 1.0, mask_overlay, 0.3, 0) - - # バウンディングボックスを描画 - for (x, y, w, h, conf) in detections: - # ボックス - cv2.rectangle(output, (x, y), (x + w, y + h), (0, 255, 0), 2) - - # 信頼度テキスト - label = f"{conf:.2f}" - label_size, baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1) - y_label = max(y, label_size[1]) - cv2.rectangle( - output, - (x, y_label - label_size[1]), - (x + label_size[0], y_label + baseline), - (0, 255, 0), - -1 - ) - cv2.putText( - output, - label, - (x, y_label), - cv2.FONT_HERSHEY_SIMPLEX, - 0.5, - (0, 0, 0), - 1 - ) - - return output - - -def debug_image(args, detector): - """画像ファイルで検出をデバッグ""" - print(f"画像を読み込み中: {args.image}") - image = cv2.imread(args.image) - - if image is None: - print(f"エラー: 画像を読み込めません: {args.image}") - return - - print(f"画像サイズ: {image.shape[1]}x{image.shape[0]}") - - # 検出実行 - print("顔検出を実行中...") - detections = detector.detect(image) - - print(f"\n検出結果: {len(detections)}個の顔を検出") - for i, (x, y, w, h, conf) in enumerate(detections): - print(f" [{i+1}] x={x}, y={y}, w={w}, h={h}, conf={conf:.3f}") - - # マスク生成 - if len(detections) > 0: - mask = detector.generate_mask( - image.shape, - detections, - mask_scale=args.mask_scale, - feather_radius=args.feather_radius - ) - else: - mask = None - - # 結果を描画 - result = draw_detections(image, detections, mask) - - # 表示または保存 - if args.output: - cv2.imwrite(args.output, result) - print(f"\n結果を保存しました: {args.output}") - - if mask is not None and args.save_mask: - mask_path = args.output.replace('.', '_mask.') - cv2.imwrite(mask_path, mask) - print(f"マスクを保存しました: {mask_path}") - else: - cv2.imshow("Detection Result", result) - if mask is not None: - cv2.imshow("Mask", mask) - print("\nキーを押して終了してください...") - cv2.waitKey(0) - cv2.destroyAllWindows() - - -def debug_video(args, detector): - """動画ファイルで検出をデバッグ""" - print(f"動画を読み込み中: {args.video}") - cap = cv2.VideoCapture(args.video) - - if not cap.isOpened(): - print(f"エラー: 動画を開けません: {args.video}") - return - - total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) - fps = cap.get(cv2.CAP_PROP_FPS) - width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) - height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - - print(f"動画情報: {width}x{height}, {fps:.2f}fps, {total_frames}フレーム") - - # フレーム範囲の決定 - start_frame = args.start if args.start is not None else args.frame - end_frame = args.end if args.end is not None else args.frame - - start_frame = max(0, min(start_frame, total_frames - 1)) - end_frame = max(0, min(end_frame, total_frames - 1)) - - print(f"処理範囲: フレーム {start_frame} - {end_frame}") - - # 出力動画の準備 - out_writer = None - if args.output: - fourcc = cv2.VideoWriter_fourcc(*'mp4v') - out_writer = cv2.VideoWriter(args.output, fourcc, fps, (width, height)) - - # フレーム処理 - for frame_idx in range(start_frame, end_frame + 1): - cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) - ret, frame = cap.read() - - if not ret: - print(f"警告: フレーム {frame_idx} を読み込めませんでした") - continue - - # 検出実行 - detections = detector.detect(frame) - - # マスク生成 - if len(detections) > 0: - mask = detector.generate_mask( - frame.shape, - detections, - mask_scale=args.mask_scale, - feather_radius=args.feather_radius - ) - else: - mask = None - - # 結果を描画 - result = draw_detections(frame, detections, mask) - - print(f"フレーム {frame_idx}: {len(detections)}個の顔を検出") - - # 保存または表示 - if out_writer: - out_writer.write(result) - else: - cv2.imshow(f"Frame {frame_idx}", result) - if mask is not None: - cv2.imshow("Mask", mask) - - key = cv2.waitKey(0 if end_frame == start_frame else 30) - if key == ord('q'): - break - - cap.release() - if out_writer: - out_writer.release() - print(f"\n結果を保存しました: {args.output}") - else: - cv2.destroyAllWindows() - - -def main(): - parser = argparse.ArgumentParser( - description="顔検出処理の単体デバッグスクリプト", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=__doc__ - ) - - # 入力ソース - input_group = parser.add_mutually_exclusive_group(required=True) - input_group.add_argument("--image", type=str, help="テスト用画像ファイル") - input_group.add_argument("--video", type=str, help="テスト用動画ファイル") - - # 動画用オプション - parser.add_argument("--frame", type=int, default=0, help="処理する動画フレーム番号(デフォルト: 0)") - parser.add_argument("--start", type=int, help="処理開始フレーム(動画のみ)") - parser.add_argument("--end", type=int, help="処理終了フレーム(動画のみ)") - - # 検出パラメータ - parser.add_argument("--conf", type=float, default=0.5, help="信頼度閾値(デフォルト: 0.5)") - parser.add_argument("--iou", type=float, default=0.45, help="NMS IoU閾値(デフォルト: 0.45)") - parser.add_argument("--mask-scale", type=float, default=1.5, help="マスクスケール(デフォルト: 1.5)") - parser.add_argument("--feather-radius", type=int, default=20, help="マスクぼかし半径(デフォルト: 20)") - - # 出力オプション - parser.add_argument("--output", "-o", type=str, help="結果画像/動画の保存先") - parser.add_argument("--save-mask", action="store_true", help="マスク画像も保存する(画像のみ)") - - # モデル - parser.add_argument("--model", type=str, help="カスタムモデルパス") - - args = parser.parse_args() - - # 検出器を初期化 - print("YOLOFaceDetectorを初期化中...") - detector = YOLOFaceDetector( - model_path=args.model, - conf_threshold=args.conf, - iou_threshold=args.iou - ) - - # モデルを事前ロード - print("モデルをロード中...") - _ = detector.model - print("準備完了\n") - - # デバッグ実行 - if args.image: - debug_image(args, detector) - else: - debug_video(args, detector) - - -if __name__ == "__main__": - main() diff --git a/docs/debugging.md b/docs/debugging.md deleted file mode 100644 index f9e2a7c..0000000 --- a/docs/debugging.md +++ /dev/null @@ -1,151 +0,0 @@ -# デバッグガイド - -## 処理プロセスの単体デバッグ - -顔検出処理をBlenderアドオンから独立してテストできます。 - -### セットアップ - -```bash -# 仮想環境をアクティベート -source .venv/bin/activate - -# 必要なパッケージがインストールされていることを確認 -pip install ultralytics opencv-python torch -``` - -### 基本的な使い方 - -#### 画像ファイルで検出をテスト - -```bash -# 検出結果を画面に表示 -python debug_detector.py --image path/to/image.jpg - -# 検出結果を保存 -python debug_detector.py --image path/to/image.jpg --output result.jpg - -# マスク画像も保存 -python debug_detector.py --image path/to/image.jpg --output result.jpg --save-mask -``` - -#### 動画ファイルで検出をテスト - -```bash -# 特定のフレームをテスト -python debug_detector.py --video path/to/video.mp4 --frame 100 - -# フレーム範囲をテスト(画面表示) -python debug_detector.py --video path/to/video.mp4 --start 0 --end 10 - -# フレーム範囲を処理して動画保存 -python debug_detector.py --video path/to/video.mp4 --start 0 --end 100 --output result.mp4 -``` - -### パラメータ調整 - -```bash -# 信頼度閾値を調整(デフォルト: 0.5) -python debug_detector.py --image test.jpg --conf 0.3 - -# NMS IoU閾値を調整(デフォルト: 0.45) -python debug_detector.py --image test.jpg --iou 0.5 - -# マスクサイズを調整(デフォルト: 1.5) -python debug_detector.py --image test.jpg --mask-scale 2.0 - -# マスクのぼかし半径を調整(デフォルト: 20) -python debug_detector.py --image test.jpg --feather-radius 30 -``` - -### カスタムモデルの使用 - -```bash -python debug_detector.py --image test.jpg --model path/to/custom_model.pt -``` - -## 推論サーバーの単体起動 - -推論サーバーを単独で起動してテストすることもできます。 - -### サーバー起動 - -```bash -# 仮想環境をアクティベート -source .venv/bin/activate - -# サーバーを起動(ポート8181) -python server/main.py -``` - -### APIテスト - -別のターミナルで: - -```bash -# サーバー状態を確認 -curl http://127.0.0.1:8181/status - -# マスク生成をリクエスト -curl -X POST http://127.0.0.1:8181/generate \ - -H "Content-Type: application/json" \ - -d '{ - "video_path": "/path/to/video.mp4", - "output_dir": "/tmp/masks", - "start_frame": 0, - "end_frame": 10, - "conf_threshold": 0.5, - "iou_threshold": 0.45, - "mask_scale": 1.5 - }' - -# タスクの状態を確認(task_idは上記レスポンスから取得) -curl http://127.0.0.1:8181/tasks/{task_id} - -# タスクをキャンセル -curl -X POST http://127.0.0.1:8181/tasks/{task_id}/cancel -``` - -## トラブルシューティング - -### GPU(ROCm)が認識されない - -```bash -# PyTorchがROCmを認識しているか確認 -python -c "import torch; print(f'CUDA available: {torch.cuda.is_available()}')" - -# ROCm環境変数を確認 -echo $ROCM_PATH -echo $HSA_OVERRIDE_GFX_VERSION -``` - -環境変数が設定されていない場合: - -```bash -source .envrc -# または -eval "$(direnv export bash)" -``` - -### モデルが見つからない - -デフォルトモデルは `models/yolov8n-face-lindevs.pt` に配置する必要があります。 - -```bash -ls -l models/yolov8n-face-lindevs.pt -``` - -### メモリ不足エラー - -大きな動画を処理する場合、メモリ不足になる可能性があります: - -- フレーム範囲を小さく分割して処理 -- `--conf` 閾値を上げて検出数を減らす -- より小さいモデルを使用 - -## デバッグのベストプラクティス - -1. **まず画像でテスト**: 動画よりも画像の方が早く結果を確認できます -2. **パラメータの影響を理解**: `--conf`、`--mask-scale` などを変えて結果を比較 -3. **小さいフレーム範囲から始める**: 動画テストは最初は5-10フレーム程度で -4. **結果を保存して比較**: `--output` オプションで結果を保存し、パラメータごとに比較 diff --git a/test_quick.sh b/test_quick.sh deleted file mode 100755 index 01459a6..0000000 --- a/test_quick.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash -# クイックテストスクリプト -# 処理プロセスが正常に動作するか確認 - -set -e - -echo "=== 顔検出処理のクイックテスト ===" -echo "" - -# 仮想環境の確認 -if [ ! -d ".venv" ]; then - echo "エラー: .venv が見つかりません" - echo "仮想環境を作成してください: python -m venv .venv" - exit 1 -fi - -# 環境変数の読み込み -if [ -f ".env" ]; then - echo "環境変数を読み込み中..." - export $(cat .env | grep -v '^#' | xargs) -fi - -# 仮想環境をアクティベート -source .venv/bin/activate - -# モデルの確認 -MODEL_PATH="models/yolov8n-face-lindevs.pt" -if [ ! -f "$MODEL_PATH" ]; then - echo "警告: モデルファイルが見つかりません: $MODEL_PATH" - echo "デフォルトモデルをダウンロードしてください" -fi - -# テスト画像の確認 -if [ $# -eq 0 ]; then - echo "使い方: $0 <画像ファイルまたは動画ファイル>" - echo "" - echo "例:" - echo " $0 test.jpg" - echo " $0 test.mp4" - exit 1 -fi - -INPUT_FILE="$1" - -if [ ! -f "$INPUT_FILE" ]; then - echo "エラー: ファイルが見つかりません: $INPUT_FILE" - exit 1 -fi - -# ファイルタイプの判定 -EXT="${INPUT_FILE##*.}" -EXT_LOWER=$(echo "$EXT" | tr '[:upper:]' '[:lower:]') - -echo "入力ファイル: $INPUT_FILE" -echo "" - -# GPU情報の表示 -echo "=== GPU情報 ===" -python -c "import torch; print(f'PyTorch CUDA available: {torch.cuda.is_available()}'); print(f'Device: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else \"CPU\"}') if torch.cuda.is_available() else None" 2>/dev/null || echo "PyTorchが見つかりません" -echo "" - -# テスト実行 -echo "=== 検出テストを開始 ===" -if [[ "$EXT_LOWER" == "mp4" || "$EXT_LOWER" == "avi" || "$EXT_LOWER" == "mov" ]]; then - # 動画の場合は最初の1フレームのみテスト - python debug_detector.py --video "$INPUT_FILE" --frame 0 -else - # 画像の場合 - python debug_detector.py --image "$INPUT_FILE" -fi - -echo "" -echo "=== テスト完了 ===" diff --git a/test_server_api.py b/test_server_api.py deleted file mode 100755 index c086053..0000000 --- a/test_server_api.py +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env python3 -""" -推論サーバーAPIのテストスクリプト - -Usage: - # サーバーの状態確認 - python test_server_api.py --status - - # マスク生成のテスト - python test_server_api.py --video test.mp4 --output /tmp/masks --start 0 --end 10 -""" - -import argparse -import json -import time -import urllib.request -import urllib.error -from pathlib import Path -import sys - - -SERVER_URL = "http://127.0.0.1:8181" - - -def check_status(): - """サーバーの状態を確認""" - try: - with urllib.request.urlopen(f"{SERVER_URL}/status", timeout=2) as response: - data = json.loads(response.read().decode('utf-8')) - print("✓ サーバーは稼働中です") - print(f" Status: {data.get('status')}") - print(f" GPU Available: {data.get('gpu_available')}") - if data.get('gpu_device'): - print(f" GPU Device: {data.get('gpu_device')}") - if data.get('gpu_count'): - print(f" GPU Count: {data.get('gpu_count')}") - if data.get('rocm_version'): - print(f" ROCm Version: {data.get('rocm_version')}") - return True - except (urllib.error.URLError, ConnectionRefusedError, TimeoutError) as e: - print("✗ サーバーに接続できません") - print(f" エラー: {e}") - print("\nサーバーを起動してください:") - print(" ./run_server.sh") - return False - - -def submit_task(video_path, output_dir, start_frame, end_frame, conf, iou, mask_scale): - """マスク生成タスクを送信""" - data = { - "video_path": video_path, - "output_dir": output_dir, - "start_frame": start_frame, - "end_frame": end_frame, - "conf_threshold": conf, - "iou_threshold": iou, - "mask_scale": mask_scale, - } - - req = urllib.request.Request( - f"{SERVER_URL}/generate", - data=json.dumps(data).encode('utf-8'), - headers={'Content-Type': 'application/json'}, - method='POST' - ) - - try: - with urllib.request.urlopen(req) as response: - result = json.loads(response.read().decode('utf-8')) - return result - except urllib.error.HTTPError as e: - error_msg = e.read().decode('utf-8') - raise RuntimeError(f"サーバーエラー: {error_msg}") - - -def get_task_status(task_id): - """タスクの状態を取得""" - try: - with urllib.request.urlopen(f"{SERVER_URL}/tasks/{task_id}") as response: - return json.loads(response.read().decode('utf-8')) - except urllib.error.HTTPError: - return {"status": "unknown"} - - -def cancel_task(task_id): - """タスクをキャンセル""" - try: - req = urllib.request.Request( - f"{SERVER_URL}/tasks/{task_id}/cancel", - method='POST' - ) - with urllib.request.urlopen(req): - pass - return True - except urllib.error.HTTPError: - return False - - -def monitor_task(task_id, poll_interval=0.5): - """タスクの進行状況を監視""" - print(f"\nタスクID: {task_id}") - print("進行状況を監視中...\n") - - last_progress = -1 - - while True: - status = get_task_status(task_id) - state = status.get('status') - progress = status.get('progress', 0) - total = status.get('total', 0) - - # 進行状況の表示 - if progress != last_progress and total > 0: - percentage = (progress / total) * 100 - bar_length = 40 - filled = int(bar_length * progress / total) - bar = '=' * filled + '-' * (bar_length - filled) - print(f"\r[{bar}] {progress}/{total} ({percentage:.1f}%)", end='', flush=True) - last_progress = progress - - # 終了状態のチェック - if state == "completed": - print("\n\n✓ 処理が完了しました") - print(f" 出力先: {status.get('result_path')}") - print(f" メッセージ: {status.get('message')}") - return True - - elif state == "failed": - print("\n\n✗ 処理が失敗しました") - print(f" エラー: {status.get('message')}") - return False - - elif state == "cancelled": - print("\n\n- 処理がキャンセルされました") - return False - - time.sleep(poll_interval) - - -def main(): - parser = argparse.ArgumentParser( - description="推論サーバーAPIのテストスクリプト" - ) - - # 操作モード - mode_group = parser.add_mutually_exclusive_group(required=True) - mode_group.add_argument("--status", action="store_true", help="サーバーの状態を確認") - mode_group.add_argument("--video", type=str, help="処理する動画ファイル") - - # タスクパラメータ - parser.add_argument("--output", type=str, default="/tmp/masks", help="マスク出力先ディレクトリ") - parser.add_argument("--start", type=int, default=0, help="開始フレーム") - parser.add_argument("--end", type=int, default=10, help="終了フレーム") - parser.add_argument("--conf", type=float, default=0.5, help="信頼度閾値") - parser.add_argument("--iou", type=float, default=0.45, help="NMS IoU閾値") - parser.add_argument("--mask-scale", type=float, default=1.5, help="マスクスケール") - - # その他のオプション - parser.add_argument("--no-wait", action="store_true", help="タスク送信後、完了を待たない") - - args = parser.parse_args() - - # 状態確認モード - if args.status: - check_status() - return - - # マスク生成モード - print("=== 推論サーバーAPIテスト ===\n") - - # サーバーの確認 - if not check_status(): - sys.exit(1) - - # 動画ファイルの確認 - if not Path(args.video).exists(): - print(f"\n✗ 動画ファイルが見つかりません: {args.video}") - sys.exit(1) - - video_path = str(Path(args.video).absolute()) - output_dir = str(Path(args.output).absolute()) - - print(f"\n動画: {video_path}") - print(f"出力先: {output_dir}") - print(f"フレーム範囲: {args.start} - {args.end}") - print(f"パラメータ: conf={args.conf}, iou={args.iou}, mask_scale={args.mask_scale}") - - # タスクを送信 - print("\nタスクを送信中...") - try: - result = submit_task( - video_path, - output_dir, - args.start, - args.end, - args.conf, - args.iou, - args.mask_scale - ) - except Exception as e: - print(f"\n✗ タスク送信失敗: {e}") - sys.exit(1) - - task_id = result['id'] - print(f"✓ タスクが送信されました (ID: {task_id})") - - # 完了待機 - if not args.no_wait: - try: - success = monitor_task(task_id) - sys.exit(0 if success else 1) - except KeyboardInterrupt: - print("\n\n中断されました") - print("タスクをキャンセル中...") - if cancel_task(task_id): - print("✓ タスクをキャンセルしました") - sys.exit(130) - else: - print("\nタスクの状態を確認するには:") - print(f" python test_server_api.py --task-status {task_id}") - - -if __name__ == "__main__": - main()