--- 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. ![Screenshot 2025-05-03 003226](https://hackmd.io/_uploads/SJKWMFGglx.png) - 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 ``` ![image](https://hackmd.io/_uploads/r1PedKGegx.png) This is the looks after running the code above. - Simply tap the url and copy the IP address as a password. ![Screenshot 2025-03-18 212903](https://hackmd.io/_uploads/HJg3uYMxxg.png) This is how the UI looks like. Simply drag or upload the videos, then download the processed videos as shown in the image.