Hamza0426 commited on
Commit
834230a
Β·
1 Parent(s): 0b95da6

Deploying object Detection backend

Browse files
Files changed (2) hide show
  1. app.py +281 -0
  2. requirements.txt +7 -0
app.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.responses import FileResponse
4
+ from ultralytics import YOLO
5
+ import os, uuid, cv2
6
+ from datetime import datetime
7
+
8
+ app = FastAPI()
9
+
10
+ # CORS
11
+ app.add_middleware(
12
+ CORSMiddleware,
13
+ allow_origins=["*"],
14
+ allow_methods=["*"],
15
+ allow_headers=["*"],
16
+ )
17
+
18
+ UPLOAD_DIR = "uploads"
19
+ OUTPUT_DIR = "outputs"
20
+ os.makedirs(UPLOAD_DIR, exist_ok=True)
21
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
22
+
23
+ # Load YOLO once at startup β€” not per request
24
+ model = YOLO("yolov8n.pt")
25
+
26
+ @app.post("/process-video")
27
+ async def process_video(video: UploadFile = File(...)):
28
+
29
+ # ── Save uploaded video ──────────────────────────────────
30
+ input_path = os.path.join(UPLOAD_DIR, f"{uuid.uuid4()}_{video.filename}")
31
+ with open(input_path, "wb") as f:
32
+ f.write(await video.read())
33
+
34
+ # ── Output path ──────────────────────────────────────────
35
+ output_filename = f"processed_{uuid.uuid4()}.mp4"
36
+ output_path = os.path.join(OUTPUT_DIR, output_filename)
37
+
38
+ # ── Get video properties first ───────────────────────────
39
+ cap = cv2.VideoCapture(input_path)
40
+ fps = int(cap.get(cv2.CAP_PROP_FPS)) or 25
41
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
42
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
43
+ cap.release()
44
+
45
+ # ── VideoWriter with mp4v (fast software codec) ──────────
46
+ # mp4v is significantly faster than H264 on CPU β€” ideal for servers
47
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
48
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
49
+
50
+ if not out.isOpened():
51
+ # Fallback to MJPG if mp4v unavailable
52
+ fourcc = cv2.VideoWriter_fourcc(*"MJPG")
53
+ output_path = output_path.replace(".mp4", ".avi")
54
+ output_filename = output_filename.replace(".mp4", ".avi")
55
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
56
+
57
+ # ── Single-pass: track + write frames together ───────────
58
+ # Key optimizations:
59
+ # - imgsz=416 : smaller inference size (faster, still accurate)
60
+ # - conf=0.45 : skip low-confidence detections
61
+ # - device='cpu': explicit CPU (avoids device detection overhead)
62
+ # - verbose=False: suppress per-frame console output
63
+ results = model.track(
64
+ source=input_path,
65
+ show=False,
66
+ stream=True,
67
+ imgsz=416,
68
+ conf=0.45,
69
+ device="cpu",
70
+ verbose=False,
71
+ )
72
+
73
+ object_count = {}
74
+ frame_idx = 0
75
+
76
+ for result in results:
77
+ frame_idx += 1
78
+ # Use the already-annotated frame from YOLO result
79
+ # result.plot() draws boxes + labels in one optimized call
80
+ # Much faster than manually drawing with cv2.rectangle + putText
81
+ frame = result.plot()
82
+
83
+ # Count unique tracked objects
84
+ if result.boxes.id is not None:
85
+ names = result.names
86
+ for cls_id, track_id in zip(
87
+ result.boxes.cls.cpu().numpy(),
88
+ result.boxes.id.cpu().numpy(),
89
+ ):
90
+ label = names[int(cls_id)]
91
+ if label not in object_count:
92
+ object_count[label] = set()
93
+ object_count[label].add(int(track_id))
94
+
95
+ out.write(frame)
96
+
97
+ out.release()
98
+
99
+ # ── Cleanup uploaded input to save disk space ────────────
100
+ try:
101
+ os.remove(input_path)
102
+ except Exception:
103
+ pass
104
+
105
+ # ── Build response ───────────────────────────────────────
106
+ object_count = {k: len(v) for k, v in object_count.items()}
107
+ total_count = sum(object_count.values())
108
+
109
+ return {
110
+ "frame_count": frame_idx,
111
+ "timestamp": datetime.now().isoformat(),
112
+ "video_url": f"/outputs/{output_filename}",
113
+ "download_url": f"/download/{output_filename}",
114
+ "filename": output_filename,
115
+ "total": total_count,
116
+ **object_count,
117
+ }
118
+
119
+
120
+ @app.get("/outputs/{filename}")
121
+ def serve_video(filename: str):
122
+ file_path = os.path.join(OUTPUT_DIR, filename)
123
+ if not os.path.exists(file_path) or os.path.getsize(file_path) == 0:
124
+ return {"error": "Video file not found or empty"}, 404
125
+ return FileResponse(
126
+ file_path,
127
+ media_type="video/mp4",
128
+ headers={
129
+ "Access-Control-Allow-Origin": "*",
130
+ "Cache-Control": "no-cache, no-store, must-revalidate",
131
+ },
132
+ )
133
+
134
+
135
+ @app.get("/download/{filename}")
136
+ def download_video(filename: str):
137
+ return FileResponse(
138
+ os.path.join(OUTPUT_DIR, filename),
139
+ media_type="video/mp4",
140
+ filename=filename,
141
+ )
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+ # from fastapi import FastAPI, UploadFile, File
150
+ # from fastapi.middleware.cors import CORSMiddleware
151
+ # from fastapi.responses import FileResponse
152
+ # from ultralytics import YOLO
153
+ # import os, uuid, cv2
154
+ # from datetime import datetime
155
+
156
+ # app = FastAPI()
157
+
158
+ # # CORS
159
+ # app.add_middleware(
160
+ # CORSMiddleware,
161
+ # allow_origins=["*"],
162
+ # allow_methods=["*"],
163
+ # allow_headers=["*"],
164
+ # )
165
+
166
+ # UPLOAD_DIR = "uploads"
167
+ # OUTPUT_DIR = "outputs"
168
+ # os.makedirs(UPLOAD_DIR, exist_ok=True)
169
+ # os.makedirs(OUTPUT_DIR, exist_ok=True)
170
+
171
+ # # Load YOLO
172
+ # model = YOLO("yolov8n.pt") # pretrained
173
+
174
+ # @app.post("/process-video")
175
+ # async def process_video(video: UploadFile = File(...)):
176
+
177
+ # # Save uploaded video
178
+ # input_path = os.path.join(UPLOAD_DIR, f"{uuid.uuid4()}_{video.filename}")
179
+ # with open(input_path, "wb") as f:
180
+ # f.write(await video.read())
181
+
182
+ # # Output video path
183
+ # output_filename = f"processed_{uuid.uuid4()}.mp4"
184
+ # output_path = os.path.join(OUTPUT_DIR, output_filename)
185
+
186
+ # # ---------------- Process video ----------------
187
+ # results = model.track(
188
+ # source=input_path,
189
+ # show=False,
190
+ # stream=True
191
+ # )
192
+
193
+ # # ---------------- Save video with labels ----------------
194
+ # cap = cv2.VideoCapture(input_path)
195
+
196
+ # # Try H.264 codec (more compatible), fallback to MJPG if needed
197
+ # try:
198
+ # fourcc = cv2.VideoWriter_fourcc(*"H264")
199
+ # except:
200
+ # fourcc = cv2.VideoWriter_fourcc(*"MJPG")
201
+
202
+ # fps = int(cap.get(cv2.CAP_PROP_FPS))
203
+ # width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
204
+ # height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
205
+ # out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
206
+
207
+ # # Validate video writer
208
+ # if not out.isOpened():
209
+ # print(f"Error: VideoWriter failed to open. Codec may not be available.")
210
+ # cap.release()
211
+ # return {"error": "Video encoding failed. Codec not available."}, 500
212
+
213
+ # object_count = {}
214
+
215
+ # frame_idx = 0
216
+ # for result in results:
217
+ # frame_idx += 1
218
+ # frame = result.orig_img.copy()
219
+
220
+ # if result.boxes.id is not None:
221
+ # names = result.names
222
+ # for cls_id, track_id, box in zip(
223
+ # result.boxes.cls.cpu().numpy(),
224
+ # result.boxes.id.cpu().numpy(),
225
+ # result.boxes.xyxy.cpu().numpy()
226
+ # ):
227
+ # label = names[int(cls_id)]
228
+ # if label not in object_count:
229
+ # object_count[label] = set()
230
+ # object_count[label].add(track_id)
231
+
232
+ # # Draw box
233
+ # x1, y1, x2, y2 = map(int, box)
234
+ # cv2.rectangle(frame, (x1, y1), (x2, y2), (0,255,0), 2)
235
+ # cv2.putText(frame, f"{label} ID:{int(track_id)}", (x1, y1-10),
236
+ # cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2)
237
+
238
+ # out.write(frame)
239
+
240
+ # cap.release()
241
+ # out.release()
242
+
243
+ # # Convert sets to counts
244
+ # object_count = {k: len(v) for k,v in object_count.items()}
245
+ # total_count = sum(object_count.values())
246
+ # return {
247
+ # "frame_count": frame_idx,
248
+ # "timestamp": datetime.now().isoformat(),
249
+ # "video_url": f"/outputs/{output_filename}",
250
+ # "download_url": f"/download/{output_filename}",
251
+ # "filename": output_filename,
252
+ # "total": total_count,
253
+ # **object_count
254
+ # }
255
+
256
+ # @app.get("/outputs/{filename}")
257
+ # def serve_video(filename: str):
258
+ # file_path = os.path.join(OUTPUT_DIR, filename)
259
+
260
+ # # Validate file exists and has content
261
+ # if not os.path.exists(file_path) or os.path.getsize(file_path) == 0:
262
+ # return {"error": "Video file not found or empty"}, 404
263
+
264
+ # return FileResponse(
265
+ # file_path,
266
+ # media_type="video/mp4",
267
+ # headers={
268
+ # "Access-Control-Allow-Origin": "*",
269
+ # "Access-Control-Allow-Methods": "GET, OPTIONS",
270
+ # "Access-Control-Allow-Headers": "Content-Type",
271
+ # "Cache-Control": "no-cache, no-store, must-revalidate"
272
+ # }
273
+ # )
274
+
275
+ # @app.get("/download/{filename}")
276
+ # def download_video(filename: str):
277
+ # return FileResponse(
278
+ # os.path.join(OUTPUT_DIR, filename),
279
+ # media_type="video/mp4",
280
+ # filename=filename
281
+ # )
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-multipart
4
+ ultralytics==8.1.0
5
+ opencv-python-headless
6
+ numpy==1.26.4
7
+ torch>=2.0.0