268 lines
8.4 KiB
Python
Executable File
268 lines
8.4 KiB
Python
Executable File
#!/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()
|