UrbanFlow / backend /speed.py
Subh775's picture
heavy add-ons: 13+ features implemented
d9ebe88
"""
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}