Update app.py
Browse files
app.py
CHANGED
|
@@ -8,10 +8,11 @@ import cv2
|
|
| 8 |
import os
|
| 9 |
import torch.serialization
|
| 10 |
from ultralytics.nn.tasks import DetectionModel
|
|
|
|
| 11 |
import numpy as np
|
| 12 |
|
| 13 |
-
# Allowlist
|
| 14 |
-
torch.serialization.add_safe_globals([DetectionModel])
|
| 15 |
|
| 16 |
# Load the pre-trained YOLOv8n model
|
| 17 |
model = YOLO('model/yolov8n.pt')
|
|
@@ -21,22 +22,19 @@ model.overrides['agnostic_nms'] = False
|
|
| 21 |
model.overrides['max_det'] = 1000
|
| 22 |
|
| 23 |
def detect_empty_spots(img_width, img_height, bottle_bboxes, min_gap=50):
|
| 24 |
-
"""Simple logic to infer empty spots based on gaps between bottles"""
|
| 25 |
empty_bboxes = []
|
| 26 |
-
# Assume shelf spans full image width; split into horizontal segments
|
| 27 |
x_coords = sorted([bbox[0] for bbox in bottle_bboxes] + [bbox[2] for bbox in bottle_bboxes])
|
| 28 |
x_coords = [0] + x_coords + [img_width]
|
| 29 |
|
| 30 |
for i in range(len(x_coords) - 1):
|
| 31 |
gap_start = x_coords[i]
|
| 32 |
gap_end = x_coords[i + 1]
|
| 33 |
-
if gap_end - gap_start > min_gap:
|
| 34 |
-
empty_bboxes.append([gap_start, 0, gap_end, img_height])
|
| 35 |
|
| 36 |
return empty_bboxes
|
| 37 |
|
| 38 |
def process_input(input_file):
|
| 39 |
-
# Handle video: Extract middle frame
|
| 40 |
if input_file.lower().endswith(('.mp4', '.avi', '.mov')):
|
| 41 |
cap = cv2.VideoCapture(input_file)
|
| 42 |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
@@ -51,12 +49,10 @@ def process_input(input_file):
|
|
| 51 |
else:
|
| 52 |
process_img_path = input_file
|
| 53 |
|
| 54 |
-
# Run model inference
|
| 55 |
results = model(process_img_path)
|
| 56 |
boxes = results[0].boxes
|
| 57 |
class_names = results[0].names
|
| 58 |
|
| 59 |
-
# Count bottles and collect their bounding boxes
|
| 60 |
num_present = 0
|
| 61 |
bottle_bboxes = []
|
| 62 |
for box in boxes:
|
|
@@ -64,24 +60,20 @@ def process_input(input_file):
|
|
| 64 |
cls_name = class_names[cls_id]
|
| 65 |
if cls_name == 'bottle':
|
| 66 |
num_present += 1
|
| 67 |
-
bottle_bboxes.append(box.xyxy[0].cpu().numpy())
|
| 68 |
|
| 69 |
-
# Load image for annotation
|
| 70 |
img = Image.open(process_img_path)
|
| 71 |
img_width, img_height = img.size
|
| 72 |
draw = ImageDraw.Draw(img)
|
| 73 |
|
| 74 |
-
# Draw bottle bounding boxes
|
| 75 |
for bbox in bottle_bboxes:
|
| 76 |
draw.rectangle(((bbox[0], bbox[1]), (bbox[2], bbox[3])), outline="green", width=3)
|
| 77 |
|
| 78 |
-
# Infer empty spots
|
| 79 |
empty_bboxes = detect_empty_spots(img_width, img_height, bottle_bboxes)
|
| 80 |
num_empty = len(empty_bboxes)
|
| 81 |
for bbox in empty_bboxes:
|
| 82 |
draw.rectangle(((bbox[0], bbox[1]), (bbox[2], bbox[3])), outline="red", width=3)
|
| 83 |
|
| 84 |
-
# Generate PDF
|
| 85 |
pdf = FPDF()
|
| 86 |
pdf.add_page()
|
| 87 |
pdf.set_font("Arial", size=12)
|
|
@@ -91,23 +83,20 @@ def process_input(input_file):
|
|
| 91 |
pdf.cell(200, 10, txt=f"Number of empty spots (inferred): {num_empty}", ln=1)
|
| 92 |
pdf.ln(10)
|
| 93 |
|
| 94 |
-
# Add annotated full image
|
| 95 |
with tempfile.NamedTemporaryFile(suffix='.png') as tmp_annotated:
|
| 96 |
img.save(tmp_annotated.name)
|
| 97 |
-
pdf.image(tmp_annotated.name, x=10, y=pdf.get_y(), w=180)
|
| 98 |
|
| 99 |
-
# Add screenshots of bottles and empty spots
|
| 100 |
pdf.add_page()
|
| 101 |
pdf.cell(200, 10, txt="Detected Bottles and Empty Spots", ln=1, align='C')
|
| 102 |
pdf.ln(5)
|
| 103 |
y_pos = pdf.get_y()
|
| 104 |
|
| 105 |
-
# Bottles
|
| 106 |
for i, bbox in enumerate(bottle_bboxes):
|
| 107 |
cropped_img = img.crop((bbox[0], bbox[1], bbox[2], bbox[3]))
|
| 108 |
with tempfile.NamedTemporaryFile(suffix='.png') as tmp_crop:
|
| 109 |
cropped_img.save(tmp_crop.name)
|
| 110 |
-
pdf.image(tmp_crop.name, x=10, y=y_pos, w=90)
|
| 111 |
y_pos += 100
|
| 112 |
pdf.cell(200, 10, txt=f"Bottle {i+1} (Location: x1={int(bbox[0])}, y1={int(bbox[1])}, x2={int(bbox[2])}, y2={int(bbox[3])})", ln=1)
|
| 113 |
pdf.ln(5)
|
|
@@ -115,7 +104,6 @@ def process_input(input_file):
|
|
| 115 |
pdf.add_page()
|
| 116 |
y_pos = 10
|
| 117 |
|
| 118 |
-
# Empty spots
|
| 119 |
for i, bbox in enumerate(empty_bboxes):
|
| 120 |
cropped_img = img.crop((bbox[0], bbox[1], bbox[2], bbox[3]))
|
| 121 |
with tempfile.NamedTemporaryFile(suffix='.png') as tmp_crop:
|
|
@@ -128,18 +116,15 @@ def process_input(input_file):
|
|
| 128 |
pdf.add_page()
|
| 129 |
y_pos = 10
|
| 130 |
|
| 131 |
-
# Output PDF
|
| 132 |
pdf_bytes = io.BytesIO()
|
| 133 |
pdf.output(pdf_bytes)
|
| 134 |
pdf_bytes.seek(0)
|
| 135 |
|
| 136 |
-
# Clean up temp files
|
| 137 |
if 'temp_frame_path' in locals():
|
| 138 |
os.remove(temp_frame_path)
|
| 139 |
|
| 140 |
return pdf_bytes.getvalue()
|
| 141 |
|
| 142 |
-
# Gradio interface
|
| 143 |
with gr.Blocks() as demo:
|
| 144 |
gr.Markdown("# Wine Shop CCTV Analyzer")
|
| 145 |
gr.Markdown("Upload a CCTV image or video to analyze stock and generate a PDF report.")
|
|
|
|
| 8 |
import os
|
| 9 |
import torch.serialization
|
| 10 |
from ultralytics.nn.tasks import DetectionModel
|
| 11 |
+
from torch.nn.modules.container import Sequential
|
| 12 |
import numpy as np
|
| 13 |
|
| 14 |
+
# Allowlist required classes to fix UnpicklingError
|
| 15 |
+
torch.serialization.add_safe_globals([DetectionModel, Sequential])
|
| 16 |
|
| 17 |
# Load the pre-trained YOLOv8n model
|
| 18 |
model = YOLO('model/yolov8n.pt')
|
|
|
|
| 22 |
model.overrides['max_det'] = 1000
|
| 23 |
|
| 24 |
def detect_empty_spots(img_width, img_height, bottle_bboxes, min_gap=50):
|
|
|
|
| 25 |
empty_bboxes = []
|
|
|
|
| 26 |
x_coords = sorted([bbox[0] for bbox in bottle_bboxes] + [bbox[2] for bbox in bottle_bboxes])
|
| 27 |
x_coords = [0] + x_coords + [img_width]
|
| 28 |
|
| 29 |
for i in range(len(x_coords) - 1):
|
| 30 |
gap_start = x_coords[i]
|
| 31 |
gap_end = x_coords[i + 1]
|
| 32 |
+
if gap_end - gap_start > min_gap:
|
| 33 |
+
empty_bboxes.append([gap_start, 0, gap_end, img_height])
|
| 34 |
|
| 35 |
return empty_bboxes
|
| 36 |
|
| 37 |
def process_input(input_file):
|
|
|
|
| 38 |
if input_file.lower().endswith(('.mp4', '.avi', '.mov')):
|
| 39 |
cap = cv2.VideoCapture(input_file)
|
| 40 |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
|
|
| 49 |
else:
|
| 50 |
process_img_path = input_file
|
| 51 |
|
|
|
|
| 52 |
results = model(process_img_path)
|
| 53 |
boxes = results[0].boxes
|
| 54 |
class_names = results[0].names
|
| 55 |
|
|
|
|
| 56 |
num_present = 0
|
| 57 |
bottle_bboxes = []
|
| 58 |
for box in boxes:
|
|
|
|
| 60 |
cls_name = class_names[cls_id]
|
| 61 |
if cls_name == 'bottle':
|
| 62 |
num_present += 1
|
| 63 |
+
bottle_bboxes.append(box.xyxy[0].cpu().numpy())
|
| 64 |
|
|
|
|
| 65 |
img = Image.open(process_img_path)
|
| 66 |
img_width, img_height = img.size
|
| 67 |
draw = ImageDraw.Draw(img)
|
| 68 |
|
|
|
|
| 69 |
for bbox in bottle_bboxes:
|
| 70 |
draw.rectangle(((bbox[0], bbox[1]), (bbox[2], bbox[3])), outline="green", width=3)
|
| 71 |
|
|
|
|
| 72 |
empty_bboxes = detect_empty_spots(img_width, img_height, bottle_bboxes)
|
| 73 |
num_empty = len(empty_bboxes)
|
| 74 |
for bbox in empty_bboxes:
|
| 75 |
draw.rectangle(((bbox[0], bbox[1]), (bbox[2], bbox[3])), outline="red", width=3)
|
| 76 |
|
|
|
|
| 77 |
pdf = FPDF()
|
| 78 |
pdf.add_page()
|
| 79 |
pdf.set_font("Arial", size=12)
|
|
|
|
| 83 |
pdf.cell(200, 10, txt=f"Number of empty spots (inferred): {num_empty}", ln=1)
|
| 84 |
pdf.ln(10)
|
| 85 |
|
|
|
|
| 86 |
with tempfile.NamedTemporaryFile(suffix='.png') as tmp_annotated:
|
| 87 |
img.save(tmp_annotated.name)
|
| 88 |
+
pdf.image(tmp_annotated.name, x=10, y=pdf.get_y(), w=180)
|
| 89 |
|
|
|
|
| 90 |
pdf.add_page()
|
| 91 |
pdf.cell(200, 10, txt="Detected Bottles and Empty Spots", ln=1, align='C')
|
| 92 |
pdf.ln(5)
|
| 93 |
y_pos = pdf.get_y()
|
| 94 |
|
|
|
|
| 95 |
for i, bbox in enumerate(bottle_bboxes):
|
| 96 |
cropped_img = img.crop((bbox[0], bbox[1], bbox[2], bbox[3]))
|
| 97 |
with tempfile.NamedTemporaryFile(suffix='.png') as tmp_crop:
|
| 98 |
cropped_img.save(tmp_crop.name)
|
| 99 |
+
pdf.image(tmp_crop.name, x=10, y=y_pos, w=90)
|
| 100 |
y_pos += 100
|
| 101 |
pdf.cell(200, 10, txt=f"Bottle {i+1} (Location: x1={int(bbox[0])}, y1={int(bbox[1])}, x2={int(bbox[2])}, y2={int(bbox[3])})", ln=1)
|
| 102 |
pdf.ln(5)
|
|
|
|
| 104 |
pdf.add_page()
|
| 105 |
y_pos = 10
|
| 106 |
|
|
|
|
| 107 |
for i, bbox in enumerate(empty_bboxes):
|
| 108 |
cropped_img = img.crop((bbox[0], bbox[1], bbox[2], bbox[3]))
|
| 109 |
with tempfile.NamedTemporaryFile(suffix='.png') as tmp_crop:
|
|
|
|
| 116 |
pdf.add_page()
|
| 117 |
y_pos = 10
|
| 118 |
|
|
|
|
| 119 |
pdf_bytes = io.BytesIO()
|
| 120 |
pdf.output(pdf_bytes)
|
| 121 |
pdf_bytes.seek(0)
|
| 122 |
|
|
|
|
| 123 |
if 'temp_frame_path' in locals():
|
| 124 |
os.remove(temp_frame_path)
|
| 125 |
|
| 126 |
return pdf_bytes.getvalue()
|
| 127 |
|
|
|
|
| 128 |
with gr.Blocks() as demo:
|
| 129 |
gr.Markdown("# Wine Shop CCTV Analyzer")
|
| 130 |
gr.Markdown("Upload a CCTV image or video to analyze stock and generate a PDF report.")
|