Spaces:
Runtime error
Runtime error
Arslan Akhtar
commited on
Commit
•
8afbbf2
1
Parent(s):
21b3049
Upload 6 files
Browse files- .gitignore +40 -0
- ANPR.ipynb +0 -0
- README.md +39 -6
- app.py +331 -0
- deploy.yml +34 -0
- requirements.txt +5 -0
.gitignore
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Logs
|
2 |
+
logs
|
3 |
+
*.log
|
4 |
+
*.log.mbtree
|
5 |
+
npm-debug.log*
|
6 |
+
yarn-debug.log*
|
7 |
+
yarn-error.log*
|
8 |
+
pnpm-debug.log*
|
9 |
+
lerna-debug.log*
|
10 |
+
|
11 |
+
node_modules
|
12 |
+
dist
|
13 |
+
dist-ssr
|
14 |
+
*.local
|
15 |
+
pnpm-lock.yaml
|
16 |
+
|
17 |
+
# Editor directories and files
|
18 |
+
.vscode/*
|
19 |
+
!.vscode/extensions.json
|
20 |
+
.idea
|
21 |
+
.DS_Store
|
22 |
+
*.suo
|
23 |
+
*.ntvs*
|
24 |
+
*.njsproj
|
25 |
+
*.sln
|
26 |
+
*.sw?
|
27 |
+
|
28 |
+
__pycache__
|
29 |
+
.ipynb_checkpoints
|
30 |
+
gradio_cached_examples
|
31 |
+
*.pt
|
32 |
+
deep_sort
|
33 |
+
yolov7
|
34 |
+
*.mp4
|
35 |
+
*.tar.gz
|
36 |
+
examples
|
37 |
+
anpr-dataset
|
38 |
+
runs
|
39 |
+
anpr_weights
|
40 |
+
anpr_examples_202208
|
ANPR.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
README.md
CHANGED
@@ -1,12 +1,45 @@
|
|
1 |
---
|
2 |
-
title: Number
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
-
sdk_version: 3.28.2
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
---
|
11 |
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
+
title: Automatic Number-Plate Recognition
|
3 |
+
emoji: 🚘
|
4 |
+
colorFrom: red
|
5 |
+
colorTo: gray
|
6 |
sdk: gradio
|
|
|
7 |
app_file: app.py
|
8 |
pinned: false
|
9 |
---
|
10 |
|
11 |
+
# Automatic Number Plate Recognition
|
12 |
+
|
13 |
+
> AI to detect and recognize number plates on vehicles.
|
14 |
+
|
15 |
+
## Table of contents
|
16 |
+
|
17 |
+
- [General information](#general-information)
|
18 |
+
- [Dataset](#dataset)
|
19 |
+
- [How does it work](#how-does-it-work)
|
20 |
+
|
21 |
+
## [Live demo](https://huggingface.co/spaces/itsyoboieltr/anpr)
|
22 |
+
|
23 |
+
## General information
|
24 |
+
|
25 |
+
This is an AI that was trained on images of number plates to carry out number plate detection and recognition. It works for both images and videos. Video detection also includes object tracking.
|
26 |
+
|
27 |
+
<img width="300" src="https://user-images.githubusercontent.com/72046715/183776545-c51843c9-d350-4f4f-aa4f-1168e6922904.png">
|
28 |
+
|
29 |
+
## Dataset
|
30 |
+
|
31 |
+
For this project, I created the [ANPR dataset](https://archive.org/details/anpr-dataset), a dataset of approx. 30k handpicked images of number plates.
|
32 |
+
|
33 |
+
Annotations are in YOLO format.
|
34 |
+
|
35 |
+
<img width="600" src="https://user-images.githubusercontent.com/72046715/183776762-7e0d9822-80a1-442e-a111-2fbc03b8213c.png">
|
36 |
+
|
37 |
+
## How does it work
|
38 |
+
|
39 |
+
Technologies used:
|
40 |
+
|
41 |
+
- [YOLOv8](https://github.com/ultralytics/ultralytics): Object detection model to detect the number plate
|
42 |
+
- [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR): OCR to read the number plate
|
43 |
+
- [Deep SORT](https://github.com/levan92/deep_sort_realtime): Object tracking algorithm for video detection
|
44 |
+
|
45 |
+
The YOLOv8 Model was fine-tuned using the ANPR dataset to detect number plates. When a number plate is detected, PaddleOCR is used to read the number plate. For video detection, Deep SORT is used to handle object tracking.
|
app.py
ADDED
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import gradio as gr # type: ignore
|
3 |
+
from paddleocr import PaddleOCR # type: ignore
|
4 |
+
from ultralytics import YOLO # type: ignore
|
5 |
+
from pathlib import Path
|
6 |
+
from deep_sort_realtime.deepsort_tracker import DeepSort # type: ignore
|
7 |
+
import cv2 # type: ignore
|
8 |
+
import numpy as np
|
9 |
+
import re
|
10 |
+
from internetarchive import download # type: ignore
|
11 |
+
from tqdm import trange
|
12 |
+
|
13 |
+
download("anpr_weights", files=["anpr.pt"], verbose=True) # type: ignore
|
14 |
+
|
15 |
+
download(
|
16 |
+
"anpr_examples_202208",
|
17 |
+
files=["test_image_1.jpg", "test_image_2.jpg", "test_image_3.jpeg", "test_video_1.mp4"], # type: ignore
|
18 |
+
verbose=True,
|
19 |
+
)
|
20 |
+
|
21 |
+
paddle = PaddleOCR(lang="en", use_angle_cls=True, show_log=False)
|
22 |
+
|
23 |
+
model = YOLO(model="./anpr_weights/anpr.pt", task="detect")
|
24 |
+
|
25 |
+
|
26 |
+
def detect_plates(src):
|
27 |
+
predictions = model.predict(src, verbose=False)
|
28 |
+
|
29 |
+
results = []
|
30 |
+
|
31 |
+
for prediction in predictions:
|
32 |
+
for box in prediction.boxes:
|
33 |
+
det_confidence = box.conf.item()
|
34 |
+
if det_confidence < 0.6:
|
35 |
+
continue
|
36 |
+
coords = [int(position) for position in (box.xyxy.view(1, 4)).tolist()[0]]
|
37 |
+
results.append({"coords": coords, "det_conf": det_confidence})
|
38 |
+
|
39 |
+
return results
|
40 |
+
|
41 |
+
|
42 |
+
def crop(img, coords):
|
43 |
+
cropped = img[coords[1] : coords[3], coords[0] : coords[2]]
|
44 |
+
return cropped
|
45 |
+
|
46 |
+
|
47 |
+
def preprocess_image(src):
|
48 |
+
normalize = cv2.normalize(
|
49 |
+
src, np.zeros((src.shape[0], src.shape[1])), 0, 255, cv2.NORM_MINMAX
|
50 |
+
)
|
51 |
+
denoise = cv2.fastNlMeansDenoisingColored(
|
52 |
+
normalize, h=10, hColor=10, templateWindowSize=7, searchWindowSize=15
|
53 |
+
)
|
54 |
+
grayscale = cv2.cvtColor(denoise, cv2.COLOR_BGR2GRAY)
|
55 |
+
threshold = cv2.threshold(grayscale, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
|
56 |
+
return threshold
|
57 |
+
|
58 |
+
|
59 |
+
def ocr_plate(src):
|
60 |
+
# Preprocess the image for better OCR results
|
61 |
+
preprocessed = preprocess_image(src)
|
62 |
+
|
63 |
+
# OCR the preprocessed image
|
64 |
+
results = paddle.ocr(preprocessed, det=False, cls=True)
|
65 |
+
|
66 |
+
# Get the best OCR result
|
67 |
+
plate_text, ocr_confidence = max(
|
68 |
+
results,
|
69 |
+
key=lambda ocr_prediction: max(
|
70 |
+
ocr_prediction,
|
71 |
+
key=lambda ocr_prediction_result: ocr_prediction_result[1], # type: ignore
|
72 |
+
),
|
73 |
+
)[0]
|
74 |
+
|
75 |
+
# Filter out anything but uppercase letters, digits, hypens and whitespace.
|
76 |
+
# Also, remove hypens and whitespaces at the first and last positions
|
77 |
+
plate_text_filtered = re.sub(r"[^A-Z0-9- ]", "", plate_text).strip("- ")
|
78 |
+
|
79 |
+
return {"plate": plate_text_filtered, "ocr_conf": ocr_confidence}
|
80 |
+
|
81 |
+
|
82 |
+
def ocr_plates(src, det_predictions):
|
83 |
+
results = []
|
84 |
+
|
85 |
+
for det_prediction in det_predictions:
|
86 |
+
plate_region = crop(src, det_prediction["coords"])
|
87 |
+
ocr_prediction = ocr_plate(plate_region)
|
88 |
+
results.append(ocr_prediction)
|
89 |
+
|
90 |
+
return results
|
91 |
+
|
92 |
+
|
93 |
+
def plot_box(img, coords, label=None, color=[0, 150, 255], line_thickness=3):
|
94 |
+
# Plots box on image
|
95 |
+
c1, c2 = (int(coords[0]), int(coords[1])), (int(coords[2]), int(coords[3]))
|
96 |
+
cv2.rectangle(img, c1, c2, color, thickness=line_thickness, lineType=cv2.LINE_AA)
|
97 |
+
# Plots label on image, if exists
|
98 |
+
if label:
|
99 |
+
tf = max(line_thickness - 1, 1) # font thickness
|
100 |
+
t_size = cv2.getTextSize(label, 0, fontScale=line_thickness / 3, thickness=tf)[
|
101 |
+
0
|
102 |
+
]
|
103 |
+
c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
|
104 |
+
cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA) # filled
|
105 |
+
cv2.putText(
|
106 |
+
img,
|
107 |
+
label,
|
108 |
+
(c1[0], c1[1] - 2),
|
109 |
+
0,
|
110 |
+
line_thickness / 3,
|
111 |
+
[225, 255, 255],
|
112 |
+
thickness=tf,
|
113 |
+
lineType=cv2.LINE_AA,
|
114 |
+
)
|
115 |
+
|
116 |
+
|
117 |
+
def get_plates(src):
|
118 |
+
det_predictions = detect_plates(src)
|
119 |
+
ocr_predictions = ocr_plates(src, det_predictions)
|
120 |
+
|
121 |
+
for det_prediction, ocr_prediction in zip(det_predictions, ocr_predictions):
|
122 |
+
plot_box(src, det_prediction["coords"], ocr_prediction["plate"])
|
123 |
+
|
124 |
+
return src, det_predictions, ocr_predictions
|
125 |
+
|
126 |
+
|
127 |
+
def predict_image(src):
|
128 |
+
detected_image, det_predictions, ocr_predictions = get_plates(src)
|
129 |
+
return detected_image
|
130 |
+
|
131 |
+
|
132 |
+
def predict_image_api(src):
|
133 |
+
detected_image, det_predictions, ocr_predictions = get_plates(src)
|
134 |
+
return ocr_predictions[0]["plate"]
|
135 |
+
|
136 |
+
|
137 |
+
def pascal_voc_to_coco(x1y1x2y2):
|
138 |
+
x1, y1, x2, y2 = x1y1x2y2
|
139 |
+
return [x1, y1, x2 - x1, y2 - y1]
|
140 |
+
|
141 |
+
|
142 |
+
def get_best_ocr(preds, rec_conf, ocr_res, track_id):
|
143 |
+
for info in preds:
|
144 |
+
# Check if it is current track id
|
145 |
+
if info["track_id"] == track_id:
|
146 |
+
# Check if the ocr confidence is maximum or not
|
147 |
+
if info["ocr_conf"] < rec_conf:
|
148 |
+
info["ocr_conf"] = rec_conf
|
149 |
+
info["ocr_txt"] = ocr_res
|
150 |
+
else:
|
151 |
+
rec_conf = info["ocr_conf"]
|
152 |
+
ocr_res = info["ocr_txt"]
|
153 |
+
break
|
154 |
+
return preds, rec_conf, ocr_res
|
155 |
+
|
156 |
+
|
157 |
+
def predict_video(src):
|
158 |
+
output = f"{Path(src).stem}_detected{Path(src).suffix}"
|
159 |
+
|
160 |
+
# Create a VideoCapture object
|
161 |
+
video = cv2.VideoCapture(src)
|
162 |
+
|
163 |
+
# Default resolutions of the frame are obtained. The default resolutions are system dependent.
|
164 |
+
# We convert the resolutions from float to integer.
|
165 |
+
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
|
166 |
+
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
167 |
+
fps = video.get(cv2.CAP_PROP_FPS)
|
168 |
+
frames_total = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
169 |
+
|
170 |
+
# Define the codec and create VideoWriter object.
|
171 |
+
temp = f"{Path(output).stem}_temp{Path(output).suffix}"
|
172 |
+
export = cv2.VideoWriter(
|
173 |
+
temp, cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height)
|
174 |
+
)
|
175 |
+
|
176 |
+
# Intializing tracker
|
177 |
+
tracker = DeepSort()
|
178 |
+
|
179 |
+
# Initializing some helper variables.
|
180 |
+
preds = []
|
181 |
+
total_obj = 0
|
182 |
+
|
183 |
+
for i in trange(frames_total):
|
184 |
+
ret, frame = video.read()
|
185 |
+
if ret is True:
|
186 |
+
# Run the ANPR algorithm
|
187 |
+
det_predictions = detect_plates(frame)
|
188 |
+
# Convert Pascal VOC detections to COCO
|
189 |
+
bboxes = list(
|
190 |
+
map(
|
191 |
+
lambda bbox: pascal_voc_to_coco(bbox),
|
192 |
+
[det_prediction["coords"] for det_prediction in det_predictions],
|
193 |
+
)
|
194 |
+
)
|
195 |
+
|
196 |
+
if len(bboxes) > 0:
|
197 |
+
# Storing all the required info in a list.
|
198 |
+
detections = [
|
199 |
+
(bbox, score, "number_plate")
|
200 |
+
for bbox, score in zip(
|
201 |
+
bboxes,
|
202 |
+
[
|
203 |
+
det_prediction["det_conf"]
|
204 |
+
for det_prediction in det_predictions
|
205 |
+
],
|
206 |
+
)
|
207 |
+
]
|
208 |
+
|
209 |
+
# Applying tracker.
|
210 |
+
# The tracker code flow: kalman filter -> target association(using hungarian algorithm) and appearance descriptor.
|
211 |
+
tracks = tracker.update_tracks(detections, frame=frame)
|
212 |
+
|
213 |
+
# Checking if tracks exist.
|
214 |
+
for track in tracks:
|
215 |
+
if not track.is_confirmed() or track.time_since_update > 1:
|
216 |
+
continue
|
217 |
+
|
218 |
+
# Changing track bbox to top left, bottom right coordinates
|
219 |
+
bbox = [int(position) for position in list(track.to_tlbr())]
|
220 |
+
|
221 |
+
for i in range(len(bbox)):
|
222 |
+
if bbox[i] < 0:
|
223 |
+
bbox[i] = 0
|
224 |
+
|
225 |
+
# Cropping the license plate and applying the OCR.
|
226 |
+
plate_region = crop(frame, bbox)
|
227 |
+
ocr_prediction = ocr_plate(plate_region)
|
228 |
+
plate_text, ocr_confidence = (
|
229 |
+
ocr_prediction["plate"],
|
230 |
+
ocr_prediction["ocr_conf"],
|
231 |
+
)
|
232 |
+
|
233 |
+
# Storing the ocr output for corresponding track id.
|
234 |
+
output_frame = {
|
235 |
+
"track_id": track.track_id,
|
236 |
+
"ocr_txt": plate_text,
|
237 |
+
"ocr_conf": ocr_confidence,
|
238 |
+
}
|
239 |
+
|
240 |
+
# Appending track_id to list only if it does not exist in the list
|
241 |
+
# else looking for the current track in the list and updating the highest confidence of it.
|
242 |
+
if track.track_id not in list(
|
243 |
+
set(pred["track_id"] for pred in preds)
|
244 |
+
):
|
245 |
+
total_obj += 1
|
246 |
+
preds.append(output_frame)
|
247 |
+
else:
|
248 |
+
preds, ocr_confidence, plate_text = get_best_ocr(
|
249 |
+
preds,
|
250 |
+
ocr_confidence,
|
251 |
+
plate_text,
|
252 |
+
track.track_id,
|
253 |
+
)
|
254 |
+
|
255 |
+
# Plotting the prediction.
|
256 |
+
plot_box(
|
257 |
+
frame,
|
258 |
+
bbox,
|
259 |
+
f"{str(track.track_id)}. {plate_text}",
|
260 |
+
color=[255, 150, 0],
|
261 |
+
)
|
262 |
+
|
263 |
+
# Write the frame into the output file
|
264 |
+
export.write(frame)
|
265 |
+
else:
|
266 |
+
break
|
267 |
+
|
268 |
+
# When everything done, release the video capture and video write objects
|
269 |
+
video.release()
|
270 |
+
export.release()
|
271 |
+
|
272 |
+
# Compressing the video for smaller size and web compatibility.
|
273 |
+
os.system(
|
274 |
+
f"ffmpeg -y -i {temp} -c:v libx264 -b:v 5000k -minrate 1000k -maxrate 8000k -pass 1 -c:a aac -f mp4 /dev/null && ffmpeg -y -i {temp} -c:v libx264 -b:v 5000k -minrate 1000k -maxrate 8000k -pass 2 -c:a aac -movflags faststart {output}"
|
275 |
+
)
|
276 |
+
os.system(f"rm -rf {temp} ffmpeg2pass-0.log ffmpeg2pass-0.log.mbtree")
|
277 |
+
return output
|
278 |
+
|
279 |
+
|
280 |
+
with gr.Blocks() as demo:
|
281 |
+
gr.Markdown('### <h3 align="center">Automatic Number Plate Recognition</h3>')
|
282 |
+
gr.Markdown(
|
283 |
+
"This AI was trained to detect and recognize number plates on vehicles."
|
284 |
+
)
|
285 |
+
with gr.Tabs():
|
286 |
+
with gr.TabItem("Image"):
|
287 |
+
with gr.Row():
|
288 |
+
image_input = gr.Image()
|
289 |
+
image_output = gr.Image()
|
290 |
+
image_input.upload(
|
291 |
+
predict_image,
|
292 |
+
inputs=[image_input],
|
293 |
+
outputs=[image_output],
|
294 |
+
)
|
295 |
+
with gr.Row(visible=False): # Prediction API
|
296 |
+
api_image_input = gr.Image()
|
297 |
+
api_prediction_output = gr.Textbox()
|
298 |
+
api_image_input.upload(
|
299 |
+
predict_image_api,
|
300 |
+
inputs=[api_image_input],
|
301 |
+
outputs=[api_prediction_output],
|
302 |
+
api_name="predict",
|
303 |
+
)
|
304 |
+
gr.Examples(
|
305 |
+
[
|
306 |
+
["./anpr_examples_202208/test_image_1.jpg"],
|
307 |
+
["./anpr_examples_202208/test_image_2.jpg"],
|
308 |
+
["./anpr_examples_202208/test_image_3.jpeg"],
|
309 |
+
],
|
310 |
+
[image_input],
|
311 |
+
[image_output],
|
312 |
+
predict_image,
|
313 |
+
cache_examples=True,
|
314 |
+
)
|
315 |
+
with gr.TabItem("Video"):
|
316 |
+
with gr.Row():
|
317 |
+
video_input = gr.Video(format="mp4")
|
318 |
+
video_output = gr.Video(format="mp4")
|
319 |
+
video_input.upload(
|
320 |
+
predict_video, inputs=[video_input], outputs=[video_output]
|
321 |
+
)
|
322 |
+
gr.Examples(
|
323 |
+
[["./anpr_examples_202208/test_video_1.mp4"]],
|
324 |
+
[video_input],
|
325 |
+
[video_output],
|
326 |
+
predict_video,
|
327 |
+
cache_examples=True,
|
328 |
+
)
|
329 |
+
gr.Markdown("[@itsyoboieltr](https://github.com/itsyoboieltr)")
|
330 |
+
|
331 |
+
demo.launch()
|
deploy.yml
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Deploy to Hugging Face Hub
|
2 |
+
|
3 |
+
on:
|
4 |
+
push:
|
5 |
+
branches:
|
6 |
+
- main
|
7 |
+
paths:
|
8 |
+
- app.py
|
9 |
+
workflow_dispatch:
|
10 |
+
|
11 |
+
jobs:
|
12 |
+
deploy:
|
13 |
+
runs-on: ubuntu-latest
|
14 |
+
steps:
|
15 |
+
- name: Checkout 🛎️
|
16 |
+
uses: actions/checkout@v3
|
17 |
+
with:
|
18 |
+
fetch-depth: 0
|
19 |
+
- name: Push to HF ⬆️
|
20 |
+
env:
|
21 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
22 |
+
HF_USERNAME: itsyoboieltr
|
23 |
+
HF_SPACE: itsyoboieltr/anpr
|
24 |
+
TARGET_BRANCH: main
|
25 |
+
run: |
|
26 |
+
rm -fr .git
|
27 |
+
git config --global init.defaultBranch main
|
28 |
+
git config --global user.email "itsyoboieltr@users.noreply.github.com"
|
29 |
+
git config --global user.name "itsyoboieltr"
|
30 |
+
git init
|
31 |
+
git add . :^main/ANPR.ipynb
|
32 |
+
git commit -m "${{ github.event.head_commit.message }}"
|
33 |
+
git remote add space https://$HF_USERNAME:$HF_TOKEN@huggingface.co/spaces/$HF_SPACE
|
34 |
+
git push --force space $TARGET_BRANCH
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
ultralytics
|
2 |
+
paddlepaddle
|
3 |
+
paddleocr
|
4 |
+
deep-sort-realtime
|
5 |
+
internetarchive
|