"""実測 PNG で白色板の境界をマウスドラッグで選択する GUI スクリプト.
使い方:
uv run python scripts/select_white_board.py \
[--image reference_measurement/.../*.png] [--output configs/white_board_bbox.json]
操作:
1. 表示された画像で白色板の **境界を囲むように** マウスでドラッグして矩形を引く
2. Enter または Space で確定
3. 結果が JSON に保存される
備考:
実画像は 2448x3264 と大きいので 30% 縮小表示する.座標は実寸に変換して保存.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import cv2
def main() -> None:
parser = argparse.ArgumentParser(description="Select white board boundary in real PNG")
parser.add_argument(
"--image",
type=Path,
default=Path("reference_measurement/MiniTIAS_20260408_144434/MiniTIAS_20260408_144434.png"),
help="実測 PNG パス",
)
parser.add_argument(
"--output",
type=Path,
default=Path("configs/white_board_bbox.json"),
help="出力 JSON パス",
)
parser.add_argument("--scale", type=float, default=0.3, help="表示時の縮小率")
args = parser.parse_args()
if not args.image.exists():
msg = f"画像が見つかりません: {args.image}"
raise FileNotFoundError(msg)
img = cv2.imread(str(args.image))
if img is None:
msg = f"画像を読み込めません: {args.image}"
raise RuntimeError(msg)
h, w = img.shape[:2]
print(f"画像サイズ: {w} x {h}")
small = cv2.resize(img, None, fx=args.scale, fy=args.scale)
print("白色板の境界をマウスでドラッグして選択してください(Enter/Space で確定)")
bbox_small = cv2.selectROI(
"Select white board (drag, then Enter/Space)",
small,
fromCenter=False,
showCrosshair=True,
)
cv2.destroyAllWindows()
if bbox_small == (0, 0, 0, 0):
print("選択がキャンセルされました")
return
# 実寸に戻す
x = int(bbox_small[0] / args.scale)
y = int(bbox_small[1] / args.scale)
bw = int(bbox_small[2] / args.scale)
bh = int(bbox_small[3] / args.scale)
result = {
"image": str(args.image),
"x": x,
"y": y,
"width": bw,
"height": bh,
}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"\n保存: {args.output}")
print(f"白色板 bbox: x={x}, y={y}, width={bw}, height={bh}")
print(f"画像中心からのオフセット: ({x + bw//2 - w//2}, {y + bh//2 - h//2})")
if __name__ == "__main__":
main()