从 dota2-draft-vision 迁出并定名,作为天梯选将识别项目起点。 Co-authored-by: Cursor <cursoragent@cursor.com>
80 lines
2.7 KiB
Python
80 lines
2.7 KiB
Python
"""One-time ROI calibration.
|
|
|
|
Usage:
|
|
python calibrate.py samples/full_1080p.png # interactive: drag 10 slot boxes
|
|
python calibrate.py samples/full_1080p.png --check # draw current config on image
|
|
|
|
Interactive mode: for each of the 10 hero slots (any order), drag a box around
|
|
the portrait (include the whole parallelogram, exclude neighbors), then press
|
|
SPACE/ENTER. Press ESC when all 10 are done. Slots are sorted left-to-right
|
|
and stored as resolution-independent relative coordinates.
|
|
"""
|
|
|
|
import sys
|
|
import time
|
|
|
|
import cv2
|
|
|
|
from common import load_config, save_config, slot_rect_px
|
|
|
|
|
|
def calibrate(image_path: str) -> None:
|
|
img = cv2.imread(image_path)
|
|
if img is None:
|
|
sys.exit(f"cannot read image: {image_path}")
|
|
ih, iw = img.shape[:2]
|
|
print(f"image size: {iw}x{ih}")
|
|
print("Drag a box per slot (10 total), SPACE/ENTER to confirm each, ESC to finish.")
|
|
|
|
rois = cv2.selectROIs("calibrate - drag 10 slots", img, showCrosshair=True)
|
|
cv2.destroyAllWindows()
|
|
if len(rois) != 10:
|
|
sys.exit(f"expected 10 boxes, got {len(rois)} - please rerun")
|
|
|
|
rois = sorted(rois.tolist(), key=lambda r: r[0])
|
|
cfg = load_config()
|
|
cfg["slots"] = []
|
|
avg_w = sum(r[2] for r in rois) / 10
|
|
avg_h = sum(r[3] for r in rois) / 10
|
|
cfg["slot_w_rel"] = round(avg_w / ih, 5)
|
|
cfg["slot_h_rel"] = round(avg_h / ih, 5)
|
|
for i, (x, y, w, h) in enumerate(rois):
|
|
cfg["slots"].append(
|
|
{
|
|
"index": i + 1,
|
|
"cx_rel": round((x + w / 2 - iw / 2) / ih, 5),
|
|
"cy_rel": round((y + h / 2) / ih, 5),
|
|
}
|
|
)
|
|
cfg["calibrated_on"] = f"{iw}x{ih} {time.strftime('%Y-%m-%d %H:%M')}"
|
|
save_config(cfg)
|
|
print(f"saved {len(cfg['slots'])} slots to config.json")
|
|
check(image_path)
|
|
|
|
|
|
def check(image_path: str) -> None:
|
|
"""Draw configured slot rects onto the image for visual verification."""
|
|
img = cv2.imread(image_path)
|
|
if img is None:
|
|
sys.exit(f"cannot read image: {image_path}")
|
|
ih, iw = img.shape[:2]
|
|
cfg = load_config()
|
|
if not cfg["slots"]:
|
|
sys.exit("config.json has no slots - run calibration first")
|
|
for slot in cfg["slots"]:
|
|
x, y, w, h = slot_rect_px(slot, cfg, iw, ih)
|
|
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
|
|
cv2.putText(img, str(slot["index"]), (x, y - 4), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
|
out = "calibrate_check.png"
|
|
cv2.imwrite(out, img)
|
|
print(f"wrote {out} - open it and verify the boxes sit on the 10 portraits")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
sys.exit(__doc__)
|
|
if "--check" in sys.argv:
|
|
check(sys.argv[1])
|
|
else:
|
|
calibrate(sys.argv[1])
|