ShadowGard3n commited on
Commit
36e7618
Β·
1 Parent(s): f078722

First commit

Browse files
Files changed (5) hide show
  1. Dockerfile +21 -0
  2. main.py +41 -0
  3. requirements.txt +8 -0
  4. utils.py +159 -0
  5. weights/best.pt +3 -0
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # System dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ libgl1 \
8
+ tesseract-ocr \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy files
12
+ COPY . .
13
+
14
+ # Install Python dependencies
15
+ RUN pip install --no-cache-dir -r requirements.txt
16
+
17
+ # Expose port (HF expects 7860)
18
+ EXPOSE 7860
19
+
20
+ # Run app
21
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
main.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ import numpy as np
4
+ import cv2
5
+ import shutil
6
+ import os
7
+ import uuid
8
+ from utils import process_image, process_video_stream
9
+
10
+ app = FastAPI(title="DeepSight Lite API")
11
+
12
+ app.add_middleware(
13
+ CORSMiddleware,
14
+ allow_origins=["*"],
15
+ allow_methods=["*"],
16
+ allow_headers=["*"],
17
+ )
18
+
19
+ @app.post("/predict-image")
20
+ async def predict_image(file: UploadFile = File(...)):
21
+ contents = await file.read()
22
+ nparr = np.frombuffer(contents, np.uint8)
23
+ img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
24
+ results = process_image(img)
25
+ return {"status": "success", "data": results}
26
+
27
+ @app.post("/predict-video")
28
+ async def predict_video(file: UploadFile = File(...)):
29
+ temp_path = f"temp_{uuid.uuid4()}.mp4"
30
+ with open(temp_path, "wb") as buffer:
31
+ shutil.copyfileobj(file.file, buffer)
32
+ try:
33
+ detected_plates = process_video_stream(temp_path)
34
+ return {"status": "success", "plates": detected_plates}
35
+ finally:
36
+ if os.path.exists(temp_path):
37
+ os.remove(temp_path)
38
+
39
+ if __name__ == "__main__":
40
+ import uvicorn
41
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ opencv-python-headless
4
+ numpy
5
+ ultralytics
6
+ easyocr
7
+ torch
8
+ torchvision
utils.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from ultralytics import YOLO
4
+ import easyocr
5
+ import os
6
+ import uuid
7
+
8
+ # =========================
9
+ # Load YOLO model
10
+ # =========================
11
+ MODEL_PATH = "weights/best.pt"
12
+ if not os.path.exists(MODEL_PATH):
13
+ print(f"❌ ERROR: Model weights not found at {MODEL_PATH}")
14
+
15
+ model = YOLO(MODEL_PATH)
16
+
17
+ # =========================
18
+ # EasyOCR Init
19
+ # =========================
20
+ reader = easyocr.Reader(['en'], gpu=False) # set gpu=False if needed
21
+
22
+ # =========================
23
+ # Debug folder
24
+ # =========================
25
+ DEBUG_DIR = "debug_crops"
26
+ os.makedirs(DEBUG_DIR, exist_ok=True)
27
+
28
+
29
+ # =========================
30
+ # OCR FUNCTION (EasyOCR)
31
+ # =========================
32
+ def get_ocr_text(crop, crop_id):
33
+ try:
34
+ if crop is None or crop.size == 0:
35
+ return None, 0.0
36
+
37
+ crop = cv2.resize(crop, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
38
+ crop_rgb = cv2.cvtColor(crop, cv2.COLOR_BGR2RGB)
39
+
40
+ cv2.imwrite(f"{DEBUG_DIR}/{crop_id}.jpg", crop)
41
+
42
+ results = reader.readtext(crop_rgb)
43
+
44
+ if not results:
45
+ return None, 0.0
46
+
47
+ texts = []
48
+ confidences = []
49
+
50
+ for (bbox, text, conf) in results:
51
+ clean_text = "".join([c for c in text if c.isalnum()])
52
+
53
+ if len(clean_text) >= 2: # allow small parts like "L8"
54
+ texts.append(clean_text)
55
+ confidences.append(conf)
56
+
57
+ if not texts:
58
+ return None, 0.0
59
+
60
+ # πŸ”₯ SORT by vertical position (top β†’ bottom)
61
+ results_sorted = sorted(results, key=lambda x: min([p[1] for p in x[0]]))
62
+
63
+ final_text = ""
64
+ for (_, text, _) in results_sorted:
65
+ clean = "".join([c for c in text if c.isalnum()])
66
+ if len(clean) >= 2:
67
+ final_text += clean
68
+
69
+ avg_conf = sum(confidences) / len(confidences)
70
+
71
+ print(f"βœ… OCR [{crop_id}] -> {final_text}")
72
+
73
+ return final_text, float(avg_conf)
74
+
75
+ except Exception as e:
76
+ print(f"OCR Error: {e}")
77
+ return None, 0.0
78
+
79
+ # =========================
80
+ # IMAGE PROCESSING
81
+ # =========================
82
+ def process_image(image):
83
+ print(f"\n[STEP 1] Running YOLO detection...")
84
+ results = model(image, imgsz=320, verbose=False)
85
+
86
+ plates = []
87
+
88
+ found_count = len(results[0].boxes)
89
+ print(f"[STEP 2] YOLO found {found_count} bounding boxes.")
90
+
91
+ for r in results:
92
+ if r.boxes:
93
+ for box in r.boxes.xyxy.cpu().numpy():
94
+ crop_id = f"plate_{uuid.uuid4().hex[:6]}"
95
+
96
+ x1, y1, x2, y2 = map(int, box)
97
+
98
+ # πŸ”₯ IMPORTANT FIX: bigger padding
99
+ h, w, _ = image.shape
100
+ pad = 15
101
+
102
+ crop = image[
103
+ max(0, y1 - pad):min(h, y2 + pad),
104
+ max(0, x1 - pad):min(w, x2 + pad)
105
+ ]
106
+
107
+ print(f"[STEP 3] Processing {crop_id}...")
108
+ text, conf = get_ocr_text(crop, crop_id)
109
+
110
+ if text:
111
+ plates.append({
112
+ "text": text,
113
+ "confidence": conf,
114
+ "debug_id": crop_id
115
+ })
116
+ else:
117
+ print(f" ⚠️ OCR failed for {crop_id}")
118
+
119
+ return plates
120
+
121
+
122
+ # =========================
123
+ # VIDEO PROCESSING
124
+ # =========================
125
+ def process_video_stream(video_path):
126
+ print(f"\nπŸŽ₯ Processing video: {video_path}")
127
+
128
+ cap = cv2.VideoCapture(video_path)
129
+ tracked_plates = {}
130
+ final_results = []
131
+
132
+ while cap.isOpened():
133
+ ret, frame = cap.read()
134
+ if not ret:
135
+ break
136
+
137
+ results = model.track(frame, persist=True, imgsz=320, verbose=False)
138
+
139
+ if results[0].boxes.id is not None:
140
+ ids = results[0].boxes.id.int().cpu().tolist()
141
+ boxes = results[0].boxes.xyxy.cpu().numpy()
142
+
143
+ for box, tid in zip(boxes, ids):
144
+ if tid not in tracked_plates:
145
+ x1, y1, x2, y2 = map(int, box)
146
+
147
+ crop_id = f"track_{tid}"
148
+
149
+ crop = frame[y1:y2, x1:x2]
150
+
151
+ text, conf = get_ocr_text(crop, crop_id)
152
+
153
+ if text and len(text) >= 5:
154
+ print(f"βœ… Detected Plate: {text}")
155
+ tracked_plates[tid] = text
156
+ final_results.append(text)
157
+
158
+ cap.release()
159
+ return list(set(final_results))
weights/best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bf6cb0453d80644f55f3c75e8bd34787e53a4a445b3a5e976ce128d008241a8f
3
+ size 5428314