Add YOLOv8 model weights with LFS tracking
Browse files- Dockerfile +2 -2
- backend/__init__.py β __init__.py +0 -0
- backend/data_extraction/__init__.py β app.log +0 -0
- app.py +0 -100
- backend/config.py β config.py +1 -0
- {backend/feature_extraction β data_extraction}/__init__.py +0 -0
- {backend/data_extraction β data_extraction}/interaction_analyzer.py +2 -2
- {backend/data_extraction β data_extraction}/person_tracker.py +2 -2
- {backend/preprocessing β feature_extraction}/__init__.py +0 -0
- {backend/feature_extraction β feature_extraction}/extractor.py +6 -6
- main.py +113 -0
- models/xgb_model.pkl +3 -0
- {backend/models β models}/yolov8n-pose.pt +0 -0
- {backend/models β models}/yolov8n.pt +0 -0
- {backend/services β preprocessing}/__init__.py +0 -0
- {backend/preprocessing β preprocessing}/preprocessor.py +0 -0
- requirements.txt +1 -0
- {backend/services/prediction β services}/__init__.py +0 -0
- {backend/services/video_data_extraction β services/prediction}/__init__.py +0 -0
- services/prediction/predictor.py +40 -0
- backend/services/prediction/predictor.py β services/preprocessing/preprocessor.py +41 -43
- {backend/utils β services/video_data_extraction}/__init__.py +0 -0
- {backend/services β services}/video_data_extraction/video_preprocessor.py +22 -31
- utils/__init__.py +0 -0
- {backend/utils β utils}/csv_utils.py +0 -0
- {backend/utils β utils}/gpu.py +0 -0
- {backend/utils β utils}/id_utils.py +0 -0
- {backend/utils β utils}/interaction_utils.py +0 -0
- {backend/utils β utils}/iou_utils.py +0 -0
- {backend/utils β utils}/motion_utils.py +0 -0
- {backend/utils β utils}/visualizer.py +0 -0
Dockerfile
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
# ------------------------------------------------------------
|
| 2 |
# Base image
|
| 3 |
# ------------------------------------------------------------
|
| 4 |
-
FROM python:3.12-slim
|
| 5 |
|
| 6 |
# ------------------------------------------------------------
|
| 7 |
# Environment
|
|
@@ -62,4 +62,4 @@ EXPOSE 8000
|
|
| 62 |
# ------------------------------------------------------------
|
| 63 |
# Start FastAPI with auto-reload (remove --reload for production)
|
| 64 |
# ------------------------------------------------------------
|
| 65 |
-
CMD ["uvicorn", "
|
|
|
|
| 1 |
# ------------------------------------------------------------
|
| 2 |
# Base image
|
| 3 |
# ------------------------------------------------------------
|
| 4 |
+
FROM python:3.12-slim
|
| 5 |
|
| 6 |
# ------------------------------------------------------------
|
| 7 |
# Environment
|
|
|
|
| 62 |
# ------------------------------------------------------------
|
| 63 |
# Start FastAPI with auto-reload (remove --reload for production)
|
| 64 |
# ------------------------------------------------------------
|
| 65 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
backend/__init__.py β __init__.py
RENAMED
|
File without changes
|
backend/data_extraction/__init__.py β app.log
RENAMED
|
File without changes
|
app.py
DELETED
|
@@ -1,100 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import uuid
|
| 3 |
-
import tempfile
|
| 4 |
-
import pandas as pd
|
| 5 |
-
from fastapi import FastAPI, UploadFile, File, HTTPException
|
| 6 |
-
from fastapi.responses import FileResponse
|
| 7 |
-
import uvicorn
|
| 8 |
-
|
| 9 |
-
from backend.services.video_data_extraction.video_preprocessor import VideoDataExtractor
|
| 10 |
-
from backend.services.prediction.predictor import ViolencePredictor
|
| 11 |
-
|
| 12 |
-
app = FastAPI(title="Video Analysis Backend")
|
| 13 |
-
|
| 14 |
-
processor = VideoDataExtractor()
|
| 15 |
-
predictor = ViolencePredictor()
|
| 16 |
-
jobs: dict[str, dict] = {}
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
@app.get("/")
|
| 20 |
-
def greet_json():
|
| 21 |
-
return {"Hello": "World!"}
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
@app.get("/health")
|
| 25 |
-
async def health_check():
|
| 26 |
-
return {"status": "ok", "message": "Service is running"}
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
@app.post("/process-video/")
|
| 30 |
-
async def process_video(file: UploadFile = File(...)):
|
| 31 |
-
try:
|
| 32 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as input_video:
|
| 33 |
-
input_video.write(await file.read())
|
| 34 |
-
input_path = input_video.name
|
| 35 |
-
|
| 36 |
-
output_csv = tempfile.NamedTemporaryFile(delete=False, suffix=".csv").name
|
| 37 |
-
output_video_path = tempfile.NamedTemporaryFile(
|
| 38 |
-
delete=False, suffix=".mp4"
|
| 39 |
-
).name
|
| 40 |
-
|
| 41 |
-
frame_w, frame_h, num_interactions = processor.extract_video_data(
|
| 42 |
-
input_path,
|
| 43 |
-
output_csv,
|
| 44 |
-
output_folder=os.path.dirname(output_video_path),
|
| 45 |
-
save_video=True,
|
| 46 |
-
)
|
| 47 |
-
|
| 48 |
-
job_id = str(uuid.uuid4())
|
| 49 |
-
jobs[job_id] = {"csv": output_csv, "video": output_video_path}
|
| 50 |
-
|
| 51 |
-
return {
|
| 52 |
-
"job_id": job_id,
|
| 53 |
-
"message": f"Processed video with {num_interactions} interactions",
|
| 54 |
-
"frame_width": frame_w,
|
| 55 |
-
"frame_height": frame_h,
|
| 56 |
-
}
|
| 57 |
-
except Exception as e:
|
| 58 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 59 |
-
finally:
|
| 60 |
-
if os.path.exists(input_path):
|
| 61 |
-
os.unlink(input_path)
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
@app.get("/get-results/{job_id}")
|
| 65 |
-
async def get_results(job_id: str):
|
| 66 |
-
if job_id not in jobs:
|
| 67 |
-
raise HTTPException(status_code=404, detail="Job ID not found")
|
| 68 |
-
csv_path = jobs[job_id]["csv"]
|
| 69 |
-
if not os.path.exists(csv_path):
|
| 70 |
-
raise HTTPException(status_code=404, detail="CSV file not found")
|
| 71 |
-
return FileResponse(
|
| 72 |
-
csv_path, media_type="text/csv", filename="violence_analysis_results.csv"
|
| 73 |
-
)
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
@app.get("/sample-results/{job_id}")
|
| 77 |
-
async def get_sample_results(job_id: str):
|
| 78 |
-
if job_id not in jobs:
|
| 79 |
-
raise HTTPException(status_code=404, detail="Job ID not found")
|
| 80 |
-
csv_path = jobs[job_id]["csv"]
|
| 81 |
-
if not os.path.exists(csv_path):
|
| 82 |
-
raise HTTPException(status_code=404, detail="CSV file not found")
|
| 83 |
-
df = pd.read_csv(csv_path)
|
| 84 |
-
return df.head(5).to_dict(orient="records")
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
@app.post("/predict/{job_id}")
|
| 88 |
-
async def predict_violence(job_id: str):
|
| 89 |
-
if job_id not in jobs:
|
| 90 |
-
raise HTTPException(status_code=404, detail="Job ID not found")
|
| 91 |
-
csv_path = jobs[job_id]["csv"]
|
| 92 |
-
if not os.path.exists(csv_path):
|
| 93 |
-
raise HTTPException(status_code=404, detail="CSV file not found")
|
| 94 |
-
df = pd.read_csv(csv_path)
|
| 95 |
-
preds = predictor.predict(df)
|
| 96 |
-
return {"predictions": preds.tolist()}
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
if __name__ == "__main__":
|
| 100 |
-
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/config.py β config.py
RENAMED
|
@@ -3,6 +3,7 @@ import os
|
|
| 3 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 4 |
DETECT_MODEL = os.path.join(BASE_DIR, "models", "yolov8n.pt")
|
| 5 |
POSE_MODEL = os.path.join(BASE_DIR, "models", "yolov8n-pose.pt")
|
|
|
|
| 6 |
|
| 7 |
|
| 8 |
# Thresholds and params
|
|
|
|
| 3 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 4 |
DETECT_MODEL = os.path.join(BASE_DIR, "models", "yolov8n.pt")
|
| 5 |
POSE_MODEL = os.path.join(BASE_DIR, "models", "yolov8n-pose.pt")
|
| 6 |
+
MAIN_MODEL = os.path.join(BASE_DIR, "models", "xgb_model.pkl")
|
| 7 |
|
| 8 |
|
| 9 |
# Thresholds and params
|
{backend/feature_extraction β data_extraction}/__init__.py
RENAMED
|
File without changes
|
{backend/data_extraction β data_extraction}/interaction_analyzer.py
RENAMED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
import numpy as np
|
| 2 |
-
from
|
| 3 |
calc_avg_speed,
|
| 4 |
calc_motion_intensity,
|
| 5 |
calc_sudden_movements,
|
| 6 |
)
|
| 7 |
-
from
|
| 8 |
get_box_center,
|
| 9 |
euclidean_distance,
|
| 10 |
relative_distance,
|
|
|
|
| 1 |
import numpy as np
|
| 2 |
+
from utils.motion_utils import (
|
| 3 |
calc_avg_speed,
|
| 4 |
calc_motion_intensity,
|
| 5 |
calc_sudden_movements,
|
| 6 |
)
|
| 7 |
+
from utils.interaction_utils import (
|
| 8 |
get_box_center,
|
| 9 |
euclidean_distance,
|
| 10 |
relative_distance,
|
{backend/data_extraction β data_extraction}/person_tracker.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import numpy as np
|
| 2 |
-
from
|
| 3 |
-
from
|
| 4 |
|
| 5 |
|
| 6 |
class PersonTracker:
|
|
|
|
| 1 |
import numpy as np
|
| 2 |
+
from utils.iou_utils import calculate_iou
|
| 3 |
+
from utils.id_utils import get_new_id
|
| 4 |
|
| 5 |
|
| 6 |
class PersonTracker:
|
{backend/preprocessing β feature_extraction}/__init__.py
RENAMED
|
File without changes
|
{backend/feature_extraction β feature_extraction}/extractor.py
RENAMED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
import torch
|
| 2 |
-
from
|
| 3 |
-
from
|
| 4 |
-
from
|
| 5 |
-
from
|
| 6 |
-
from
|
| 7 |
-
from
|
| 8 |
import numpy as np
|
| 9 |
from ultralytics import YOLO
|
| 10 |
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from config import DETECT_MODEL, POSE_MODEL, CONF_THRESHOLD
|
| 3 |
+
from utils.gpu import GPUConfigurator
|
| 4 |
+
from preprocessing.preprocessor import FramePreprocessor
|
| 5 |
+
from data_extraction.interaction_analyzer import InteractionAnalyzer
|
| 6 |
+
from data_extraction.person_tracker import PersonTracker
|
| 7 |
+
from utils.visualizer import Visualizer
|
| 8 |
import numpy as np
|
| 9 |
from ultralytics import YOLO
|
| 10 |
|
main.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from services.prediction.predictor import ViolencePredictor
|
| 2 |
+
from services.video_data_extraction.video_preprocessor import VideoDataExtractor
|
| 3 |
+
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
|
| 4 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
+
from fastapi.responses import JSONResponse
|
| 6 |
+
import numpy as np
|
| 7 |
+
import os
|
| 8 |
+
import logging
|
| 9 |
+
import uuid
|
| 10 |
+
|
| 11 |
+
# Initialize logging
|
| 12 |
+
logging.basicConfig(
|
| 13 |
+
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
| 14 |
+
)
|
| 15 |
+
logger = logging.getLogger("main")
|
| 16 |
+
|
| 17 |
+
app = FastAPI(title="Violence Prediction System")
|
| 18 |
+
|
| 19 |
+
# β
Enable CORS
|
| 20 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 21 |
+
|
| 22 |
+
app.add_middleware(
|
| 23 |
+
CORSMiddleware,
|
| 24 |
+
allow_origins=["*"], # or ["http://localhost:5173"]
|
| 25 |
+
allow_credentials=True,
|
| 26 |
+
allow_methods=["*"],
|
| 27 |
+
allow_headers=["*"],
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
UPLOAD_DIR = "temp"
|
| 31 |
+
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
| 32 |
+
# Initialize shared service objects
|
| 33 |
+
try:
|
| 34 |
+
extractor = VideoDataExtractor()
|
| 35 |
+
predictor = ViolencePredictor()
|
| 36 |
+
logger.info("Initialized shared service objects")
|
| 37 |
+
except Exception as e:
|
| 38 |
+
logger.error(f"Failed to create service objects: {str(e)}")
|
| 39 |
+
# Create mock objects for testing
|
| 40 |
+
extractor = None
|
| 41 |
+
predictor = None
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def to_python(obj):
|
| 45 |
+
if isinstance(obj, np.generic):
|
| 46 |
+
return obj.item()
|
| 47 |
+
elif isinstance(obj, np.ndarray):
|
| 48 |
+
return obj.tolist()
|
| 49 |
+
elif isinstance(obj, dict):
|
| 50 |
+
return {k: to_python(v) for k, v in obj.items()}
|
| 51 |
+
elif isinstance(obj, list):
|
| 52 |
+
return [to_python(i) for i in obj]
|
| 53 |
+
return obj
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# Health Check endpoint
|
| 57 |
+
@app.get("/")
|
| 58 |
+
async def health():
|
| 59 |
+
return {"status": "ok", "message": "Violence Detection API is running"}
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# Extract video data
|
| 63 |
+
@app.post("/analyze")
|
| 64 |
+
async def extract_data(mode: str = Form(...), file: UploadFile = File(...)):
|
| 65 |
+
if not extractor or not predictor:
|
| 66 |
+
raise HTTPException(status_code=500, detail="Service not initialized properly")
|
| 67 |
+
|
| 68 |
+
if not file.filename:
|
| 69 |
+
raise HTTPException(status_code=400, detail="No file provided")
|
| 70 |
+
|
| 71 |
+
# Create temp file with proper path
|
| 72 |
+
tmp_file_code = uuid.uuid4()
|
| 73 |
+
temp_path = os.path.join(UPLOAD_DIR, f"{tmp_file_code}_{file.filename}")
|
| 74 |
+
|
| 75 |
+
try:
|
| 76 |
+
# Save uploaded file
|
| 77 |
+
with open(temp_path, "wb") as f:
|
| 78 |
+
content = await file.read()
|
| 79 |
+
f.write(content)
|
| 80 |
+
|
| 81 |
+
logger.info(f"Processing file: {file.filename}, mode: {mode}")
|
| 82 |
+
|
| 83 |
+
# Extract video data
|
| 84 |
+
data = extractor.extract_video_data(temp_path)
|
| 85 |
+
|
| 86 |
+
if mode == "extract":
|
| 87 |
+
result = {"data": data.to_dict(orient="records")}
|
| 88 |
+
else:
|
| 89 |
+
prediction = predictor.predict(data)
|
| 90 |
+
prediction = to_python(prediction)
|
| 91 |
+
result = {"prediction": prediction}
|
| 92 |
+
|
| 93 |
+
return result
|
| 94 |
+
|
| 95 |
+
except Exception as e:
|
| 96 |
+
logger.error(f"Error processing video: {str(e)}")
|
| 97 |
+
raise HTTPException(
|
| 98 |
+
status_code=500, detail=f"Failed to process video: {str(e)}"
|
| 99 |
+
)
|
| 100 |
+
finally:
|
| 101 |
+
# Clean up temp file
|
| 102 |
+
try:
|
| 103 |
+
if os.path.exists(temp_path):
|
| 104 |
+
os.remove(temp_path)
|
| 105 |
+
except Exception as e:
|
| 106 |
+
logger.warning(f"Could not remove temp file: {e}")
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
# Run app
|
| 110 |
+
if __name__ == "__main__":
|
| 111 |
+
import uvicorn
|
| 112 |
+
|
| 113 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
models/xgb_model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:301342df1ecd04297cca83af7172b5fded894ee9259657efc2cfaf030a7ab6d0
|
| 3 |
+
size 465468
|
{backend/models β models}/yolov8n-pose.pt
RENAMED
|
File without changes
|
{backend/models β models}/yolov8n.pt
RENAMED
|
File without changes
|
{backend/services β preprocessing}/__init__.py
RENAMED
|
File without changes
|
{backend/preprocessing β preprocessing}/preprocessor.py
RENAMED
|
File without changes
|
requirements.txt
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
fastapi
|
| 2 |
uvicorn[standard]
|
| 3 |
numpy
|
|
|
|
| 4 |
opencv-python
|
| 5 |
pandas
|
| 6 |
scikit-learn
|
|
|
|
| 1 |
fastapi
|
| 2 |
uvicorn[standard]
|
| 3 |
numpy
|
| 4 |
+
xgboost
|
| 5 |
opencv-python
|
| 6 |
pandas
|
| 7 |
scikit-learn
|
{backend/services/prediction β services}/__init__.py
RENAMED
|
File without changes
|
{backend/services/video_data_extraction β services/prediction}/__init__.py
RENAMED
|
File without changes
|
services/prediction/predictor.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import joblib
|
| 2 |
+
from config import MAIN_MODEL
|
| 3 |
+
import pandas as pd
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class ViolencePredictor:
|
| 7 |
+
def __init__(self):
|
| 8 |
+
self.model = joblib.load(MAIN_MODEL)
|
| 9 |
+
|
| 10 |
+
def _preprocess_data_pdict(self, data: pd.DataFrame) -> pd.DataFrame:
|
| 11 |
+
cols_to_drop = [
|
| 12 |
+
"video_name",
|
| 13 |
+
"frame_index",
|
| 14 |
+
"timestamp",
|
| 15 |
+
"frame_width",
|
| 16 |
+
"frame_height",
|
| 17 |
+
"person1_id",
|
| 18 |
+
"person2_id",
|
| 19 |
+
"person1_idx",
|
| 20 |
+
"person2_idx",
|
| 21 |
+
]
|
| 22 |
+
data = data.drop(columns=cols_to_drop)
|
| 23 |
+
return data
|
| 24 |
+
|
| 25 |
+
def predict(self, data):
|
| 26 |
+
data = self._preprocess_data_pdict(data)
|
| 27 |
+
y_pred = self.model.predict(data)
|
| 28 |
+
print(y_pred)
|
| 29 |
+
|
| 30 |
+
return y_pred
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
if __name__ == "__main__":
|
| 34 |
+
import pandas as pd
|
| 35 |
+
|
| 36 |
+
data = pd.read_csv("data/fight_train.csv")
|
| 37 |
+
data = data[0:20]
|
| 38 |
+
print("dataloaded")
|
| 39 |
+
VP = ViolencePredictor()
|
| 40 |
+
VP.predict(data)
|
backend/services/prediction/predictor.py β services/preprocessing/preprocessor.py
RENAMED
|
@@ -1,43 +1,44 @@
|
|
| 1 |
import numpy as np
|
|
|
|
| 2 |
from sklearn.preprocessing import MinMaxScaler
|
| 3 |
|
| 4 |
|
| 5 |
-
class
|
| 6 |
def __init__(self):
|
|
|
|
| 7 |
self.scaler = MinMaxScaler()
|
| 8 |
|
| 9 |
-
def preprocess_data(self, df):
|
| 10 |
-
# Normalize coordinates, distances and keypoints
|
| 11 |
-
# Drop confidence columns
|
| 12 |
-
# Scale selected columns
|
| 13 |
-
# Similar as existing code...
|
| 14 |
"""
|
| 15 |
-
Preprocess the data by normalizing box coordinates, center coordinates,
|
|
|
|
| 16 |
"""
|
|
|
|
|
|
|
| 17 |
# Normalize box coordinates
|
| 18 |
frame_height = df["frame_height"]
|
| 19 |
frame_width = df["frame_width"]
|
| 20 |
-
df["box1_x_min"] = df["box1_x_min"] / frame_width
|
| 21 |
-
df["box1_y_min"] = df["box1_y_min"] / frame_height
|
| 22 |
-
df["box1_x_max"] = df["box1_x_max"] / frame_width
|
| 23 |
-
df["box1_y_max"] = df["box1_y_max"] / frame_height
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
| 29 |
|
| 30 |
# Normalize center coordinates
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
| 36 |
|
| 37 |
# Normalize distances
|
| 38 |
max_distance = np.sqrt(frame_width**2 + frame_height**2)
|
| 39 |
-
|
| 40 |
-
|
|
|
|
| 41 |
|
| 42 |
# Drop confidence columns
|
| 43 |
drop_columns = (
|
|
@@ -45,31 +46,28 @@ class ViolencePredictor:
|
|
| 45 |
+ [f"person2_kp{i}_conf" for i in range(17)]
|
| 46 |
+ [f"relative_kp{i}_conf" for i in range(17)]
|
| 47 |
)
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
|
| 52 |
# Normalize keypoints
|
| 53 |
for i in range(17):
|
| 54 |
for prefix in ["person1_kp", "person2_kp", "relative_kp"]:
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
| 57 |
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
-
# Scale specific columns
|
| 64 |
-
df["distance"] = self.scaler.fit_transform(df[["distance"]])
|
| 65 |
-
df["relative_distance"] = self.scaler.fit_transform(df[["relative_distance"]])
|
| 66 |
-
df["motion_average_speed"] = self.scaler.fit_transform(
|
| 67 |
-
df[["motion_average_speed"]]
|
| 68 |
-
)
|
| 69 |
-
df["motion_motion_intensity"] = self.scaler.fit_transform(
|
| 70 |
-
df[["motion_motion_intensity"]]
|
| 71 |
-
)
|
| 72 |
return df
|
| 73 |
-
|
| 74 |
-
def predict(self, data):
|
| 75 |
-
return 0
|
|
|
|
| 1 |
import numpy as np
|
| 2 |
+
import pandas as pd
|
| 3 |
from sklearn.preprocessing import MinMaxScaler
|
| 4 |
|
| 5 |
|
| 6 |
+
class DataPreprocessor:
|
| 7 |
def __init__(self):
|
| 8 |
+
# Initialize scaler (use transform() only during inference)
|
| 9 |
self.scaler = MinMaxScaler()
|
| 10 |
|
| 11 |
+
def preprocess_data(self, df: pd.DataFrame) -> pd.DataFrame:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
"""
|
| 13 |
+
Preprocess the data by normalizing box coordinates, center coordinates,
|
| 14 |
+
distances, and keypoints.
|
| 15 |
"""
|
| 16 |
+
df = df.copy() # prevent modifying original
|
| 17 |
+
|
| 18 |
# Normalize box coordinates
|
| 19 |
frame_height = df["frame_height"]
|
| 20 |
frame_width = df["frame_width"]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
+
for prefix in ["box1", "box2"]:
|
| 23 |
+
for coord in ["x_min", "x_max"]:
|
| 24 |
+
df[f"{prefix}_{coord}"] = df[f"{prefix}_{coord}"] / frame_width
|
| 25 |
+
for coord in ["y_min", "y_max"]:
|
| 26 |
+
df[f"{prefix}_{coord}"] = df[f"{prefix}_{coord}"] / frame_height
|
| 27 |
|
| 28 |
# Normalize center coordinates
|
| 29 |
+
for axis in ["x", "y"]:
|
| 30 |
+
df[f"center1_{axis}"] = df[f"center1_{axis}"] / (
|
| 31 |
+
frame_width if axis == "x" else frame_height
|
| 32 |
+
)
|
| 33 |
+
df[f"center2_{axis}"] = df[f"center2_{axis}"] / (
|
| 34 |
+
frame_width if axis == "x" else frame_height
|
| 35 |
+
)
|
| 36 |
|
| 37 |
# Normalize distances
|
| 38 |
max_distance = np.sqrt(frame_width**2 + frame_height**2)
|
| 39 |
+
for col in ["distance", "relative_distance"]:
|
| 40 |
+
if col in df.columns:
|
| 41 |
+
df[col] = df[col] / max_distance
|
| 42 |
|
| 43 |
# Drop confidence columns
|
| 44 |
drop_columns = (
|
|
|
|
| 46 |
+ [f"person2_kp{i}_conf" for i in range(17)]
|
| 47 |
+ [f"relative_kp{i}_conf" for i in range(17)]
|
| 48 |
)
|
| 49 |
+
df = df.drop(
|
| 50 |
+
columns=[c for c in drop_columns if c in df.columns], errors="ignore"
|
| 51 |
+
)
|
| 52 |
|
| 53 |
# Normalize keypoints
|
| 54 |
for i in range(17):
|
| 55 |
for prefix in ["person1_kp", "person2_kp", "relative_kp"]:
|
| 56 |
+
if f"{prefix}{i}_x" in df.columns:
|
| 57 |
+
df[f"{prefix}{i}_x"] = df[f"{prefix}{i}_x"] / frame_width
|
| 58 |
+
if f"{prefix}{i}_y" in df.columns:
|
| 59 |
+
df[f"{prefix}{i}_y"] = df[f"{prefix}{i}_y"] / frame_height
|
| 60 |
|
| 61 |
+
# Scale motion/distance columns
|
| 62 |
+
for col in [
|
| 63 |
+
"distance",
|
| 64 |
+
"relative_distance",
|
| 65 |
+
"motion_average_speed",
|
| 66 |
+
"motion_motion_intensity",
|
| 67 |
+
]:
|
| 68 |
+
if col in df.columns:
|
| 69 |
+
df[col] = self.scaler.fit_transform(
|
| 70 |
+
df[[col]]
|
| 71 |
+
) # change to transform() in production
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
return df
|
|
|
|
|
|
|
|
|
{backend/utils β services/video_data_extraction}/__init__.py
RENAMED
|
File without changes
|
{backend/services β services}/video_data_extraction/video_preprocessor.py
RENAMED
|
@@ -2,8 +2,8 @@ import os
|
|
| 2 |
import cv2
|
| 3 |
import torch
|
| 4 |
import pandas as pd
|
| 5 |
-
from
|
| 6 |
-
from
|
| 7 |
|
| 8 |
|
| 9 |
class VideoDataExtractor:
|
|
@@ -13,23 +13,23 @@ class VideoDataExtractor:
|
|
| 13 |
def extract_video_data(
|
| 14 |
self,
|
| 15 |
video_path,
|
| 16 |
-
output_csv_path,
|
| 17 |
output_folder=None,
|
| 18 |
show_video=False,
|
| 19 |
save_video=False,
|
| 20 |
):
|
| 21 |
"""
|
| 22 |
-
Extract data from a video file.
|
| 23 |
|
| 24 |
Args:
|
| 25 |
video_path: Path to input video
|
| 26 |
-
output_csv_path:
|
| 27 |
-
output_folder:
|
| 28 |
show_video: Whether to display video during processing
|
| 29 |
save_video: Whether to save output video
|
| 30 |
|
| 31 |
Returns:
|
| 32 |
-
|
| 33 |
"""
|
| 34 |
cap = None
|
| 35 |
video_writer = None
|
|
@@ -51,16 +51,13 @@ class VideoDataExtractor:
|
|
| 51 |
|
| 52 |
video_name = os.path.splitext(os.path.basename(video_path))[0]
|
| 53 |
|
| 54 |
-
#
|
| 55 |
batch_size, frame_skip = self.extractor.preprocessor.set_resolution_config(
|
| 56 |
frame_width, frame_height
|
| 57 |
)
|
| 58 |
self.extractor.preprocessor.frame_skip = frame_skip
|
| 59 |
|
| 60 |
-
|
| 61 |
-
print(f"Using frame_skip: {frame_skip}")
|
| 62 |
-
|
| 63 |
-
# Initialize video writer if needed
|
| 64 |
if output_folder and save_video:
|
| 65 |
os.makedirs(output_folder, exist_ok=True)
|
| 66 |
output_video_path = os.path.join(
|
|
@@ -73,7 +70,7 @@ class VideoDataExtractor:
|
|
| 73 |
(frame_width, frame_height),
|
| 74 |
)
|
| 75 |
|
| 76 |
-
# Reset extractor for
|
| 77 |
self.extractor.reset()
|
| 78 |
|
| 79 |
# Process frames
|
|
@@ -83,13 +80,11 @@ class VideoDataExtractor:
|
|
| 83 |
if not ret:
|
| 84 |
break
|
| 85 |
|
| 86 |
-
# Extract features
|
| 87 |
frame_data, annotated_frame = self.extractor.extract_features(
|
| 88 |
frame, frame_idx
|
| 89 |
)
|
| 90 |
|
| 91 |
if frame_data is not None:
|
| 92 |
-
# Process interactions
|
| 93 |
for interaction in frame_data["interactions"]:
|
| 94 |
interaction_id = (
|
| 95 |
interaction["person1_id"],
|
|
@@ -108,34 +103,30 @@ class VideoDataExtractor:
|
|
| 108 |
)
|
| 109 |
csv_data.append(row)
|
| 110 |
|
| 111 |
-
# Write frame to output video
|
| 112 |
if video_writer is not None and annotated_frame is not None:
|
| 113 |
video_writer.write(annotated_frame)
|
| 114 |
|
| 115 |
-
# Show video if enabled
|
| 116 |
if show_video and annotated_frame is not None:
|
| 117 |
cv2.imshow("Video Data Extraction", annotated_frame)
|
| 118 |
-
|
| 119 |
-
if key == ord("q"):
|
| 120 |
break
|
| 121 |
|
| 122 |
-
# Clear memory periodically
|
| 123 |
if frame_idx % 100 == 0:
|
| 124 |
torch.cuda.empty_cache()
|
| 125 |
|
|
|
|
| 126 |
if csv_data:
|
| 127 |
df = pd.DataFrame(csv_data)
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
return frame_width, frame_height, len(csv_data)
|
| 139 |
|
| 140 |
finally:
|
| 141 |
if cap is not None:
|
|
|
|
| 2 |
import cv2
|
| 3 |
import torch
|
| 4 |
import pandas as pd
|
| 5 |
+
from feature_extraction.extractor import VideoFeatureExtractor
|
| 6 |
+
from utils.csv_utils import _create_interaction_row
|
| 7 |
|
| 8 |
|
| 9 |
class VideoDataExtractor:
|
|
|
|
| 13 |
def extract_video_data(
|
| 14 |
self,
|
| 15 |
video_path,
|
| 16 |
+
output_csv_path=None,
|
| 17 |
output_folder=None,
|
| 18 |
show_video=False,
|
| 19 |
save_video=False,
|
| 20 |
):
|
| 21 |
"""
|
| 22 |
+
Extract interaction data from a video file and return a DataFrame.
|
| 23 |
|
| 24 |
Args:
|
| 25 |
video_path: Path to input video
|
| 26 |
+
output_csv_path: Optional path to save CSV output
|
| 27 |
+
output_folder: Optional folder to save annotated video
|
| 28 |
show_video: Whether to display video during processing
|
| 29 |
save_video: Whether to save output video
|
| 30 |
|
| 31 |
Returns:
|
| 32 |
+
pandas.DataFrame containing extracted interactions
|
| 33 |
"""
|
| 34 |
cap = None
|
| 35 |
video_writer = None
|
|
|
|
| 51 |
|
| 52 |
video_name = os.path.splitext(os.path.basename(video_path))[0]
|
| 53 |
|
| 54 |
+
# Configure resolution-based settings
|
| 55 |
batch_size, frame_skip = self.extractor.preprocessor.set_resolution_config(
|
| 56 |
frame_width, frame_height
|
| 57 |
)
|
| 58 |
self.extractor.preprocessor.frame_skip = frame_skip
|
| 59 |
|
| 60 |
+
# Initialize video writer if required
|
|
|
|
|
|
|
|
|
|
| 61 |
if output_folder and save_video:
|
| 62 |
os.makedirs(output_folder, exist_ok=True)
|
| 63 |
output_video_path = os.path.join(
|
|
|
|
| 70 |
(frame_width, frame_height),
|
| 71 |
)
|
| 72 |
|
| 73 |
+
# Reset extractor for a fresh start
|
| 74 |
self.extractor.reset()
|
| 75 |
|
| 76 |
# Process frames
|
|
|
|
| 80 |
if not ret:
|
| 81 |
break
|
| 82 |
|
|
|
|
| 83 |
frame_data, annotated_frame = self.extractor.extract_features(
|
| 84 |
frame, frame_idx
|
| 85 |
)
|
| 86 |
|
| 87 |
if frame_data is not None:
|
|
|
|
| 88 |
for interaction in frame_data["interactions"]:
|
| 89 |
interaction_id = (
|
| 90 |
interaction["person1_id"],
|
|
|
|
| 103 |
)
|
| 104 |
csv_data.append(row)
|
| 105 |
|
|
|
|
| 106 |
if video_writer is not None and annotated_frame is not None:
|
| 107 |
video_writer.write(annotated_frame)
|
| 108 |
|
|
|
|
| 109 |
if show_video and annotated_frame is not None:
|
| 110 |
cv2.imshow("Video Data Extraction", annotated_frame)
|
| 111 |
+
if cv2.waitKey(1) & 0xFF == ord("q"):
|
|
|
|
| 112 |
break
|
| 113 |
|
|
|
|
| 114 |
if frame_idx % 100 == 0:
|
| 115 |
torch.cuda.empty_cache()
|
| 116 |
|
| 117 |
+
# β
Return only the DataFrame
|
| 118 |
if csv_data:
|
| 119 |
df = pd.DataFrame(csv_data)
|
| 120 |
+
if output_csv_path:
|
| 121 |
+
df.to_csv(
|
| 122 |
+
output_csv_path,
|
| 123 |
+
mode="a" if os.path.exists(output_csv_path) else "w",
|
| 124 |
+
header=not os.path.exists(output_csv_path),
|
| 125 |
+
index=False,
|
| 126 |
+
)
|
| 127 |
+
return df
|
| 128 |
+
else:
|
| 129 |
+
return pd.DataFrame() # empty DataFrame if nothing found
|
|
|
|
| 130 |
|
| 131 |
finally:
|
| 132 |
if cap is not None:
|
utils/__init__.py
ADDED
|
File without changes
|
{backend/utils β utils}/csv_utils.py
RENAMED
|
File without changes
|
{backend/utils β utils}/gpu.py
RENAMED
|
File without changes
|
{backend/utils β utils}/id_utils.py
RENAMED
|
File without changes
|
{backend/utils β utils}/interaction_utils.py
RENAMED
|
File without changes
|
{backend/utils β utils}/iou_utils.py
RENAMED
|
File without changes
|
{backend/utils β utils}/motion_utils.py
RENAMED
|
File without changes
|
{backend/utils β utils}/visualizer.py
RENAMED
|
File without changes
|