Spaces:
Running
Running
| """ | |
| Lightweight speed estimation using pixel displacement between frames. | |
| No camera calibration required — classifies vehicles into relative | |
| speed categories (Slow/Normal/Fast) based on percentile ranking | |
| of pixel-per-frame displacement within the video. | |
| """ | |
| import numpy as np | |
| def estimate_speeds(track_positions): | |
| """Compute per-track speed category from centroid displacement history. | |
| Args: | |
| track_positions: dict {track_id: [(frame_idx, cx, cy), ...]} | |
| Returns: | |
| dict with per_track speeds and aggregate distribution | |
| """ | |
| displacements = {} | |
| for tid, positions in track_positions.items(): | |
| if len(positions) < 2: | |
| continue | |
| positions.sort(key=lambda x: x[0]) | |
| total_disp = 0.0 | |
| count = 0 | |
| for i in range(1, len(positions)): | |
| dx = positions[i][1] - positions[i - 1][1] | |
| dy = positions[i][2] - positions[i - 1][2] | |
| total_disp += np.sqrt(dx * dx + dy * dy) | |
| count += 1 | |
| displacements[tid] = total_disp / count if count > 0 else 0.0 | |
| if not displacements: | |
| return {"per_track": {}, "distribution": {"slow": 0, "normal": 0, "fast": 0}} | |
| speeds = np.array(list(displacements.values())) | |
| p33 = np.percentile(speeds, 33) | |
| p66 = np.percentile(speeds, 66) | |
| per_track = {} | |
| counts = {"slow": 0, "normal": 0, "fast": 0} | |
| for tid, spd in displacements.items(): | |
| if spd <= p33: | |
| cat = "slow" | |
| elif spd <= p66: | |
| cat = "normal" | |
| else: | |
| cat = "fast" | |
| per_track[int(tid)] = {"px_per_frame": round(spd, 1), "category": cat} | |
| counts[cat] += 1 | |
| total = sum(counts.values()) | |
| distribution = { | |
| "slow": round(counts["slow"] / total * 100, 1) if total else 0, | |
| "normal": round(counts["normal"] / total * 100, 1) if total else 0, | |
| "fast": round(counts["fast"] / total * 100, 1) if total else 0, | |
| } | |
| return {"per_track": per_track, "distribution": distribution} | |