Compare commits
3 Commits
0f150e8a0a
...
f2665a49dd
| Author | SHA1 | Date | |
|---|---|---|---|
| f2665a49dd | |||
| a2131a962b | |||
| 0b6c31501e |
56
README.md
56
README.md
|
|
@ -2,4 +2,58 @@
|
||||||
|
|
||||||
街歩き映像に対して自動モザイクを掛けるために開発しました。
|
街歩き映像に対して自動モザイクを掛けるために開発しました。
|
||||||
|
|
||||||
使用:https://github.com/akanametov/yolo-face
|
使用:https://github.com/akanametov/yolo-face
|
||||||
|
|
||||||
|
## 開発者向け情報
|
||||||
|
|
||||||
|
### GPU環境の確認
|
||||||
|
|
||||||
|
推論サーバーは起動時に環境診断情報を出力します:
|
||||||
|
|
||||||
|
- Python環境(バージョン、仮想環境の検出)
|
||||||
|
- ROCm環境変数(ROCM_PATH、HSA_OVERRIDE_GFX_VERSION等)
|
||||||
|
- GPU検出状況(デバイス名、ROCmバージョン)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# サーバーを起動して診断ログを確認
|
||||||
|
python server/main.py
|
||||||
|
|
||||||
|
# サーバーのGPU状態を確認
|
||||||
|
python test_server_api.py --status
|
||||||
|
```
|
||||||
|
|
||||||
|
出力例:
|
||||||
|
```
|
||||||
|
[FaceMask Server] Startup Diagnostics
|
||||||
|
======================================================================
|
||||||
|
[Python Environment]
|
||||||
|
Python Version: 3.12.12
|
||||||
|
Virtual Environment: Yes
|
||||||
|
|
||||||
|
[ROCm Environment Variables]
|
||||||
|
ROCM_PATH: /nix/store/.../clr-7.1.1
|
||||||
|
HSA_OVERRIDE_GFX_VERSION: 11.0.0
|
||||||
|
|
||||||
|
[GPU Detection]
|
||||||
|
torch.cuda.is_available(): True
|
||||||
|
GPU Device 0: AMD Radeon Graphics
|
||||||
|
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) を参照してください。
|
||||||
|
|
@ -24,6 +24,8 @@ class InferenceClient:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.server_process: Optional[subprocess.Popen] = None
|
self.server_process: Optional[subprocess.Popen] = None
|
||||||
self._server_lock = threading.Lock()
|
self._server_lock = threading.Lock()
|
||||||
|
self.log_file = None
|
||||||
|
self.log_file_path = None
|
||||||
|
|
||||||
def start_server(self):
|
def start_server(self):
|
||||||
"""Start the inference server process."""
|
"""Start the inference server process."""
|
||||||
|
|
@ -52,41 +54,105 @@ class InferenceClient:
|
||||||
server_env[key] = value
|
server_env[key] = value
|
||||||
print(f"[FaceMask] Loaded environment from: {env_file}")
|
print(f"[FaceMask] Loaded environment from: {env_file}")
|
||||||
|
|
||||||
# Ensure PYTHONPATH includes project root
|
# Clean PYTHONPATH to avoid conflicts with Nix Python packages
|
||||||
pythonpath = server_env.get('PYTHONPATH', '')
|
# Only include project root to allow local imports
|
||||||
if pythonpath:
|
server_env['PYTHONPATH'] = root_dir
|
||||||
server_env['PYTHONPATH'] = f"{root_dir}:{pythonpath}"
|
|
||||||
else:
|
# Remove Python-related environment variables that might cause conflicts
|
||||||
server_env['PYTHONPATH'] = root_dir
|
# These can cause venv to import packages from Nix instead of venv
|
||||||
|
env_vars_to_remove = [
|
||||||
|
'PYTHONUNBUFFERED',
|
||||||
|
'__PYVENV_LAUNCHER__', # macOS venv variable
|
||||||
|
'VIRTUAL_ENV', # Will be set by venv's Python automatically
|
||||||
|
]
|
||||||
|
for var in env_vars_to_remove:
|
||||||
|
server_env.pop(var, None)
|
||||||
|
|
||||||
# If there's a venv in the project, add it to PATH
|
# If there's a venv in the project, add it to PATH
|
||||||
venv_bin = os.path.join(root_dir, ".venv", "bin")
|
venv_bin = os.path.join(root_dir, ".venv", "bin")
|
||||||
if os.path.isdir(venv_bin):
|
if os.path.isdir(venv_bin):
|
||||||
|
# Build a clean PATH with venv first, then essential system paths
|
||||||
|
# Filter out any Nix Python-specific paths to avoid version conflicts
|
||||||
current_path = server_env.get('PATH', '')
|
current_path = server_env.get('PATH', '')
|
||||||
server_env['PATH'] = f"{venv_bin}:{current_path}"
|
path_entries = current_path.split(':')
|
||||||
|
|
||||||
|
# Filter out Nix Python 3.11 paths
|
||||||
|
filtered_paths = [
|
||||||
|
p for p in path_entries
|
||||||
|
if not ('/python3.11/' in p.lower() or '/python3-3.11' in p.lower())
|
||||||
|
]
|
||||||
|
|
||||||
|
# Reconstruct PATH with venv first
|
||||||
|
clean_path = ':'.join([venv_bin] + filtered_paths)
|
||||||
|
server_env['PATH'] = clean_path
|
||||||
print(f"[FaceMask] Using venv from: {venv_bin}")
|
print(f"[FaceMask] Using venv from: {venv_bin}")
|
||||||
|
|
||||||
|
# Prepare log file for server output
|
||||||
|
import tempfile
|
||||||
|
log_dir = tempfile.gettempdir()
|
||||||
|
self.log_file_path = os.path.join(log_dir, "facemask_server.log")
|
||||||
|
self.log_file = open(self.log_file_path, 'w', buffering=1) # Line buffered
|
||||||
|
print(f"[FaceMask] Server log: {self.log_file_path}")
|
||||||
|
|
||||||
# Start process with 'python' command (will use venv if PATH is set correctly)
|
# Start process with 'python' command (will use venv if PATH is set correctly)
|
||||||
self.server_process = subprocess.Popen(
|
self.server_process = subprocess.Popen(
|
||||||
["python", server_script],
|
["python", "-u", server_script], # -u for unbuffered output
|
||||||
cwd=root_dir,
|
cwd=root_dir,
|
||||||
text=True,
|
text=True,
|
||||||
env=server_env,
|
env=server_env,
|
||||||
|
stdout=self.log_file, # Write to log file
|
||||||
|
stderr=subprocess.STDOUT, # Merge stderr into stdout
|
||||||
preexec_fn=os.setsid, # Create new process group
|
preexec_fn=os.setsid, # Create new process group
|
||||||
)
|
)
|
||||||
|
|
||||||
# Wait for startup
|
# Wait for startup
|
||||||
for _ in range(20): # Wait up to 10 seconds
|
for _ in range(20): # Wait up to 10 seconds
|
||||||
if self.is_server_running():
|
if self.is_server_running():
|
||||||
print("[FaceMask] Server started successfully")
|
print("[FaceMask] Server started successfully")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if process died
|
# Check if process died
|
||||||
if self.server_process.poll() is not None:
|
if self.server_process.poll() is not None:
|
||||||
raise RuntimeError(f"Server failed to start (rc={self.server_process.returncode})")
|
# Read error output from log file
|
||||||
|
error_msg = f"Server failed to start (exit code: {self.server_process.returncode})"
|
||||||
|
print(f"[FaceMask] ERROR: {error_msg}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.log_file:
|
||||||
|
self.log_file.close()
|
||||||
|
with open(self.log_file_path, 'r') as f:
|
||||||
|
log_content = f.read()
|
||||||
|
if log_content.strip():
|
||||||
|
print("[FaceMask] Server log:")
|
||||||
|
# Show last 50 lines
|
||||||
|
lines = log_content.strip().split('\n')
|
||||||
|
for line in lines[-50:]:
|
||||||
|
print(line)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[FaceMask] Could not read log file: {e}")
|
||||||
|
|
||||||
|
self.server_process = None
|
||||||
|
raise RuntimeError(error_msg)
|
||||||
|
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# If we get here, startup timed out
|
||||||
|
print("[FaceMask] Server startup timed out")
|
||||||
|
|
||||||
|
# Try to read partial log
|
||||||
|
try:
|
||||||
|
if self.log_file:
|
||||||
|
self.log_file.close()
|
||||||
|
with open(self.log_file_path, 'r') as f:
|
||||||
|
log_content = f.read()
|
||||||
|
if log_content.strip():
|
||||||
|
print("[FaceMask] Server log (partial):")
|
||||||
|
lines = log_content.strip().split('\n')
|
||||||
|
for line in lines[-30:]:
|
||||||
|
print(line)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
raise RuntimeError("Server startup timed out")
|
raise RuntimeError("Server startup timed out")
|
||||||
|
|
||||||
def stop_server(self):
|
def stop_server(self):
|
||||||
|
|
@ -101,6 +167,14 @@ class InferenceClient:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
self.server_process = None
|
self.server_process = None
|
||||||
|
|
||||||
|
# Close log file
|
||||||
|
if self.log_file:
|
||||||
|
try:
|
||||||
|
self.log_file.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.log_file = None
|
||||||
|
|
||||||
def is_server_running(self) -> bool:
|
def is_server_running(self) -> bool:
|
||||||
"""Check if server is responding."""
|
"""Check if server is responding."""
|
||||||
|
|
|
||||||
267
debug_detector.py
Executable file
267
debug_detector.py
Executable file
|
|
@ -0,0 +1,267 @@
|
||||||
|
#!/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()
|
||||||
151
docs/debugging.md
Normal file
151
docs/debugging.md
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
# デバッグガイド
|
||||||
|
|
||||||
|
## 処理プロセスの単体デバッグ
|
||||||
|
|
||||||
|
顔検出処理を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` オプションで結果を保存し、パラメータごとに比較
|
||||||
|
|
@ -64,8 +64,8 @@
|
||||||
# 必要なパッケージのインストール確認とインストール
|
# 必要なパッケージのインストール確認とインストール
|
||||||
if ! python -c "import torch; print(torch.cuda.is_available())" 2>/dev/null | grep -q "True"; then
|
if ! python -c "import torch; print(torch.cuda.is_available())" 2>/dev/null | grep -q "True"; then
|
||||||
echo "[Setup] Installing Python dependencies..."
|
echo "[Setup] Installing Python dependencies..."
|
||||||
# まずPyTorch ROCm版をインストール(ROCm 6.2用)
|
# まずPyTorch ROCm版をインストール(ROCm 7.0 nightly - ROCm 7.1.1環境で動作確認済み)
|
||||||
pip install --quiet torch torchvision --index-url https://download.pytorch.org/whl/rocm6.2
|
pip install --quiet --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/rocm7.0
|
||||||
# 次に通常のPyPIから他のパッケージをインストール
|
# 次に通常のPyPIから他のパッケージをインストール
|
||||||
pip install --quiet \
|
pip install --quiet \
|
||||||
ultralytics \
|
ultralytics \
|
||||||
|
|
|
||||||
52
run_server.sh
Executable file
52
run_server.sh
Executable file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# 推論サーバーの単体起動スクリプト
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Face Detection Inference Server ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 環境変数の読み込み
|
||||||
|
if [ -f ".env" ]; then
|
||||||
|
echo "環境変数を読み込み中..."
|
||||||
|
export $(cat .env | grep -v '^#' | xargs)
|
||||||
|
else
|
||||||
|
echo "警告: .env ファイルが見つかりません"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 仮想環境の確認とアクティベート
|
||||||
|
if [ ! -d ".venv" ]; then
|
||||||
|
echo "エラー: .venv が見つかりません"
|
||||||
|
echo "仮想環境を作成してください: python -m venv .venv"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
# モデルの確認
|
||||||
|
MODEL_PATH="models/yolov8n-face-lindevs.pt"
|
||||||
|
if [ ! -f "$MODEL_PATH" ]; then
|
||||||
|
echo "警告: モデルファイルが見つかりません: $MODEL_PATH"
|
||||||
|
echo "最初のリクエスト時にエラーになる可能性があります"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GPU情報の表示
|
||||||
|
echo "=== GPU情報 ==="
|
||||||
|
python -c "
|
||||||
|
import torch
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
print(f'GPU検出: {torch.cuda.get_device_name(0)}')
|
||||||
|
print(f'ROCm version: {torch.version.hip if hasattr(torch.version, \"hip\") else \"N/A\"}')
|
||||||
|
else:
|
||||||
|
print('GPU未検出(CPUモードで動作します)')
|
||||||
|
" 2>/dev/null || echo "PyTorchが見つかりません"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# サーバー起動
|
||||||
|
echo "=== サーバーを起動中 ==="
|
||||||
|
echo "URL: http://127.0.0.1:8181"
|
||||||
|
echo "終了するには Ctrl+C を押してください"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
python server/main.py
|
||||||
144
server/main.py
144
server/main.py
|
|
@ -7,6 +7,7 @@ GPU-accelerated face detection using ONNX Runtime.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import platform
|
||||||
import threading
|
import threading
|
||||||
import uuid
|
import uuid
|
||||||
import queue
|
import queue
|
||||||
|
|
@ -27,6 +28,9 @@ from server.detector import YOLOFaceDetector, get_detector
|
||||||
|
|
||||||
app = FastAPI(title="Face Mask Inference Server")
|
app = FastAPI(title="Face Mask Inference Server")
|
||||||
|
|
||||||
|
# GPU status cache
|
||||||
|
_gpu_status_cache = None
|
||||||
|
|
||||||
# Task storage
|
# Task storage
|
||||||
class TaskStatus:
|
class TaskStatus:
|
||||||
PENDING = "pending"
|
PENDING = "pending"
|
||||||
|
|
@ -146,9 +150,146 @@ def process_video_task(task_id: str, req: GenerateRequest):
|
||||||
if task_id in cancel_events:
|
if task_id in cancel_events:
|
||||||
del cancel_events[task_id]
|
del cancel_events[task_id]
|
||||||
|
|
||||||
|
def check_gpu_available() -> dict:
|
||||||
|
"""
|
||||||
|
Check if GPU is available for inference.
|
||||||
|
|
||||||
|
Returns a dict with GPU information:
|
||||||
|
{
|
||||||
|
"available": bool,
|
||||||
|
"device_name": str or None,
|
||||||
|
"device_count": int,
|
||||||
|
"rocm_version": str or None
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
global _gpu_status_cache
|
||||||
|
|
||||||
|
# Return cached result if available
|
||||||
|
if _gpu_status_cache is not None:
|
||||||
|
return _gpu_status_cache
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"available": False,
|
||||||
|
"device_name": None,
|
||||||
|
"device_count": 0,
|
||||||
|
"rocm_version": None
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
import torch
|
||||||
|
|
||||||
|
result["available"] = torch.cuda.is_available()
|
||||||
|
|
||||||
|
if result["available"]:
|
||||||
|
result["device_count"] = torch.cuda.device_count()
|
||||||
|
if result["device_count"] > 0:
|
||||||
|
result["device_name"] = torch.cuda.get_device_name(0)
|
||||||
|
|
||||||
|
if hasattr(torch.version, 'hip'):
|
||||||
|
result["rocm_version"] = torch.version.hip
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[FaceMask] Warning: GPU detection failed: {e}")
|
||||||
|
result["available"] = False
|
||||||
|
|
||||||
|
# Cache the result
|
||||||
|
_gpu_status_cache = result
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def log_startup_diagnostics():
|
||||||
|
"""Log diagnostic information about the environment and GPU."""
|
||||||
|
print("=" * 70)
|
||||||
|
print("[FaceMask Server] Startup Diagnostics")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
# Python Environment
|
||||||
|
print("\n[Python Environment]")
|
||||||
|
print(f" Python Version: {sys.version.split()[0]}")
|
||||||
|
print(f" Python Executable: {sys.executable}")
|
||||||
|
print(f" Platform: {platform.platform()}")
|
||||||
|
print(f" Working Directory: {os.getcwd()}")
|
||||||
|
|
||||||
|
# Check if in venv
|
||||||
|
in_venv = sys.prefix != sys.base_prefix
|
||||||
|
print(f" Virtual Environment: {'Yes' if in_venv else 'No'}")
|
||||||
|
if in_venv:
|
||||||
|
print(f" venv path: {sys.prefix}")
|
||||||
|
|
||||||
|
# ROCm Environment Variables
|
||||||
|
print("\n[ROCm Environment Variables]")
|
||||||
|
rocm_vars = [
|
||||||
|
'ROCM_PATH',
|
||||||
|
'HSA_OVERRIDE_GFX_VERSION',
|
||||||
|
'PYTORCH_ROCM_ARCH',
|
||||||
|
'ROCBLAS_TENSILE_LIBPATH',
|
||||||
|
'LD_LIBRARY_PATH'
|
||||||
|
]
|
||||||
|
|
||||||
|
for var in rocm_vars:
|
||||||
|
value = os.environ.get(var)
|
||||||
|
if value:
|
||||||
|
# Truncate very long values
|
||||||
|
if len(value) > 200:
|
||||||
|
display_value = value[:200] + "... (truncated)"
|
||||||
|
else:
|
||||||
|
display_value = value
|
||||||
|
print(f" {var}: {display_value}")
|
||||||
|
else:
|
||||||
|
print(f" {var}: (not set)")
|
||||||
|
|
||||||
|
# GPU Detection
|
||||||
|
print("\n[GPU Detection]")
|
||||||
|
try:
|
||||||
|
import torch
|
||||||
|
|
||||||
|
cuda_available = torch.cuda.is_available()
|
||||||
|
print(f" torch.cuda.is_available(): {cuda_available}")
|
||||||
|
|
||||||
|
if cuda_available:
|
||||||
|
device_count = torch.cuda.device_count()
|
||||||
|
print(f" GPU Device Count: {device_count}")
|
||||||
|
|
||||||
|
if device_count > 0:
|
||||||
|
device_name = torch.cuda.get_device_name(0)
|
||||||
|
print(f" GPU Device 0: {device_name}")
|
||||||
|
|
||||||
|
# ROCm version
|
||||||
|
if hasattr(torch.version, 'hip'):
|
||||||
|
print(f" ROCm Version (HIP): {torch.version.hip}")
|
||||||
|
|
||||||
|
# CUDA version (might be emulated by ROCm)
|
||||||
|
if torch.version.cuda:
|
||||||
|
print(f" CUDA Version: {torch.version.cuda}")
|
||||||
|
else:
|
||||||
|
print(" WARNING: GPU not detected!")
|
||||||
|
print(" Server will use CPU for inference (slower)")
|
||||||
|
print(" Troubleshooting:")
|
||||||
|
print(" - Check ROCm environment variables above")
|
||||||
|
print(" - Run: python -c 'import torch; print(torch.cuda.is_available())'")
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
print(f" ERROR: Cannot import torch: {e}")
|
||||||
|
print(" PyTorch must be installed for inference")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ERROR during GPU detection: {e}")
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
@app.get("/status")
|
@app.get("/status")
|
||||||
def get_status():
|
def get_status():
|
||||||
return {"status": "running", "gpu_available": True} # TODO: check GPU
|
gpu_info = check_gpu_available()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "running",
|
||||||
|
"gpu_available": gpu_info["available"],
|
||||||
|
"gpu_device": gpu_info["device_name"],
|
||||||
|
"gpu_count": gpu_info["device_count"],
|
||||||
|
"rocm_version": gpu_info["rocm_version"]
|
||||||
|
}
|
||||||
|
|
||||||
@app.post("/generate", response_model=Task)
|
@app.post("/generate", response_model=Task)
|
||||||
def generate_mask_endpoint(req: GenerateRequest, background_tasks: BackgroundTasks):
|
def generate_mask_endpoint(req: GenerateRequest, background_tasks: BackgroundTasks):
|
||||||
|
|
@ -177,4 +318,5 @@ def cancel_task(task_id: str):
|
||||||
return {"message": "Cancellation requested"}
|
return {"message": "Cancellation requested"}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
log_startup_diagnostics()
|
||||||
uvicorn.run(app, host="127.0.0.1", port=8181)
|
uvicorn.run(app, host="127.0.0.1", port=8181)
|
||||||
|
|
|
||||||
73
test_quick.sh
Executable file
73
test_quick.sh
Executable file
|
|
@ -0,0 +1,73 @@
|
||||||
|
#!/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 "=== テスト完了 ==="
|
||||||
224
test_server_api.py
Executable file
224
test_server_api.py
Executable file
|
|
@ -0,0 +1,224 @@
|
||||||
|
#!/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()
|
||||||
Loading…
Reference in New Issue
Block a user