r/Dashcam • u/Legitimate_Career212 • 2d ago
Video Can anyone help me pull the license plate from this dash cam footage?
https://www.dropbox.com/scl/fo/v4bx7t0brir1d5atf1d6e/AFmGj-mvnkSOJxUA-2Vgolo?rlkey=pl1an793s7ya50thzz4onw5hd&st=08xg0emu&dl=0•
u/Legitimate_Career212 2d ago
I was involved in a hit and run at a gas station in Lone Tree, CO. The other driver was in a white Toyota Camry and took off immediately after hitting me.
I managed to get the gas station's security footage, which shows the accident clearly, but the Camry didn't have a front license plate. Fortunately, my own dash cam caught the back of the car right as I was pulling into the station. The problem is the footage is slightly blurred, and I just can't quite make out the characters on the rear plate.
I've linked the raw dash cam footage and the security video below. Are there any wizards on here who can help enhance the video or decipher the plate? Any help, tips, or enhancements would be hugely appreciated!
•
u/Individdy 2d ago edited 2d ago
I asked Google AI Mode about Multi-Frame Super-Resolution (MF-SR) and it gave me some Python code to run on Google Colab (never used this before) and it worked. I fed it the video from 5.5 to 7.5 seconds, and had it crop down to just around the car. Not as sharp as I was hoping, but might help (I added an enhanced version with some unsharp mask to make text a little clearer): https://postimg.cc/gallery/fYmkYTg
Python code (I just went to https://colab.research.google.com/, created new notebook, pasted it in, uploaded a short clip with the folder icon where there was no major motion as dashcam_clip.mp4, and set target_box at the end of the code (left, top, width, height), clicked Run All at the top, refreshed the file area, and downloaded enhanced_plate.png):
import cv2
import numpy as np
def multi_frame_super_resolution_cropped(video_path, output_image_path, crop_box=None, scale_factor=2):
"""
Crops video frames on-the-fly and fuses them using dense optical flow
sub-pixel shifts to reconstruct a higher-resolution image.
crop_box: Tuple of (x, y, w, h) defining the region to isolate.
"""
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print("Error: Could not open video file.")
return
# Check original dimensions to ensure safety boundaries
orig_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
orig_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
frames = []
while True:
ret, frame = cap.read()
if not ret:
break
# Convert to grayscale first to maximize structural/luminance detail tracking
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Apply the crop dynamically in-memory (Avoiding Transcoding)
if crop_box is not None:
x, y, w, h = crop_box
# Ensure crop boundaries do not exceed the actual video dimensions
x, y = max(0, x), max(0, y)
w, h = min(w, orig_w - x), min(h, orig_h - y)
gray = gray[y:y+h, x:x+w]
frames.append(gray.astype(np.float32))
cap.release()
if len(frames) < 5:
print("Error: Provide a clip with at least 5-10 frames.")
return
print(f"Loaded {len(frames)} frames dynamically cropped to {frames[0].shape[::-1]}. Alignment starting...")
# Choose the middle frame as the anchor target grid
anchor_idx = len(frames) // 2
anchor_frame = frames[anchor_idx]
# Scale up the base target matrix dimensions
h, w = anchor_frame.shape
new_h, new_w = int(h * scale_factor), int(w * scale_factor)
# Initialize accumulators
high_res_accumulator = cv2.resize(anchor_frame, (new_w, new_h), interpolation=cv2.INTER_CUBIC)
weight_matrix = np.ones_like(high_res_accumulator)
for i, frame in enumerate(frames):
if i == anchor_idx:
continue
# Calculate sub-pixel displacements using dense optical flow
# Upgraded local tracking settings
flow = cv2.calcOpticalFlowFarneback(
prev=anchor_frame.astype(np.uint8),
next=frame.astype(np.uint8),
flow=None,
pyr_scale=0.5,
levels=5, # Increased from 3 to 5 for deeper layer tracking
winsize=21, # Increased from 15 to 21 to capture wider sub-pixel motion
iterations=7, # Increased from 3 to 7 for cleaner mathematical alignment
poly_n=7, # Increased from 5 to 7 for smoother pixel expansion
poly_sigma=1.5,
flags=0
)
#flow = cv2.calcOpticalFlowFarneback(
# prev=anchor_frame.astype(np.uint8),
# next=frame.astype(np.uint8),
# flow=None, pyr_scale=0.5, levels=3, winsize=15,
# iterations=3, poly_n=5, poly_sigma=1.2, flags=0
#)
# Scale displacement map to match the target high-res resolution
flow_scaled = cv2.resize(flow, (new_w, new_h), interpolation=cv2.INTER_LINEAR) * scale_factor
# Map pixel sub-pixel target adjustments
map_x, map_y = np.meshgrid(np.arange(new_w), np.arange(new_h))
map_x = (map_x + flow_scaled[..., 0]).astype(np.float32)
map_y = (map_y + flow_scaled[..., 1]).astype(np.float32)
# Remap the low-res elements onto the expanded grid coordinates
warped_frame = cv2.remap(
cv2.resize(frame, (new_w, new_h), interpolation=cv2.INTER_CUBIC),
map_x, map_y, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT
)
high_res_accumulator += warped_frame
weight_matrix += 1
# Fuse frame calculations together
fused_output = high_res_accumulator / weight_matrix
fused_output = np.clip(fused_output, 0, 255).astype(np.uint8)
# Post-processing: Enhance text legibility with a crisp unsharp mask
blurred = cv2.GaussianBlur(fused_output, (3, 3), 0)
enhanced = cv2.addWeighted(fused_output, 1.8, blurred, -0.8, 0)
cv2.imwrite(output_image_path, enhanced)
print(f"Super-resolution complete! File saved: {output_image_path}")
# --- CONFIGURATION & EXECUTION ---
video_clip = "dashcam_clip.mp4"
output_result = "enhanced_plate.png"
# Using your exact crop dimensions
target_box = (928, 429, 256, 256)
# Changed scale_factor to 4 for deeper sub-pixel density extraction
multi_frame_super_resolution_cropped(video_clip, output_result, crop_box=target_box, scale_factor=4)
Reading through the code, it basically assembles the frames from the crop in the video, chooses an anchor frame in the middle of the video as a motion reference, then for every frame, determines a subpixel offset (the slight camera motion due to vibration etc), and adjusts the image by this offset to align with the anchor position. It mixes all these frames together. Since the adjustments are in fractional pixels, it's able to reconstruct more resolution, thanks to the camera not being perfectly still. It's a variation on the way you can see through a slotted fence as you move by, because over time you get more information, even though at any particular moment you can only see through narrow slits.
•
•
u/DeliriousBlues 2d ago
Run a negative filter to see if the numbers come through