crabbly commited on
Commit
b3bb8ac
·
verified ·
1 Parent(s): d0dc7e3

Delete test_color_calibration.py

Browse files
Files changed (1) hide show
  1. test_color_calibration.py +0 -140
test_color_calibration.py DELETED
@@ -1,140 +0,0 @@
1
- import cv2 as cv
2
- import numpy as np
3
-
4
- # ── linear / sRGB helpers ──────────────────────────────────────────
5
-
6
- def to_linear_srgb(u8_bgr):
7
- rgb = cv.cvtColor(u8_bgr, cv.COLOR_BGR2RGB).astype(np.float32) / 255.0
8
- a = 0.055
9
- lin = np.where(rgb <= 0.04045, rgb / 12.92, ((rgb + a) / (1 + a)) ** 2.4)
10
- return lin
11
-
12
- def to_srgb_u8(lin_rgb):
13
- a = 0.055
14
- srgb = np.where(lin_rgb <= 0.0031308, 12.92 * lin_rgb, (1 + a) * np.power(lin_rgb, 1/2.4) - a)
15
- srgb = np.clip(srgb, 0, 1)
16
- return cv.cvtColor((srgb * 255.0).astype(np.uint8), cv.COLOR_RGB2BGR)
17
-
18
- # ── checker geometry ───────────────────────────────────────────────
19
-
20
- def warp_checker(img, corners, out_w=600, out_h=400):
21
- # FIX: cv.mcc returns [Bottom-Left, Top-Left, Top-Right, Bottom-Right]
22
- # We must match this order to prevent 90-degree rotation.
23
- dst = np.float32([
24
- [0, out_h - 1], # Bottom-Left
25
- [0, 0], # Top-Left
26
- [out_w - 1, 0], # Top-Right
27
- [out_w - 1, out_h - 1]# Bottom-Right
28
- ])
29
- H_mat = cv.getPerspectiveTransform(np.float32(corners), dst)
30
- warped = cv.warpPerspective(img, H_mat, (out_w, out_h), flags=cv.INTER_CUBIC)
31
- return warped
32
-
33
- def detect_checker_corners(img_bgr):
34
- det = cv.mcc.CCheckerDetector_create()
35
- ok = det.process(img_bgr, cv.mcc.MCC24)
36
- if not ok:
37
- raise RuntimeError("ColorChecker not found")
38
- lst = det.getListColorChecker()
39
- cc = lst[0]
40
- if hasattr(cc, "getBox"):
41
- corners = np.array(cc.getBox(), dtype=np.float32)
42
- else:
43
- corners = np.array(cc.getCorners(), dtype=np.float32)
44
- return corners
45
-
46
- def sample_24_patches(warped, margin=12):
47
- H, W = warped.shape[:2]
48
- cell_w, cell_h = W / 6.0, H / 4.0
49
- lin = to_linear_srgb(warped)
50
- means =[]
51
- for r in range(4):
52
- for c in range(6):
53
- x0 = int(c * cell_w + margin); x1 = int((c+1) * cell_w - margin)
54
- y0 = int(r * cell_h + margin); y1 = int((r+1) * cell_h - margin)
55
- roi = lin[y0:y1, x0:x1]
56
- means.append(np.median(roi.reshape(-1, 3), axis=0))
57
- return np.stack(means, 0)
58
-
59
- # ── calibration math ──────────────────────────────────────────────
60
-
61
- def white_balance_neutrals(src24, ref24):
62
- # FIX: Use median to prevent a clipped white patch from dominating the math
63
- idx = np.arange(18, 24)
64
- src_g = np.median(src24[idx], axis=0)
65
- ref_g = np.median(ref24[idx], axis=0)
66
- gains = ref_g / np.maximum(src_g, 1e-6)
67
- return gains
68
-
69
- def solve_ccm_no_bias(src24, ref24, use_indices):
70
- A = src24[use_indices]
71
- B = ref24[use_indices]
72
- X, *_ = np.linalg.lstsq(A, B, rcond=None)
73
- M = X.T
74
- return M
75
-
76
- def fit_monotone_luma_curve_midgrays(src24_lin, ref24_lin):
77
- mid_idx = np.array([19, 20, 21, 22])
78
- w = np.array([0.2126, 0.7152, 0.0722], np.float32)
79
-
80
- Ls = (src24_lin[mid_idx] @ w).astype(np.float32)
81
- Lt = (ref24_lin[mid_idx] @ w).astype(np.float32)
82
-
83
- # FIX: Sort by Ls, and apply the SAME index sort to Lt to preserve pairing
84
- sort_idx = np.argsort(Ls)
85
- Ls = Ls[sort_idx]
86
- Lt = Lt[sort_idx]
87
-
88
- eps_lo, eps_hi = 0.01, 0.98
89
- Ls = np.concatenate([[eps_lo], Ls, [eps_hi]])
90
- Lt = np.concatenate([[eps_lo], Lt,[eps_hi]])
91
-
92
- # FIX: Use numpy's highly optimized vectorized interpolation
93
- def map_luma(L):
94
- L_clip = np.clip(L, 0, 1)
95
- return np.interp(L_clip, Ls, Lt)
96
-
97
- return map_luma
98
-
99
- def soft_highlight_rolloff(L, knee=0.90, strength=0.6):
100
- below = L < knee
101
- out = np.empty_like(L, dtype=np.float32)
102
- out[below] = L[below]
103
- x = (L[~below] - knee) / max(1e-6, (1.0 - knee))
104
- out[~below] = knee + (1.0 - knee) * (1.0 - np.exp(-strength * x))
105
- return out
106
-
107
- # ── full correction pipeline ─────────────────────────────────────
108
-
109
- def apply_pipeline(target_bgr_u8, ref24, tgt24):
110
- # 1. White Balance
111
- gains = white_balance_neutrals(tgt24, ref24)
112
- lin = to_linear_srgb(target_bgr_u8)
113
- lin_wb = lin * gains.reshape(1, 1, 3)
114
- tgt24_wb = tgt24 * gains
115
-
116
- # 2. Color Correction Matrix
117
- chroma_idx = np.arange(0, 18)
118
- M = solve_ccm_no_bias(tgt24_wb, ref24, chroma_idx)
119
-
120
- H, W = lin_wb.shape[:2]
121
- corrected = lin_wb.reshape(-1, 3) @ M.T
122
- corrected = corrected.reshape(H, W, 3)
123
-
124
- # FIX: Calculate Luma curve against the CCM-corrected patches!
125
- tgt24_ccm = tgt24_wb @ M.T
126
-
127
- # 3. Luma Curve
128
- map_luma = fit_monotone_luma_curve_midgrays(tgt24_ccm, ref24)
129
- w = np.array([0.2126, 0.7152, 0.0722], np.float32)
130
-
131
- L = np.clip(np.tensordot(corrected, w, axes=([2], [0])), 0, 1)
132
- Lt = map_luma(L)
133
- Lt = soft_highlight_rolloff(Lt, knee=0.90, strength=0.6)
134
-
135
- eps = 1e-6
136
- scale = (Lt + eps) / (L + eps)
137
- corrected = corrected * scale[..., None]
138
-
139
- corrected = np.clip(corrected, 0, 1)
140
- return to_srgb_u8(corrected)