File size: 2,649 Bytes
78738de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
"""Test phần THUẦN của poolcoach_cv.overlay (lát A2 phần 3) — venv app,
không cv2/torch: RecordingModel (ghi bbox quanh predict) + _uncomp_xy
(nghịch đảo bù độ cao — vẽ ngược lên frame phải ra chỗ bi HIỆN RA).
Render thật nghiệm thu bằng smoke trên clip cú 03 rack_a1 (venv CV,
13/08) + gate A2.2 khi rerun trọn VOD.
"""

from __future__ import annotations

import numpy as np
import pytest

from poolcoach_cv.camera import HeightCompensation
from poolcoach_cv.overlay import BALL_BGR, RecordingModel, _uncomp_xy


class _Box:
    def __init__(self, cls_i, conf, xyxy):
        self.cls = cls_i
        self.conf = conf
        self.xyxy = [xyxy]


class _Res:
    names = {0: "Cue", 1: "Solid"}

    def __init__(self, boxes):
        self.boxes = boxes


class _FakeYolo:
    def predict(self, imgs, **kw):
        # mỗi ảnh 1 kết quả — ảnh thứ hai không có det nào
        out = [_Res([_Box(0, 0.9, (10.0, 20.0, 30.0, 40.0)),
                     _Box(1, 0.8, (50.0, 60.0, 70.0, 80.0))])]
        out += [_Res([]) for _ in imgs[1:]]
        return out


def test_recording_model_ghi_bbox_theo_thu_tu_frame():
    rec = RecordingModel(_FakeYolo())
    res = rec.predict(["f0", "f1"], conf=0.3, imgsz=640, verbose=False)
    assert len(res) == 2                      # forward nguyên kết quả
    assert len(rec.frames) == 2
    assert rec.frames[0] == [("Cue", 0.9, (10.0, 20.0, 30.0, 40.0)),
                             ("Solid", 0.8, (50.0, 60.0, 70.0, 80.0))]
    assert rec.frames[1] == []
    rec.predict(["f2"], conf=0.3)
    assert len(rec.frames) == 3               # batch sau nối tiếp, không đè


def test_uncomp_xy_dao_dung_bu_do_cao():
    """apply (bù) rồi _uncomp_xy (đảo) phải về đúng toạ độ map gốc — vẽ
    ngược qua homography mới trúng chỗ bi hiện ra trên frame."""
    comp = HeightCompensation(True, cam_xy=(0.6, 1.2), h_m=2.41,
                              f_px=2236.0, ball_r=0.028575)
    raw = np.array([[0.2, 0.4], [1.1, 2.3]])
    compensated = comp.apply(raw)
    meta = {"on": True, "h_m": 2.41, "cx_m": 0.6, "cy_m": 1.2}
    assert _uncomp_xy(compensated, meta) == pytest.approx(raw, abs=1e-9)


def test_uncomp_xy_off_hay_thieu_camera_giu_nguyen():
    pts = np.array([[0.5, 0.5]])
    assert _uncomp_xy(pts, {"on": False}) is pts
    assert _uncomp_xy(pts, None) is pts
    # on nhưng thiếu cx/cy (kết quả thời kỳ BG31 trước BG32) — không đoán
    assert _uncomp_xy(pts, {"on": True, "h_m": 2.4}) is pts


def test_bang_mau_bi_du_1_den_9():
    assert set(BALL_BGR) == set(range(1, 10))