| |
| """Render demo/C per-frame images v3: clean, large fonts, clear scores.""" |
| import cv2, json, sys, logging |
| import numpy as np |
| from pathlib import Path |
|
|
| ROOT = Path("PROJECT_ROOT") |
| OUT = ROOT / "demo/C" |
| C_RESULTS = ROOT / "demo/C_results" |
|
|
| logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s") |
| log = logging.getLogger("render") |
|
|
| COLOR_BGR = { |
| "SILENT": (40, 190, 40), |
| "OBSERVE": (30, 190, 255), |
| "ALERT": (30, 30, 230), |
| } |
|
|
|
|
| def find_frame_dir(vid, src): |
| if src == "nexar": |
| num = vid.replace("nexar_", "") |
| for sp in ["train", "test-public", "test-private"]: |
| for po in ["positive", "negative"]: |
| p = ROOT / f"NEXAR_COLLISION/dataset/{sp}/{po}/{num}" |
| if p.exists(): return p |
| elif src == "dada": |
| name = vid.replace("dada_", "") |
| for cat in ["positive", "non-ego", "negative"]: |
| p = ROOT / f"DADA-2000/{cat}/{name}" |
| if p.exists(): return p |
| elif src == "dota": |
| raw = vid.replace("dota_", "") |
| p = ROOT / f"DoTA/frames/{raw}/images" |
| if p.exists(): return p |
| return None |
|
|
|
|
| def load_frame(frame_dir, idx): |
| for fmt in [f"{idx:06d}.jpg", f"{idx:05d}.jpg", f"{idx:04d}.jpg", |
| f"{idx:03d}.jpg", f"{idx}.jpg"]: |
| fp = frame_dir / fmt |
| if fp.exists(): |
| return cv2.imread(str(fp)) |
| return None |
|
|
|
|
| def get_fps(src): |
| return 20.0 if src in ("dada", "dota") else 30.0 |
|
|
|
|
| def put_text_bg(img, text, pos, font_scale, color, thickness=2, bg_alpha=0.6): |
| """Put text with dark background.""" |
| font = cv2.FONT_HERSHEY_SIMPLEX |
| (tw, th), baseline = cv2.getTextSize(text, font, font_scale, thickness) |
| x, y = pos |
| overlay = img.copy() |
| cv2.rectangle(overlay, (x - 4, y - th - 6), (x + tw + 4, y + baseline + 4), (0, 0, 0), -1) |
| cv2.addWeighted(overlay, bg_alpha, img, 1 - bg_alpha, 0, img) |
| cv2.putText(img, text, (x, y), font, font_scale, color, thickness, cv2.LINE_AA) |
|
|
|
|
| def render_gt_frame(img, action, tick_idx, t_sec): |
| H, W = img.shape[:2] |
| out = img.copy() |
| color = COLOR_BGR[action] |
|
|
| |
| bar_h = 60 |
| overlay = out.copy() |
| cv2.rectangle(overlay, (0, 0), (W, bar_h), color, -1) |
| cv2.addWeighted(overlay, 0.7, out, 0.3, 0, out) |
|
|
| cv2.putText(out, "Ground Truth", (15, 28), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2, cv2.LINE_AA) |
| cv2.putText(out, action, (W - 180, 28), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2, cv2.LINE_AA) |
| cv2.putText(out, f"t = {t_sec:.1f}s", (15, 52), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.55, (220, 220, 220), 1, cv2.LINE_AA) |
| return out |
|
|
|
|
| def render_badas_frame(img, action, p_alert, tick_idx, t_sec): |
| H, W = img.shape[:2] |
| out = img.copy() |
| color = COLOR_BGR[action] |
|
|
| |
| bar_h = 60 |
| overlay = out.copy() |
| cv2.rectangle(overlay, (0, 0), (W, bar_h), color, -1) |
| cv2.addWeighted(overlay, 0.7, out, 0.3, 0, out) |
|
|
| cv2.putText(out, "BADAS", (15, 28), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2, cv2.LINE_AA) |
| cv2.putText(out, action, (W - 180, 28), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2, cv2.LINE_AA) |
| cv2.putText(out, f"t = {t_sec:.1f}s", (15, 52), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.55, (220, 220, 220), 1, cv2.LINE_AA) |
|
|
| |
| bar_bot_h = 50 |
| overlay2 = out.copy() |
| cv2.rectangle(overlay2, (0, H - bar_bot_h), (W, H), (0, 0, 0), -1) |
| cv2.addWeighted(overlay2, 0.65, out, 0.35, 0, out) |
|
|
| |
| bar_x0, bar_x1 = 20, W - 20 |
| bar_y0, bar_y1 = H - bar_bot_h + 8, H - 10 |
| bar_w = bar_x1 - bar_x0 |
| fill_w = int(bar_w * min(p_alert, 1.0)) |
|
|
| |
| if p_alert < 0.5: |
| r = int(p_alert * 2 * 255) |
| fill_color = (0, 255 - r // 2, r) |
| else: |
| fill_color = (0, int((1 - p_alert) * 200), 230) |
|
|
| cv2.rectangle(out, (bar_x0, bar_y0), (bar_x0 + fill_w, bar_y1), fill_color, -1) |
| cv2.rectangle(out, (bar_x0, bar_y0), (bar_x1, bar_y1), (180, 180, 180), 1) |
|
|
| cv2.putText(out, f"Danger: {p_alert:.3f}", (bar_x0, bar_y0 - 3), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 1, cv2.LINE_AA) |
|
|
| return out |
|
|
|
|
| def render_vlalert_frame(img, action, p_alert, p_observe, p_silent, tick_idx, t_sec, |
| clip_danger=None, tta=None): |
| H, W = img.shape[:2] |
| out = img.copy() |
| color = COLOR_BGR[action] |
|
|
| |
| bar_h = 60 |
| overlay = out.copy() |
| cv2.rectangle(overlay, (0, 0), (W, bar_h), color, -1) |
| cv2.addWeighted(overlay, 0.7, out, 0.3, 0, out) |
|
|
| cv2.putText(out, "VLAlert", (15, 28), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2, cv2.LINE_AA) |
| cv2.putText(out, action, (W - 180, 28), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2, cv2.LINE_AA) |
| cv2.putText(out, f"t = {t_sec:.1f}s", (15, 52), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.55, (220, 220, 220), 1, cv2.LINE_AA) |
|
|
| |
| bar_bot_h = 65 |
| overlay2 = out.copy() |
| cv2.rectangle(overlay2, (0, H - bar_bot_h), (W, H), (0, 0, 0), -1) |
| cv2.addWeighted(overlay2, 0.65, out, 0.35, 0, out) |
|
|
| bar_x0, bar_x1 = 20, W - 20 |
| bar_w = bar_x1 - bar_x0 |
| bar_h_each = 14 |
| y = H - bar_bot_h + 6 |
|
|
| probs = [ |
| ("SILENT", p_silent, COLOR_BGR["SILENT"]), |
| ("OBSERVE", p_observe, COLOR_BGR["OBSERVE"]), |
| ("ALERT", p_alert, COLOR_BGR["ALERT"]), |
| ] |
|
|
| for label, prob, clr in probs: |
| fill_w = int(bar_w * min(prob, 1.0)) |
| cv2.rectangle(out, (bar_x0, y), (bar_x0 + fill_w, y + bar_h_each), clr, -1) |
| cv2.rectangle(out, (bar_x0, y), (bar_x1, y + bar_h_each), (120, 120, 120), 1) |
| cv2.putText(out, f"{label}: {prob:.2f}", (bar_x0 + 5, y + bar_h_each - 2), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1, cv2.LINE_AA) |
| y += bar_h_each + 2 |
|
|
| return out |
|
|
|
|
| def main(): |
| selected = json.load(open(OUT / "selected_6.json")) |
| log.info(f"Rendering {len(selected)} videos") |
|
|
| for v in selected: |
| vid = v["video_id"] |
| src = v["source"] |
| gt = v["gt"] |
|
|
| frame_dir = find_frame_dir(vid, src) |
| if frame_dir is None: |
| log.warning(f" {vid}: no frames, skip") |
| continue |
|
|
| fps = get_fps(src) |
| tick_interval = max(1, int(fps)) |
| n_ticks = len(gt) |
|
|
| scores_path = C_RESULTS / vid / "scores.json" |
| all_scores = json.load(open(scores_path)) if scores_path.exists() else {} |
|
|
| log.info(f" {vid} ({src}): {n_ticks} ticks") |
|
|
| |
| ref_ticks = next(iter(all_scores.values())) |
| actual_n = len(ref_ticks) |
|
|
| |
| gt_dir = OUT / vid / "GT" |
| gt_dir.mkdir(parents=True, exist_ok=True) |
| for ti, rt in enumerate(ref_ticks): |
| fidx = rt.get("frame", ti * tick_interval) |
| t_sec = rt.get("t", fidx / fps) |
| img = load_frame(frame_dir, fidx) |
| if img is None: |
| continue |
| gt_act = gt[ti] if ti < len(gt) else "SILENT" |
| cv2.imwrite(str(gt_dir / f"frame_{ti:03d}.png"), |
| render_gt_frame(img, gt_act, ti, t_sec)) |
|
|
| |
| for model_name, ticks in all_scores.items(): |
| is_badas = "BADAS" in model_name |
| folder_name = model_name.replace(" ", "_") |
| model_dir = OUT / vid / folder_name |
| model_dir.mkdir(parents=True, exist_ok=True) |
|
|
| for ti, td in enumerate(ticks): |
| fidx = td.get("frame", ti * tick_interval) |
| t_sec = td.get("t", fidx / fps) |
| img = load_frame(frame_dir, fidx) |
| if img is None: |
| continue |
|
|
| action = td.get("action", "SILENT") |
| p_alert = td.get("p_alert", 0) |
| p_observe = td.get("p_observe", 0) |
| p_silent = max(0, 1 - p_alert - p_observe) |
| clip_d = td.get("clip_danger", None) |
|
|
| if is_badas: |
| out = render_badas_frame(img, action, p_alert, ti, t_sec) |
| else: |
| out = render_vlalert_frame(img, action, p_alert, p_observe, p_silent, |
| ti, t_sec, clip_danger=clip_d) |
| cv2.imwrite(str(model_dir / f"frame_{ti:03d}.png"), out) |
|
|
| log.info(f" done") |
|
|
| log.info(f"\nAll done! → {OUT}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|