---
title: LPR Documentation
---
# LPR Documentation
## Collecting Data
- Collecting data from github, no copyright Youtube footage, and user daily footage.
- https://youtu.be/UpleTeyFPJ0?si=M0P-mkP80Ivcljfo
- https://youtu.be/FOo0AbigryE?si=cTwvDfNwbS_7Gbsx
- https://youtu.be/jek6V2UlMUo?si=QmznOYTNyBPLCxsX
- https://github.com/mendez-luisjose/License-Plate-Detection-with-YoloV8-and-EasyOCR
- https://github.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR
## Labeling Data
- We're using website to annotate the data we've collected. In this case we using Roboflow to label our data.

- The interface and the usage is as shown above. ctrl+b to mark the object we want to annotate, and define the class (In this case it's 88888888 (License Plate)).
## Training Data
- After annotating all of the data we've collected next we need to train the dataset.
```python=
from ultralytics import YOLO
# Load a YOLOv8m model (pre-trained weights)
model = YOLO("yolov8m.pt")
# Train the model on your dataset
model.train(
data="dataset/data.yaml", # path to the YAML config
epochs=50,
imgsz=640,
batch=16,
workers=4,
project="my_yolo_project",
name="yolov8m_custom",
exist_ok=True # overwrite if the project folder exists
)
```
Above shown is the **example** of a training scripts using Yolov8
- After extracting the zip file or the Roboflow script, we just need to import the path to the data.yaml file into the training script.
- The output will be a model that's ready to be used for implementation on video.
## Inference
- Next step is to make the inference script to implement the model.
```python=
%%writefile app.py
import streamlit as st
import cv2
import numpy as np
import torch
import os
import warnings
from datetime import datetime
from ultralytics import YOLO
from ByteTrack.yolox.tracker.byte_tracker import BYTETracker
from configs.tracker_cfg import args
from sam2.build_sam import build_sam2
from sam2.sam2_image_predictor import SAM2ImagePredictor
import tempfile
import shutil
# Disable warnings for cleaner output
warnings.filterwarnings("ignore")
# ===== Debug Configuration =====
DEBUG = True
DEBUG_DIR = tempfile.mkdtemp()
os.makedirs(DEBUG_DIR, exist_ok=True)
# ===== Step 1: Initialize Models =====
try:
yolo_model = YOLO("V5.pt")
checkpoint = "./sam2/checkpoints/sam2.1_hiera_small.pt"
model_cfg = "./configs/sam2.1/sam2.1_hiera_s.yaml"
sam2_model = build_sam2(model_cfg, checkpoint)
predictor = SAM2ImagePredictor(sam2_model)
tracker = BYTETracker(args=args, frame_rate=args.fps)
except Exception as e:
st.error(f"Error loading models: {str(e)}")
st.stop()
if not torch.cuda.is_available():
st.warning("GPU is not available. Processing may be slow on CPU.")
# ===== Step 2: Utility Functions =====
def log(message, image=None, prefix=""):
if DEBUG:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
print(f"[{timestamp}] {message}")
if image is not None:
filename = f"{DEBUG_DIR}/{prefix}_{timestamp}.png"
cv2.imwrite(filename, image)
def apply_mask(frame, mask, x1, y1, x2, y2):
try:
current_height = y2 - y1
current_width = x2 - x1
resized_mask = cv2.resize(
(mask * 255).astype(np.uint8),
(current_width, current_height),
interpolation=cv2.INTER_NEAREST
)
frame[y1:y2, x1:x2][resized_mask > 0] = 255
except Exception as e:
print(f"Error applying mask: {str(e)}")
def segment_plate(cropped_plate):
try:
cropped_plate_rgb = cv2.cvtColor(cropped_plate, cv2.COLOR_BGR2RGB)
h, w = cropped_plate_rgb.shape[:2]
scale = 0.9
new_w = int(w * scale)
new_h = int(h * scale)
x1 = (w - new_w) // 2
y1 = (h - new_h) // 2
x2 = x1 + new_w
y2 = y1 + new_h
prompt_box = np.array([[x1, y1, x2, y2]])
center_point = np.array([[w // 2, h // 2]])
point_labels = np.array([1])
with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16):
predictor.set_image(cropped_plate_rgb)
masks, scores, _ = predictor.predict(
box=prompt_box,
point_coords=center_point,
point_labels=point_labels,
multimask_output=False
)
if masks is not None and len(masks) > 0:
best_mask = masks[np.argmax(scores)]
return best_mask
return None
except Exception as e:
print(f"Error during segmentation: {str(e)}")
return None
# ===== Step 3: Process Video =====
def process_video(input_path, output_path, temp_crop_folder):
try:
cap = cv2.VideoCapture(input_path)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
out = cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))
tracker = BYTETracker(args=args, frame_rate=fps)
mask_memory = {}
frame_count = 0
os.makedirs(temp_crop_folder, exist_ok=True)
track_id_map = {}
next_track_id = 1
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
frame_count += 1
results = yolo_model(frame)[0]
detections = [box.xyxy[0].cpu().numpy().tolist() + [box.conf.item()] for box in results.boxes]
tracks = tracker.update(np.array(detections), (height, width), (height, width)) if detections else []
for track in tracks:
tracker_track_id = int(track.track_id)
if tracker_track_id not in track_id_map:
track_id_map[tracker_track_id] = next_track_id
next_track_id += 1
my_track_id = track_id_map[tracker_track_id]
x1, y1, x2, y2 = track.tlbr.astype(int)
# Clamp coordinates to frame boundaries
x1 = max(0, x1)
y1 = max(0, y1)
x2 = min(width, x2)
y2 = min(height, y2)
# Check if the crop region is valid
if x1 < x2 and y1 < y2:
cropped_plate = frame[y1:y2, x1:x2]
temp_file = os.path.join(temp_crop_folder, f"track_{my_track_id}_frame_{frame_count}.jpg")
cv2.imwrite(temp_file, cropped_plate)
if my_track_id not in mask_memory:
best_mask = segment_plate(cropped_plate)
if best_mask is not None:
mask_memory[my_track_id] = best_mask
log(f"Segmentation mask for track ID {my_track_id} created",
(best_mask * 255).astype(np.uint8), f"mask_{my_track_id}")
else:
print(f"Segmentation failed for track ID {my_track_id} on frame {frame_count}")
continue
if my_track_id in mask_memory:
apply_mask(frame, mask_memory[my_track_id], x1, y1, x2, y2)
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(frame, str(my_track_id), (x1 + 5, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
else:
print(f"Invalid crop coordinates for track ID {my_track_id}: ({x1}, {y1}, {x2}, {y2})")
out.write(frame)
log("Frame processed", frame, "frame_output")
cap.release()
out.release()
except Exception as e:
st.error(f"Error processing video: {str(e)}")
raise e
# ===== Step 4: Streamlit App =====
def main():
st.title("License Plate Detection and Segmentation")
uploaded_file = st.file_uploader("Upload a video", type=["mp4", "avi", "mov"])
if uploaded_file is not None:
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as temp_input:
temp_input.write(uploaded_file.read())
input_path = temp_input.name
output_path = os.path.join(tempfile.gettempdir(), "processed_video.mp4")
temp_crop_folder = tempfile.mkdtemp()
try:
st.write("Processing video...")
process_video(input_path, output_path, temp_crop_folder)
st.write("Video processed successfully!")
st.video(output_path)
with open(output_path, "rb") as file:
st.download_button(
label="Download Processed Video",
data=file,
file_name="processed_video.mp4",
mime="video/mp4"
)
finally:
# Clean up temporary files and directories
if os.path.exists(input_path):
os.remove(input_path)
if os.path.exists(output_path):
os.remove(output_path)
if os.path.exists(temp_crop_folder):
shutil.rmtree(temp_crop_folder)
if os.path.exists(DEBUG_DIR):
shutil.rmtree(DEBUG_DIR)
if __name__ == "__main__":
main()
```
- In this case, we're using colab to run the inference code. we convert the script shown above to app.py.
- as we're integrating Streamlit as a UI for inference purpose, we need to run a Streamlit web application in a local environment, and make it publicly accessible on the internet using LocalTunnel as shown below.
```python=
print("Dependencies installed. Now running the app...")
!echo "Put this IP as password:"
!wget -q -O - ipv4.icanhazip.com
!streamlit run app.py --server.fileWatcherType none & npx localtunnel --port 8501
```

This is the looks after running the code above.
- Simply tap the url and copy the IP address as a password.

This is how the UI looks like. Simply drag or upload the videos, then download the processed videos as shown in the image.