initial commit
Browse files- README.md +0 -0
- deployment/app.py +131 -0
- deployment/outputs/seq3-drone_0000010_jpg.rf.1d1c9cb09ed588cbe7fa1eecc0947021.jpg +0 -0
- deployment/outputs/seq3-drone_0000018_jpg.rf.bd265e7b0f05b9d031e42f912e788da1.jpg +0 -0
- deployment/outputs/seq3-drone_0000155_jpg.rf.a34f65f74dcd9fe48304ac8cb3cf677d.jpg +0 -0
- deployment/outputs/seq3-drone_0002496_jpg.rf.af29baeec76b255e74d1497d516ff654.jpg +0 -0
- deployment/static/style.css +44 -0
- deployment/static/uploads/1_png.rf.2543efb6c056bc6a60b2946ecc4ce008.jpg +0 -0
- deployment/static/uploads/2_jpeg_jpg.rf.e89231a86b8a496a721d7dbe9a958206.jpg +0 -0
- deployment/static/uploads/Figure_11.png +0 -0
- deployment/static/uploads/_-_-41-_png_jpg.rf.c9eb095ec6cae86d10d90e7d2c2f920c.jpg +0 -0
- deployment/static/uploads/result.jpg +0 -0
- deployment/static/uploads/seq3-drone_0000010_jpg.rf.1d1c9cb09ed588cbe7fa1eecc0947021.jpg +0 -0
- deployment/static/uploads/seq3-drone_0000018_jpg.rf.bd265e7b0f05b9d031e42f912e788da1.jpg +0 -0
- deployment/static/uploads/seq3-drone_0000155_jpg.rf.a34f65f74dcd9fe48304ac8cb3cf677d.jpg +0 -0
- deployment/static/uploads/seq3-drone_0000606_jpg.rf.f13a5ae4dac612b023ff93fb3256139a.jpg +0 -0
- deployment/static/uploads/seq3-drone_0002496_jpg.rf.af29baeec76b255e74d1497d516ff654.jpg +0 -0
- deployment/templates/index.html +76 -0
- models/fasterrcnn_model.pth +3 -0
- requirements.txt +9 -0
- scripts/__pycache__/dataset.cpython-311.pyc +0 -0
- scripts/dataset.py +57 -0
- scripts/inference.py +143 -0
- scripts/train.py +127 -0
- static/uploads/result.jpg +0 -0
- static/uploads/seq3-drone_0000018_jpg.rf.bd265e7b0f05b9d031e42f912e788da1.jpg +0 -0
- static/uploads/seq3-drone_0000812_jpg.rf.8deaa8bc13cb2daa0535fea033dd1482.jpg +0 -0
README.md
ADDED
|
File without changes
|
deployment/app.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import torchvision.transforms as transforms
|
| 4 |
+
import torchvision.models.detection as detection
|
| 5 |
+
from flask import Flask, request, render_template, send_from_directory, url_for, Response
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import cv2
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
# Initialize Flask app
|
| 11 |
+
app = Flask(__name__)
|
| 12 |
+
UPLOAD_FOLDER = os.path.abspath(os.path.join("deployment", "static", "uploads"))
|
| 13 |
+
OUTPUT_FOLDER = os.path.abspath(os.path.join("deployment", "outputs"))
|
| 14 |
+
|
| 15 |
+
# Ensure directories exist
|
| 16 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
| 17 |
+
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
|
| 18 |
+
|
| 19 |
+
# Define class names
|
| 20 |
+
CLASS_NAMES = {
|
| 21 |
+
0: "vehicle", 1: "bicycle", 2: "bus", 3: "car", 4: "lorry"
|
| 22 |
+
}
|
| 23 |
+
NUM_CLASSES = len(CLASS_NAMES) + 1 # Include background class
|
| 24 |
+
|
| 25 |
+
# Load Faster R-CNN model
|
| 26 |
+
def load_model(model_path, num_classes):
|
| 27 |
+
model = detection.fasterrcnn_resnet50_fpn(weights=None, num_classes=num_classes)
|
| 28 |
+
model.load_state_dict(torch.load(model_path, map_location='cpu'))
|
| 29 |
+
model.eval()
|
| 30 |
+
return model
|
| 31 |
+
|
| 32 |
+
model_path = os.path.join("models", "fasterrcnn_model.pth")
|
| 33 |
+
model = load_model(model_path, NUM_CLASSES)
|
| 34 |
+
|
| 35 |
+
# Image preprocessing
|
| 36 |
+
def preprocess_image(image):
|
| 37 |
+
transform = transforms.Compose([transforms.ToTensor()])
|
| 38 |
+
return transform(image).unsqueeze(0)
|
| 39 |
+
|
| 40 |
+
# Draw predictions
|
| 41 |
+
def draw_predictions(image, prediction):
|
| 42 |
+
boxes, scores, labels = prediction['boxes'], prediction['scores'], prediction['labels']
|
| 43 |
+
for i in range(len(boxes)):
|
| 44 |
+
if scores[i] > 0.5: # Confidence threshold
|
| 45 |
+
x1, y1, x2, y2 = map(int, boxes[i].tolist())
|
| 46 |
+
class_name = CLASS_NAMES.get(labels[i].item(), f"Class {labels[i].item()}")
|
| 47 |
+
cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
| 48 |
+
cv2.putText(image, f"{class_name}: {scores[i]:.2f}", (x1, y1 - 10),
|
| 49 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
|
| 50 |
+
return image
|
| 51 |
+
|
| 52 |
+
# Home route (upload and detect)
|
| 53 |
+
@app.route('/', methods=['GET', 'POST'])
|
| 54 |
+
def upload_and_predict():
|
| 55 |
+
if request.method == 'POST':
|
| 56 |
+
file = request.files['file']
|
| 57 |
+
if file:
|
| 58 |
+
filename = file.filename
|
| 59 |
+
upload_path = os.path.join(UPLOAD_FOLDER, filename)
|
| 60 |
+
result_path = os.path.join(OUTPUT_FOLDER, filename)
|
| 61 |
+
file.save(upload_path)
|
| 62 |
+
|
| 63 |
+
print(f"✅ Uploaded file saved at: {upload_path}")
|
| 64 |
+
|
| 65 |
+
# Run inference
|
| 66 |
+
img = Image.open(upload_path).convert("RGB")
|
| 67 |
+
img_tensor = preprocess_image(img)
|
| 68 |
+
|
| 69 |
+
with torch.no_grad():
|
| 70 |
+
prediction = model(img_tensor)[0]
|
| 71 |
+
|
| 72 |
+
# Draw results
|
| 73 |
+
image_cv = cv2.imread(upload_path)
|
| 74 |
+
if image_cv is None:
|
| 75 |
+
print(f"❌ ERROR: OpenCV could not read {upload_path}")
|
| 76 |
+
else:
|
| 77 |
+
result_image = draw_predictions(image_cv, prediction)
|
| 78 |
+
cv2.imwrite(result_path, result_image)
|
| 79 |
+
print(f"✅ Result image saved at: {result_path}")
|
| 80 |
+
|
| 81 |
+
return render_template('index.html',
|
| 82 |
+
uploaded_image=url_for('uploaded_file', filename=filename),
|
| 83 |
+
result_image=url_for('output_file', filename=filename))
|
| 84 |
+
|
| 85 |
+
return render_template('index.html')
|
| 86 |
+
|
| 87 |
+
# Serve uploaded images correctly
|
| 88 |
+
@app.route('/uploads/<filename>')
|
| 89 |
+
def uploaded_file(filename):
|
| 90 |
+
return send_from_directory(os.path.abspath(UPLOAD_FOLDER), filename)
|
| 91 |
+
|
| 92 |
+
# Serve output images correctly
|
| 93 |
+
@app.route('/outputs/<filename>')
|
| 94 |
+
def output_file(filename):
|
| 95 |
+
return send_from_directory(os.path.abspath(OUTPUT_FOLDER), filename)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
# Real-time webcam detection
|
| 99 |
+
def generate_frames():
|
| 100 |
+
cap = cv2.VideoCapture(0)
|
| 101 |
+
while cap.isOpened():
|
| 102 |
+
success, frame = cap.read()
|
| 103 |
+
if not success:
|
| 104 |
+
break
|
| 105 |
+
|
| 106 |
+
# Convert frame and preprocess
|
| 107 |
+
img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
|
| 108 |
+
img_tensor = preprocess_image(img)
|
| 109 |
+
|
| 110 |
+
# Run inference
|
| 111 |
+
with torch.no_grad():
|
| 112 |
+
prediction = model(img_tensor)[0]
|
| 113 |
+
|
| 114 |
+
# Draw results
|
| 115 |
+
frame = draw_predictions(frame, prediction)
|
| 116 |
+
|
| 117 |
+
# Convert to JPEG
|
| 118 |
+
_, buffer = cv2.imencode('.jpg', frame)
|
| 119 |
+
frame_bytes = buffer.tobytes()
|
| 120 |
+
|
| 121 |
+
yield (b'--frame\r\n'
|
| 122 |
+
b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
|
| 123 |
+
|
| 124 |
+
cap.release()
|
| 125 |
+
|
| 126 |
+
@app.route('/video_feed')
|
| 127 |
+
def video_feed():
|
| 128 |
+
return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
|
| 129 |
+
|
| 130 |
+
if __name__ == '__main__':
|
| 131 |
+
app.run(debug=True)
|
deployment/outputs/seq3-drone_0000010_jpg.rf.1d1c9cb09ed588cbe7fa1eecc0947021.jpg
ADDED
|
deployment/outputs/seq3-drone_0000018_jpg.rf.bd265e7b0f05b9d031e42f912e788da1.jpg
ADDED
|
deployment/outputs/seq3-drone_0000155_jpg.rf.a34f65f74dcd9fe48304ac8cb3cf677d.jpg
ADDED
|
deployment/outputs/seq3-drone_0002496_jpg.rf.af29baeec76b255e74d1497d516ff654.jpg
ADDED
|
deployment/static/style.css
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
font-family: Arial, sans-serif;
|
| 3 |
+
background-color: #f4f4f4;
|
| 4 |
+
text-align: center;
|
| 5 |
+
padding: 20px;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
h2 {
|
| 9 |
+
color: #333;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
form {
|
| 13 |
+
background: white;
|
| 14 |
+
padding: 20px;
|
| 15 |
+
border-radius: 8px;
|
| 16 |
+
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
| 17 |
+
display: inline-block;
|
| 18 |
+
margin: 20px;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
input[type="file"] {
|
| 22 |
+
margin: 10px 0;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
button {
|
| 26 |
+
background-color: #28a745;
|
| 27 |
+
color: white;
|
| 28 |
+
border: none;
|
| 29 |
+
padding: 10px 15px;
|
| 30 |
+
border-radius: 5px;
|
| 31 |
+
cursor: pointer;
|
| 32 |
+
font-size: 16px;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
button:hover {
|
| 36 |
+
background-color: #218838;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
img {
|
| 40 |
+
margin-top: 15px;
|
| 41 |
+
max-width: 80%;
|
| 42 |
+
border-radius: 8px;
|
| 43 |
+
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
|
| 44 |
+
}
|
deployment/static/uploads/1_png.rf.2543efb6c056bc6a60b2946ecc4ce008.jpg
ADDED
|
deployment/static/uploads/2_jpeg_jpg.rf.e89231a86b8a496a721d7dbe9a958206.jpg
ADDED
|
deployment/static/uploads/Figure_11.png
ADDED
|
deployment/static/uploads/_-_-41-_png_jpg.rf.c9eb095ec6cae86d10d90e7d2c2f920c.jpg
ADDED
|
deployment/static/uploads/result.jpg
ADDED
|
deployment/static/uploads/seq3-drone_0000010_jpg.rf.1d1c9cb09ed588cbe7fa1eecc0947021.jpg
ADDED
|
deployment/static/uploads/seq3-drone_0000018_jpg.rf.bd265e7b0f05b9d031e42f912e788da1.jpg
ADDED
|
deployment/static/uploads/seq3-drone_0000155_jpg.rf.a34f65f74dcd9fe48304ac8cb3cf677d.jpg
ADDED
|
deployment/static/uploads/seq3-drone_0000606_jpg.rf.f13a5ae4dac612b023ff93fb3256139a.jpg
ADDED
|
deployment/static/uploads/seq3-drone_0002496_jpg.rf.af29baeec76b255e74d1497d516ff654.jpg
ADDED
|
deployment/templates/index.html
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Faster R-CNN Object Detection</title>
|
| 7 |
+
<style>
|
| 8 |
+
body {
|
| 9 |
+
font-family: Arial, sans-serif;
|
| 10 |
+
text-align: center;
|
| 11 |
+
background-color: #f4f4f4;
|
| 12 |
+
padding: 20px;
|
| 13 |
+
}
|
| 14 |
+
h1 {
|
| 15 |
+
color: #333;
|
| 16 |
+
}
|
| 17 |
+
.container {
|
| 18 |
+
background: white;
|
| 19 |
+
padding: 20px;
|
| 20 |
+
border-radius: 10px;
|
| 21 |
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
| 22 |
+
max-width: 600px;
|
| 23 |
+
margin: auto;
|
| 24 |
+
}
|
| 25 |
+
input[type="file"] {
|
| 26 |
+
margin: 20px 0;
|
| 27 |
+
}
|
| 28 |
+
.image-container {
|
| 29 |
+
margin-top: 20px;
|
| 30 |
+
}
|
| 31 |
+
img {
|
| 32 |
+
max-width: 100%;
|
| 33 |
+
border: 2px solid #ddd;
|
| 34 |
+
border-radius: 5px;
|
| 35 |
+
}
|
| 36 |
+
.btn {
|
| 37 |
+
display: inline-block;
|
| 38 |
+
padding: 10px 20px;
|
| 39 |
+
font-size: 16px;
|
| 40 |
+
background-color: #007bff;
|
| 41 |
+
color: white;
|
| 42 |
+
border: none;
|
| 43 |
+
cursor: pointer;
|
| 44 |
+
border-radius: 5px;
|
| 45 |
+
margin-top: 10px;
|
| 46 |
+
}
|
| 47 |
+
.btn:hover {
|
| 48 |
+
background-color: #0056b3;
|
| 49 |
+
}
|
| 50 |
+
</style>
|
| 51 |
+
</head>
|
| 52 |
+
<body>
|
| 53 |
+
<div class="container">
|
| 54 |
+
<h1>Faster R-CNN Object Detection</h1>
|
| 55 |
+
|
| 56 |
+
<form action="/" method="post" enctype="multipart/form-data">
|
| 57 |
+
<input type="file" name="file" required>
|
| 58 |
+
<button type="submit" class="btn">Upload & Detect</button>
|
| 59 |
+
</form>
|
| 60 |
+
|
| 61 |
+
{% if uploaded_image %}
|
| 62 |
+
<div class="image-container">
|
| 63 |
+
<h2>Uploaded Image</h2>
|
| 64 |
+
<img src="{{ uploaded_image }}" alt="Uploaded Image">
|
| 65 |
+
</div>
|
| 66 |
+
{% endif %}
|
| 67 |
+
|
| 68 |
+
{% if result_image %}
|
| 69 |
+
<div class="image-container">
|
| 70 |
+
<h2>Detection Results</h2>
|
| 71 |
+
<img src="{{ result_image }}" alt="Detected Image">
|
| 72 |
+
</div>
|
| 73 |
+
{% endif %}
|
| 74 |
+
</div>
|
| 75 |
+
</body>
|
| 76 |
+
</html>
|
models/fasterrcnn_model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:115326c8ea7e52e62ed0d14c5fa5d9ee67bdeac5324f8a0a559cd1b29d95bba6
|
| 3 |
+
size 165811943
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
torchvision
|
| 3 |
+
torchaudio
|
| 4 |
+
pycocotools
|
| 5 |
+
matplotlib
|
| 6 |
+
numpy
|
| 7 |
+
pandas
|
| 8 |
+
opencv-python
|
| 9 |
+
tqdm
|
scripts/__pycache__/dataset.cpython-311.pyc
ADDED
|
Binary file (3.52 kB). View file
|
|
|
scripts/dataset.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import torchvision.transforms as transforms
|
| 4 |
+
from torch.utils.data import Dataset
|
| 5 |
+
from pycocotools.coco import COCO
|
| 6 |
+
from PIL import Image
|
| 7 |
+
|
| 8 |
+
class COCODataset(Dataset):
|
| 9 |
+
def __init__(self, root, annotation_file, transforms=None):
|
| 10 |
+
self.root = root
|
| 11 |
+
self.coco = COCO(annotation_file)
|
| 12 |
+
self.transforms = transforms
|
| 13 |
+
|
| 14 |
+
self.image_ids = list(self.coco.imgs.keys())
|
| 15 |
+
|
| 16 |
+
def __getitem__(self, idx):
|
| 17 |
+
image_id = self.image_ids[idx]
|
| 18 |
+
image_info = self.coco.imgs[image_id]
|
| 19 |
+
image_path = os.path.join(self.root, image_info["file_name"])
|
| 20 |
+
|
| 21 |
+
image = Image.open(image_path).convert("RGB")
|
| 22 |
+
|
| 23 |
+
annotations = self.coco.loadAnns(self.coco.getAnnIds(imgIds=image_id))
|
| 24 |
+
|
| 25 |
+
boxes = []
|
| 26 |
+
labels = []
|
| 27 |
+
|
| 28 |
+
for ann in annotations:
|
| 29 |
+
xmin = ann["bbox"][0]
|
| 30 |
+
ymin = ann["bbox"][1]
|
| 31 |
+
width = ann["bbox"][2]
|
| 32 |
+
height = ann["bbox"][3]
|
| 33 |
+
|
| 34 |
+
boxes.append([xmin, ymin, xmin + width, ymin + height])
|
| 35 |
+
labels.append(ann["category_id"]) # Ensure category_id starts from 1
|
| 36 |
+
|
| 37 |
+
if len(boxes) == 0:
|
| 38 |
+
boxes = torch.zeros((0, 4), dtype=torch.float32)
|
| 39 |
+
labels = torch.zeros((0,), dtype=torch.int64)
|
| 40 |
+
else:
|
| 41 |
+
boxes = torch.tensor(boxes, dtype=torch.float32)
|
| 42 |
+
labels = torch.tensor(labels, dtype=torch.int64)
|
| 43 |
+
|
| 44 |
+
target = {"boxes": boxes, "labels": labels, "image_id": torch.tensor([image_id])}
|
| 45 |
+
|
| 46 |
+
if self.transforms:
|
| 47 |
+
image = self.transforms(image)
|
| 48 |
+
|
| 49 |
+
return image, target
|
| 50 |
+
|
| 51 |
+
def __len__(self):
|
| 52 |
+
return len(self.image_ids)
|
| 53 |
+
|
| 54 |
+
def get_transforms():
|
| 55 |
+
return transforms.Compose([
|
| 56 |
+
transforms.ToTensor()
|
| 57 |
+
])
|
scripts/inference.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torchvision.transforms as transforms
|
| 3 |
+
import torchvision.models.detection as detection
|
| 4 |
+
from PIL import Image
|
| 5 |
+
import cv2
|
| 6 |
+
import numpy as np
|
| 7 |
+
|
| 8 |
+
# Define class names
|
| 9 |
+
CLASS_NAMES = {
|
| 10 |
+
0: "vehicle", 1: "bicycle", 2: "bus", 3: "car", 4: "lorry"
|
| 11 |
+
}
|
| 12 |
+
NUM_CLASSES = len(CLASS_NAMES) + 1 # Include background class
|
| 13 |
+
|
| 14 |
+
# Global flag to stop detection when clicking the window
|
| 15 |
+
stop_detection = False
|
| 16 |
+
|
| 17 |
+
# Load Faster R-CNN model
|
| 18 |
+
def load_model(model_path, num_classes):
|
| 19 |
+
model = detection.fasterrcnn_resnet50_fpn(weights=None, num_classes=num_classes)
|
| 20 |
+
|
| 21 |
+
# Load the trained model
|
| 22 |
+
checkpoint = torch.load(model_path, map_location="cpu")
|
| 23 |
+
model.load_state_dict(checkpoint)
|
| 24 |
+
|
| 25 |
+
model.eval()
|
| 26 |
+
return model
|
| 27 |
+
|
| 28 |
+
# Transform input image
|
| 29 |
+
def preprocess_image(image):
|
| 30 |
+
transform = transforms.Compose([transforms.ToTensor()])
|
| 31 |
+
return transform(image).unsqueeze(0)
|
| 32 |
+
|
| 33 |
+
# Run inference on an image
|
| 34 |
+
def run_inference_on_image(image_path, model):
|
| 35 |
+
img = Image.open(image_path).convert("RGB")
|
| 36 |
+
img_tensor = preprocess_image(img)
|
| 37 |
+
|
| 38 |
+
with torch.no_grad():
|
| 39 |
+
prediction = model(img_tensor)[0]
|
| 40 |
+
|
| 41 |
+
draw_predictions(image_path, prediction)
|
| 42 |
+
|
| 43 |
+
# Draw predictions on an image
|
| 44 |
+
def draw_predictions(image_path, prediction):
|
| 45 |
+
image = cv2.imread(image_path)
|
| 46 |
+
boxes, scores, labels = prediction['boxes'], prediction['scores'], prediction['labels']
|
| 47 |
+
|
| 48 |
+
for i in range(len(boxes)):
|
| 49 |
+
if scores[i] > 0.5: # Confidence threshold
|
| 50 |
+
x1, y1, x2, y2 = map(int, boxes[i].tolist())
|
| 51 |
+
class_id = labels[i].item()
|
| 52 |
+
class_name = CLASS_NAMES.get(class_id, f"Class {class_id}")
|
| 53 |
+
cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
| 54 |
+
cv2.putText(image, f"{class_name}: {scores[i]:.2f}", (x1, y1 - 10),
|
| 55 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
|
| 56 |
+
|
| 57 |
+
cv2.imshow("Detection Result", image)
|
| 58 |
+
cv2.waitKey(0)
|
| 59 |
+
cv2.destroyAllWindows()
|
| 60 |
+
|
| 61 |
+
# Mouse click event function to stop real-time detection
|
| 62 |
+
def stop_real_time_detection(event, x, y, flags, param):
|
| 63 |
+
global stop_detection
|
| 64 |
+
if event == cv2.EVENT_LBUTTONDOWN: # Detect left mouse button click
|
| 65 |
+
stop_detection = True
|
| 66 |
+
|
| 67 |
+
# Real-time detection with webcam
|
| 68 |
+
def real_time_detection(model):
|
| 69 |
+
global stop_detection
|
| 70 |
+
stop_detection = False # Reset flag before starting detection
|
| 71 |
+
|
| 72 |
+
cap = cv2.VideoCapture(0)
|
| 73 |
+
if not cap.isOpened():
|
| 74 |
+
print("❌ Error: Could not open webcam.")
|
| 75 |
+
return
|
| 76 |
+
|
| 77 |
+
print("🎥 Starting real-time object detection. Click the window to close.")
|
| 78 |
+
|
| 79 |
+
cv2.namedWindow("Real-time Detection") # Name the OpenCV window
|
| 80 |
+
cv2.setMouseCallback("Real-time Detection", stop_real_time_detection) # Set mouse click callback
|
| 81 |
+
|
| 82 |
+
while True:
|
| 83 |
+
ret, frame = cap.read()
|
| 84 |
+
if not ret:
|
| 85 |
+
print("❌ Error: Failed to capture frame.")
|
| 86 |
+
break
|
| 87 |
+
|
| 88 |
+
img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
|
| 89 |
+
img_tensor = preprocess_image(img)
|
| 90 |
+
|
| 91 |
+
with torch.no_grad():
|
| 92 |
+
prediction = model(img_tensor)[0]
|
| 93 |
+
|
| 94 |
+
draw_predictions_live(frame, prediction)
|
| 95 |
+
|
| 96 |
+
cv2.imshow("Real-time Detection", frame)
|
| 97 |
+
|
| 98 |
+
# FIX: Process window events so OpenCV does not freeze
|
| 99 |
+
if cv2.waitKey(1) & 0xFF == ord('q'): # Press 'q' to exit (optional)
|
| 100 |
+
break
|
| 101 |
+
|
| 102 |
+
if stop_detection: # If window is clicked, stop detection
|
| 103 |
+
print("🛑 Stopping real-time detection...")
|
| 104 |
+
break
|
| 105 |
+
|
| 106 |
+
cap.release()
|
| 107 |
+
cv2.destroyAllWindows() # FIX: Close OpenCV windows properly
|
| 108 |
+
cv2.waitKey(1) # FIX: Ensure window is destroyed
|
| 109 |
+
|
| 110 |
+
# Draw predictions on live video feed
|
| 111 |
+
def draw_predictions_live(frame, prediction):
|
| 112 |
+
boxes, scores, labels = prediction['boxes'], prediction['scores'], prediction['labels']
|
| 113 |
+
|
| 114 |
+
for i in range(len(boxes)):
|
| 115 |
+
if scores[i] > 0.5:
|
| 116 |
+
x1, y1, x2, y2 = map(int, boxes[i].tolist())
|
| 117 |
+
class_id = labels[i].item()
|
| 118 |
+
class_name = CLASS_NAMES.get(class_id, f"Class {class_id}")
|
| 119 |
+
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
| 120 |
+
cv2.putText(frame, f"{class_name}: {scores[i]:.2f}", (x1, y1 - 10),
|
| 121 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
|
| 122 |
+
|
| 123 |
+
if __name__ == "__main__":
|
| 124 |
+
model = load_model("models/fasterrcnn_model.pth", NUM_CLASSES)
|
| 125 |
+
|
| 126 |
+
while True:
|
| 127 |
+
print("\nOptions:")
|
| 128 |
+
print("1 - Run object detection on an image")
|
| 129 |
+
print("2 - Run real-time object detection (Click to exit)")
|
| 130 |
+
print("q - Quit")
|
| 131 |
+
|
| 132 |
+
choice = input("Enter your choice: ").strip().lower()
|
| 133 |
+
|
| 134 |
+
if choice == "1":
|
| 135 |
+
image_path = input("Enter the path of the test image: ").strip()
|
| 136 |
+
run_inference_on_image(image_path, model)
|
| 137 |
+
elif choice == "2":
|
| 138 |
+
real_time_detection(model)
|
| 139 |
+
elif choice == "q":
|
| 140 |
+
print("👋 Exiting program.")
|
| 141 |
+
break
|
| 142 |
+
else:
|
| 143 |
+
print("⚠️ Invalid choice! Please enter '1', '2', or 'q'.")
|
scripts/train.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torchvision
|
| 3 |
+
import matplotlib.pyplot as plt
|
| 4 |
+
from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
|
| 5 |
+
from torchvision.models.detection import fasterrcnn_resnet50_fpn, FasterRCNN_ResNet50_FPN_Weights
|
| 6 |
+
from torch.utils.data import DataLoader
|
| 7 |
+
from torchmetrics.detection.mean_ap import MeanAveragePrecision
|
| 8 |
+
from torchvision import transforms as T
|
| 9 |
+
from scripts.dataset import COCODataset, get_transforms
|
| 10 |
+
|
| 11 |
+
# Paths to dataset
|
| 12 |
+
DATASET_ROOT = "dataset/train"
|
| 13 |
+
ANNOTATION_FILE = "dataset/annotations/instances_train.json"
|
| 14 |
+
|
| 15 |
+
# Function to get the model with fixed NMS
|
| 16 |
+
def get_model(num_classes):
|
| 17 |
+
model = fasterrcnn_resnet50_fpn(weights=FasterRCNN_ResNet50_FPN_Weights.COCO_V1)
|
| 18 |
+
|
| 19 |
+
# Replace the classifier with a new one (custom num_classes)
|
| 20 |
+
in_features = model.roi_heads.box_predictor.cls_score.in_features
|
| 21 |
+
model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
|
| 22 |
+
|
| 23 |
+
# Fix excessive bounding boxes by lowering NMS
|
| 24 |
+
model.rpn.nms_thresh = 0.3 # Default ~0.7
|
| 25 |
+
model.roi_heads.nms_thresh = 0.3
|
| 26 |
+
|
| 27 |
+
return model
|
| 28 |
+
|
| 29 |
+
# Define dataset and data loader
|
| 30 |
+
dataset = COCODataset(root=DATASET_ROOT, annotation_file=ANNOTATION_FILE, transforms=get_transforms())
|
| 31 |
+
dataloader = DataLoader(dataset, batch_size=4, shuffle=True, collate_fn=lambda batch: tuple(zip(*batch)))
|
| 32 |
+
|
| 33 |
+
# Device configuration
|
| 34 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 35 |
+
|
| 36 |
+
# Define the number of classes (5 classes + 1 background)
|
| 37 |
+
num_classes = 6
|
| 38 |
+
model = get_model(num_classes).to(device)
|
| 39 |
+
|
| 40 |
+
# Optimizer and learning rate scheduler
|
| 41 |
+
params = [p for p in model.parameters() if p.requires_grad]
|
| 42 |
+
optimizer = torch.optim.AdamW(params, lr=0.002, weight_decay=0.0001)
|
| 43 |
+
lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10)
|
| 44 |
+
|
| 45 |
+
# Initialize Mean Average Precision (mAP) metric
|
| 46 |
+
metric = MeanAveragePrecision()
|
| 47 |
+
|
| 48 |
+
# Function to evaluate the model
|
| 49 |
+
def evaluate_model(model, dataloader, device):
|
| 50 |
+
model.eval()
|
| 51 |
+
metric.reset()
|
| 52 |
+
|
| 53 |
+
with torch.no_grad():
|
| 54 |
+
for images, targets in dataloader:
|
| 55 |
+
images = [img.to(device) for img in images]
|
| 56 |
+
targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
|
| 57 |
+
|
| 58 |
+
outputs = model(images)
|
| 59 |
+
preds = [{"boxes": o["boxes"].cpu(), "scores": o["scores"].cpu(), "labels": o["labels"].cpu()} for o in outputs]
|
| 60 |
+
gts = [{"boxes": t["boxes"].cpu(), "labels": t["labels"].cpu()} for t in targets]
|
| 61 |
+
|
| 62 |
+
metric.update(preds, gts)
|
| 63 |
+
|
| 64 |
+
result = metric.compute()
|
| 65 |
+
return result["map"].item(), result["map_50"].item(), result["map_75"].item()
|
| 66 |
+
|
| 67 |
+
# Lists to store training loss and accuracy
|
| 68 |
+
losses_list = []
|
| 69 |
+
mAP_list = []
|
| 70 |
+
mAP50_list = []
|
| 71 |
+
mAP75_list = []
|
| 72 |
+
|
| 73 |
+
# Training loop
|
| 74 |
+
num_epochs = 15
|
| 75 |
+
for epoch in range(num_epochs):
|
| 76 |
+
model.train()
|
| 77 |
+
epoch_loss = 0.0
|
| 78 |
+
|
| 79 |
+
for images, targets in dataloader:
|
| 80 |
+
images = [image.to(device) for image in images]
|
| 81 |
+
targets = [{k: v.to(device) for k, v in target.items()} for target in targets]
|
| 82 |
+
|
| 83 |
+
optimizer.zero_grad()
|
| 84 |
+
loss_dict = model(images, targets)
|
| 85 |
+
losses = sum(loss for loss in loss_dict.values())
|
| 86 |
+
epoch_loss += losses.item()
|
| 87 |
+
|
| 88 |
+
losses.backward()
|
| 89 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=2.0)
|
| 90 |
+
optimizer.step()
|
| 91 |
+
|
| 92 |
+
lr_scheduler.step()
|
| 93 |
+
losses_list.append(epoch_loss)
|
| 94 |
+
|
| 95 |
+
# Compute mAP
|
| 96 |
+
mAP, mAP50, mAP75 = evaluate_model(model, dataloader, device)
|
| 97 |
+
mAP_list.append(mAP)
|
| 98 |
+
mAP50_list.append(mAP50)
|
| 99 |
+
mAP75_list.append(mAP75)
|
| 100 |
+
|
| 101 |
+
print(f"Epoch {epoch+1}/{num_epochs}, Loss: {epoch_loss:.4f}")
|
| 102 |
+
print(f"Epoch {epoch+1}: mAP: {mAP:.4f}, mAP@50: {mAP50:.4f}, mAP@75: {mAP75:.4f}")
|
| 103 |
+
|
| 104 |
+
# Save model
|
| 105 |
+
torch.save(model.state_dict(), "models/fasterrcnn_model.pth")
|
| 106 |
+
print("Training complete! Model saved as 'fasterrcnn_model.pth'.")
|
| 107 |
+
|
| 108 |
+
# Plot Loss Graph
|
| 109 |
+
plt.figure(figsize=(10, 5))
|
| 110 |
+
plt.plot(range(1, num_epochs + 1), losses_list, marker='o', linestyle='-')
|
| 111 |
+
plt.xlabel("Epochs")
|
| 112 |
+
plt.ylabel("Loss")
|
| 113 |
+
plt.title("Training Loss over Epochs")
|
| 114 |
+
plt.grid()
|
| 115 |
+
plt.show()
|
| 116 |
+
|
| 117 |
+
# Plot mAP Graphs
|
| 118 |
+
plt.figure(figsize=(10, 5))
|
| 119 |
+
plt.plot(range(1, num_epochs + 1), mAP_list, marker='o', linestyle='-', label="mAP")
|
| 120 |
+
plt.plot(range(1, num_epochs + 1), mAP50_list, marker='s', linestyle='-', label="mAP@50")
|
| 121 |
+
plt.plot(range(1, num_epochs + 1), mAP75_list, marker='d', linestyle='-', label="mAP@75")
|
| 122 |
+
plt.xlabel("Epochs")
|
| 123 |
+
plt.ylabel("Accuracy")
|
| 124 |
+
plt.title("mAP over Epochs")
|
| 125 |
+
plt.legend()
|
| 126 |
+
plt.grid()
|
| 127 |
+
plt.show()
|
static/uploads/result.jpg
ADDED
|
static/uploads/seq3-drone_0000018_jpg.rf.bd265e7b0f05b9d031e42f912e788da1.jpg
ADDED
|
static/uploads/seq3-drone_0000812_jpg.rf.8deaa8bc13cb2daa0535fea033dd1482.jpg
ADDED
|