Spaces:
Build error
Build error
Tun Ye Minn commited on
Commit ·
aea2878
1
Parent(s): d786717
Clean deploy to Hugging Face Space
Browse files- Anomaly/resnet50_final.pth +3 -0
- anomaly_gradio.py +56 -0
- app.py +28 -0
- pages/anomaly_detction.py +104 -0
- requirements.txt +7 -0
- segmentation_gradio.py +65 -0
- unet_mobilenet_final_50.pt +3 -0
- unet_resnet34_final_50.pt +3 -0
Anomaly/resnet50_final.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1607839eb06288892d0201d3a2eefb6db8b1d27eba5d3a0d733ca7ed0e4b62f3
|
| 3 |
+
size 330551545
|
anomaly_gradio.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import cv2
|
| 3 |
+
import os
|
| 4 |
+
import tempfile
|
| 5 |
+
from torchvision.models.detection import fasterrcnn_resnet50_fpn, FastRCNNPredictor
|
| 6 |
+
from torchvision.transforms import functional as F
|
| 7 |
+
|
| 8 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 9 |
+
num_classes = 14
|
| 10 |
+
|
| 11 |
+
def load_anomaly_model():
|
| 12 |
+
model = fasterrcnn_resnet50_fpn(pretrained=False)
|
| 13 |
+
in_features = model.roi_heads.box_predictor.cls_score.in_features
|
| 14 |
+
model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
|
| 15 |
+
checkpoint = torch.load("Anomaly/resnet50_final.pth", map_location=device)
|
| 16 |
+
model.load_state_dict(checkpoint["model_state_dict"])
|
| 17 |
+
model.to(device).eval()
|
| 18 |
+
return model
|
| 19 |
+
|
| 20 |
+
model = load_anomaly_model()
|
| 21 |
+
|
| 22 |
+
def draw_boxes(frame, outputs, threshold=0.5):
|
| 23 |
+
for box, label, score in zip(outputs['boxes'], outputs['labels'], outputs['scores']):
|
| 24 |
+
if score >= threshold:
|
| 25 |
+
x1, y1, x2, y2 = map(int, box.tolist())
|
| 26 |
+
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
| 27 |
+
cv2.putText(frame, f'{label.item()}:{score:.2f}', (x1, y1 - 10),
|
| 28 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
|
| 29 |
+
return frame
|
| 30 |
+
|
| 31 |
+
def detect_anomalies(video_file):
|
| 32 |
+
input_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
|
| 33 |
+
input_path.write(video_file.read())
|
| 34 |
+
input_path.close()
|
| 35 |
+
|
| 36 |
+
cap = cv2.VideoCapture(input_path.name)
|
| 37 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
| 38 |
+
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 39 |
+
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 40 |
+
out_path = os.path.join(tempfile.gettempdir(), "anomaly_output.mp4")
|
| 41 |
+
out = cv2.VideoWriter(out_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))
|
| 42 |
+
|
| 43 |
+
while cap.isOpened():
|
| 44 |
+
ret, frame = cap.read()
|
| 45 |
+
if not ret:
|
| 46 |
+
break
|
| 47 |
+
img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 48 |
+
tensor = F.to_tensor(img).to(device)
|
| 49 |
+
with torch.no_grad():
|
| 50 |
+
pred = model([tensor])[0]
|
| 51 |
+
result = draw_boxes(frame, pred)
|
| 52 |
+
out.write(result)
|
| 53 |
+
|
| 54 |
+
cap.release()
|
| 55 |
+
out.release()
|
| 56 |
+
return out_path
|
app.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
import gradio as gr
|
| 3 |
+
from anomaly_gradio import detect_anomalies
|
| 4 |
+
from segmentation_gradio import segment_video
|
| 5 |
+
|
| 6 |
+
anomaly_app = gr.Interface(
|
| 7 |
+
fn=detect_anomalies,
|
| 8 |
+
inputs=gr.Video(label="Upload video for anomaly detection"),
|
| 9 |
+
outputs=gr.Video(label="Processed anomaly video"),
|
| 10 |
+
title="Anomaly Detection with Faster R-CNN"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
segmentation_app = gr.Interface(
|
| 14 |
+
fn=segment_video,
|
| 15 |
+
inputs=[
|
| 16 |
+
gr.Video(label="Upload video for segmentation"),
|
| 17 |
+
gr.Radio(["MobileNet", "ResNet34"], label="Choose segmentation model")
|
| 18 |
+
],
|
| 19 |
+
outputs=gr.Video(label="Segmented output video"),
|
| 20 |
+
title="Semantic Segmentation with UNet"
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
demo = gr.TabbedInterface(
|
| 24 |
+
[anomaly_app, segmentation_app],
|
| 25 |
+
["Anomaly Detection", "Segmentation"]
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
demo.launch()
|
pages/anomaly_detction.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import cv2
|
| 3 |
+
import torch
|
| 4 |
+
import os
|
| 5 |
+
import tempfile
|
| 6 |
+
import numpy as np
|
| 7 |
+
from torchvision.models.detection import fasterrcnn_resnet50_fpn, FastRCNNPredictor
|
| 8 |
+
from torchvision.transforms import functional as F
|
| 9 |
+
|
| 10 |
+
# ─────────────────────────────────────────────────────────────
|
| 11 |
+
# Streamlit UI Setup
|
| 12 |
+
st.set_page_config(page_title="Anomaly Detection", layout="centered")
|
| 13 |
+
st.title("📹 Anomaly Detection with Faster R-CNN")
|
| 14 |
+
|
| 15 |
+
# ─────────────────────────────────────────────────────────────
|
| 16 |
+
# Model Setup
|
| 17 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 18 |
+
num_classes = 14 # 13 classes + background
|
| 19 |
+
|
| 20 |
+
@st.cache_resource
|
| 21 |
+
def load_model():
|
| 22 |
+
model = fasterrcnn_resnet50_fpn(pretrained=False)
|
| 23 |
+
in_features = model.roi_heads.box_predictor.cls_score.in_features
|
| 24 |
+
model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
|
| 25 |
+
|
| 26 |
+
checkpoint = torch.load("Anomaly/resnet50_final.pth", map_location=device)
|
| 27 |
+
model.load_state_dict(checkpoint["model_state_dict"])
|
| 28 |
+
model.to(device)
|
| 29 |
+
model.eval()
|
| 30 |
+
return model
|
| 31 |
+
|
| 32 |
+
model = load_model()
|
| 33 |
+
|
| 34 |
+
# ─────────────────────────────────────────────────────────────
|
| 35 |
+
# Utility
|
| 36 |
+
def draw_boxes(frame, outputs, threshold=0.5):
|
| 37 |
+
for box, label, score in zip(outputs['boxes'], outputs['labels'], outputs['scores']):
|
| 38 |
+
if score >= threshold:
|
| 39 |
+
x1, y1, x2, y2 = map(int, box.tolist())
|
| 40 |
+
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
| 41 |
+
cv2.putText(frame, f'{label.item()}:{score:.2f}', (x1, y1 - 10),
|
| 42 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
|
| 43 |
+
return frame
|
| 44 |
+
|
| 45 |
+
# ─────────────────────────────────────────────────────────────
|
| 46 |
+
# Interface
|
| 47 |
+
video_file = st.file_uploader("Upload a video for anomaly detection", type=["mp4", "mov", "avi"])
|
| 48 |
+
|
| 49 |
+
if video_file:
|
| 50 |
+
st.video(video_file)
|
| 51 |
+
start_button = st.button("▶ Start Anomaly Detection")
|
| 52 |
+
|
| 53 |
+
if start_button:
|
| 54 |
+
stframe = st.empty()
|
| 55 |
+
progress = st.progress(0)
|
| 56 |
+
|
| 57 |
+
temp_input = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
|
| 58 |
+
temp_input.write(video_file.read())
|
| 59 |
+
temp_input.close()
|
| 60 |
+
|
| 61 |
+
cap = cv2.VideoCapture(temp_input.name)
|
| 62 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
| 63 |
+
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 64 |
+
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 65 |
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 66 |
+
|
| 67 |
+
output_path = os.path.join(tempfile.gettempdir(), "anomaly_output.mp4")
|
| 68 |
+
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
| 69 |
+
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
|
| 70 |
+
|
| 71 |
+
frame_count = 0
|
| 72 |
+
|
| 73 |
+
while cap.isOpened():
|
| 74 |
+
ret, frame = cap.read()
|
| 75 |
+
if not ret:
|
| 76 |
+
break
|
| 77 |
+
|
| 78 |
+
image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 79 |
+
tensor = F.to_tensor(image).to(device)
|
| 80 |
+
|
| 81 |
+
with torch.no_grad():
|
| 82 |
+
prediction = model([tensor])[0]
|
| 83 |
+
|
| 84 |
+
annotated = draw_boxes(frame.copy(), prediction)
|
| 85 |
+
out.write(annotated)
|
| 86 |
+
|
| 87 |
+
resized = cv2.resize(annotated, (960, 540))
|
| 88 |
+
stframe.image(cv2.cvtColor(resized, cv2.COLOR_BGR2RGB), channels="RGB")
|
| 89 |
+
|
| 90 |
+
frame_count += 1
|
| 91 |
+
progress.progress(min(frame_count / total_frames, 1.0))
|
| 92 |
+
|
| 93 |
+
cap.release()
|
| 94 |
+
out.release()
|
| 95 |
+
|
| 96 |
+
st.success("✅ Anomaly detection complete!")
|
| 97 |
+
|
| 98 |
+
with open(output_path, "rb") as f:
|
| 99 |
+
st.download_button(
|
| 100 |
+
label="📥 Download Annotated Video",
|
| 101 |
+
data=f,
|
| 102 |
+
file_name="anomaly_output.mp4",
|
| 103 |
+
mime="video/mp4"
|
| 104 |
+
)
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
torch==2.7.1
|
| 3 |
+
torchvision==0.22.1
|
| 4 |
+
segmentation-models-pytorch==0.3.4
|
| 5 |
+
opencv-python-headless==4.8.0.76
|
| 6 |
+
numpy==1.26.4
|
| 7 |
+
Pillow
|
segmentation_gradio.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import cv2
|
| 3 |
+
import os
|
| 4 |
+
import tempfile
|
| 5 |
+
import numpy as np
|
| 6 |
+
from PIL import Image
|
| 7 |
+
from torchvision import transforms as T
|
| 8 |
+
import segmentation_models_pytorch as smp
|
| 9 |
+
|
| 10 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 11 |
+
resize_w, resize_h = 640, 384
|
| 12 |
+
mean = [0.485, 0.456, 0.406]
|
| 13 |
+
std = [0.229, 0.224, 0.225]
|
| 14 |
+
transform = T.Compose([
|
| 15 |
+
T.ToTensor(),
|
| 16 |
+
T.Normalize(mean, std)
|
| 17 |
+
])
|
| 18 |
+
color_map = np.random.RandomState(42).randint(0, 255, size=(23, 3), dtype=np.uint8)
|
| 19 |
+
|
| 20 |
+
def apply_mask(image, mask):
|
| 21 |
+
mask_color = color_map[mask]
|
| 22 |
+
return cv2.addWeighted(image, 0.6, mask_color, 0.4, 0)
|
| 23 |
+
|
| 24 |
+
def load_segmentation_model(name):
|
| 25 |
+
model = smp.Unet(
|
| 26 |
+
encoder_name="mobilenet_v2" if name == "MobileNet" else "resnet34",
|
| 27 |
+
encoder_weights=None,
|
| 28 |
+
in_channels=3,
|
| 29 |
+
classes=23
|
| 30 |
+
)
|
| 31 |
+
path = "unet_mobilenet_final_50.pt" if name == "MobileNet" else "unet_resnet34_final_50.pt"
|
| 32 |
+
model.load_state_dict(torch.load(path, map_location=device))
|
| 33 |
+
model.to(device).eval()
|
| 34 |
+
return model
|
| 35 |
+
|
| 36 |
+
def segment_video(video_file, model_name):
|
| 37 |
+
model = load_segmentation_model(model_name)
|
| 38 |
+
input_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
|
| 39 |
+
input_path.write(video_file.read())
|
| 40 |
+
input_path.close()
|
| 41 |
+
|
| 42 |
+
cap = cv2.VideoCapture(input_path.name)
|
| 43 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
| 44 |
+
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 45 |
+
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 46 |
+
out_path = os.path.join(tempfile.gettempdir(), "segmentation_output.mp4")
|
| 47 |
+
out = cv2.VideoWriter(out_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))
|
| 48 |
+
|
| 49 |
+
while cap.isOpened():
|
| 50 |
+
ret, frame = cap.read()
|
| 51 |
+
if not ret:
|
| 52 |
+
break
|
| 53 |
+
resized = cv2.resize(frame, (resize_w, resize_h))
|
| 54 |
+
rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB)
|
| 55 |
+
pil = Image.fromarray(rgb)
|
| 56 |
+
tensor = transform(pil).unsqueeze(0).to(device)
|
| 57 |
+
with torch.no_grad():
|
| 58 |
+
mask = torch.argmax(model(tensor), dim=1).squeeze().cpu().numpy()
|
| 59 |
+
resized_mask = cv2.resize(mask.astype(np.uint8), (width, height), interpolation=cv2.INTER_NEAREST)
|
| 60 |
+
overlay = apply_mask(frame, resized_mask)
|
| 61 |
+
out.write(overlay)
|
| 62 |
+
|
| 63 |
+
cap.release()
|
| 64 |
+
out.release()
|
| 65 |
+
return out_path
|
unet_mobilenet_final_50.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c977bc416a64415fd756eafecf9d07499350f64c35ef3106cc90f87899d30e25
|
| 3 |
+
size 26813798
|
unet_resnet34_final_50.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0e3a9ee76eeb1bff57338dace892d0ab9f2f90f5829ddb6d8c7bff836248c2c7
|
| 3 |
+
size 97936012
|