|
import os |
|
import cv2 |
|
import numpy as np |
|
import base64 |
|
from flask import Flask, render_template_string, request, redirect, flash |
|
import roboflow |
|
import torch |
|
from collections import Counter |
|
|
|
app = Flask(__name__) |
|
app.secret_key = 'your_secret_key' |
|
|
|
|
|
|
|
|
|
|
|
|
|
API_KEY = "wLjPoPYaLmrqCIOFA0RH" |
|
PROJECT_ID = "base-model-box-r4suo-8lkk1-6dbqh" |
|
VERSION_NUMBER = "2" |
|
|
|
rf = roboflow.Roboflow(api_key=API_KEY) |
|
workspace = rf.workspace() |
|
project = workspace.project(PROJECT_ID) |
|
version = project.version(VERSION_NUMBER) |
|
box_model = version.model |
|
|
|
|
|
|
|
yolov5_model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) |
|
|
|
YOLO_FILTER_CLASSES = {"person", "car"} |
|
|
|
|
|
|
|
|
|
|
|
def compute_iou(boxA, boxB): |
|
xA = max(boxA[0], boxB[0]) |
|
yA = max(boxA[1], boxB[1]) |
|
xB = min(boxA[2], boxB[2]) |
|
yB = min(boxA[3], boxB[3]) |
|
interWidth = max(0, xB - xA) |
|
interHeight = max(0, yB - yA) |
|
interArea = interWidth * interHeight |
|
boxAArea = (boxA[2] - boxA[0]) * (boxA[3] - boxA[1]) |
|
boxBArea = (boxB[2] - boxB[0]) * (boxB[3] - boxB[1]) |
|
if boxAArea + boxBArea - interArea == 0: |
|
return 0 |
|
return interArea / float(boxAArea + boxBArea - interArea) |
|
|
|
def custom_nms(preds, iou_threshold=0.3): |
|
preds = sorted(preds, key=lambda x: x["confidence"], reverse=True) |
|
filtered_preds = [] |
|
for pred in preds: |
|
keep = True |
|
for kept in filtered_preds: |
|
if compute_iou(pred["box"], kept["box"]) > iou_threshold: |
|
keep = False |
|
break |
|
if keep: |
|
filtered_preds.append(pred) |
|
return filtered_preds |
|
|
|
def process_image(image_path): |
|
""" |
|
Process the uploaded image using both detection pipelines: |
|
(a) Box detection via Roboflow (with measurement using an ArUco marker). |
|
(b) YOLOv5 detection for persons and cars. |
|
Returns the annotated image and a list of detection info dictionaries. |
|
""" |
|
image = cv2.imread(image_path) |
|
if image is None: |
|
return None, "Could not read the image." |
|
img_height, img_width = image.shape[:2] |
|
|
|
detection_info = [] |
|
|
|
|
|
results = box_model.predict(image_path, confidence=50, overlap=30).json() |
|
predictions = results.get("predictions", []) |
|
processed_preds = [] |
|
for prediction in predictions: |
|
x, y, width, height = prediction["x"], prediction["y"], prediction["width"], prediction["height"] |
|
x1 = int(round(x - width / 2)) |
|
y1 = int(round(y - height / 2)) |
|
x2 = int(round(x + width / 2)) |
|
y2 = int(round(y + height / 2)) |
|
|
|
x1 = max(0, min(x1, img_width - 1)) |
|
y1 = max(0, min(y1, img_height - 1)) |
|
x2 = max(0, min(x2, img_width - 1)) |
|
y2 = max(0, min(y2, img_height - 1)) |
|
processed_preds.append({ |
|
"box": (x1, y1, x2, y2), |
|
"class": prediction["class"], |
|
"confidence": prediction["confidence"] |
|
}) |
|
box_detections = custom_nms(processed_preds, iou_threshold=0.3) |
|
|
|
|
|
marker_real_width_cm = 10.0 |
|
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) |
|
aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_6X6_250) |
|
aruco_params = cv2.aruco.DetectorParameters() |
|
corners, ids, _ = cv2.aruco.detectMarkers(gray, aruco_dict, parameters=aruco_params) |
|
if ids is not None and len(corners) > 0: |
|
marker_corners = corners[0].reshape((4, 2)) |
|
cv2.aruco.drawDetectedMarkers(image, corners, ids) |
|
marker_width_pixels = np.linalg.norm(marker_corners[0] - marker_corners[1]) |
|
marker_height_pixels = np.linalg.norm(marker_corners[1] - marker_corners[2]) |
|
marker_pixel_size = (marker_width_pixels + marker_height_pixels) / 2.0 |
|
conversion_factor = marker_real_width_cm / marker_pixel_size |
|
else: |
|
conversion_factor = None |
|
|
|
|
|
for pred in box_detections: |
|
x1, y1, x2, y2 = pred["box"] |
|
label = pred["class"] |
|
confidence = pred["confidence"] |
|
cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2) |
|
if conversion_factor is not None: |
|
box_width_pixels = x2 - x1 |
|
box_height_pixels = y2 - y1 |
|
box_width_cm = box_width_pixels * conversion_factor |
|
box_height_cm = box_height_pixels * conversion_factor |
|
size_text = f"{box_width_cm:.1f}x{box_height_cm:.1f} cm" |
|
detection_info.append({ |
|
"class": label, |
|
"confidence": f"{confidence:.2f}", |
|
"width_cm": f"{box_width_cm:.1f}", |
|
"height_cm": f"{box_height_cm:.1f}" |
|
}) |
|
else: |
|
size_text = "" |
|
detection_info.append({ |
|
"class": label, |
|
"confidence": f"{confidence:.2f}", |
|
"width_cm": "N/A", |
|
"height_cm": "N/A" |
|
}) |
|
text = f"{label} ({confidence:.2f}) {size_text}" |
|
(text_width, text_height), baseline = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1) |
|
cv2.rectangle(image, (x1, y1 - text_height - baseline - 5), (x1 + text_width, y1 - 5), (0, 255, 0), -1) |
|
cv2.putText(image, text, (x1, y1 - 5 - baseline), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1) |
|
|
|
|
|
|
|
img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) |
|
yolo_results = yolov5_model(img_rgb) |
|
df = yolo_results.pandas().xyxy[0] |
|
for _, row in df.iterrows(): |
|
if row['name'] in YOLO_FILTER_CLASSES: |
|
xmin = int(row['xmin']) |
|
ymin = int(row['ymin']) |
|
xmax = int(row['xmax']) |
|
ymax = int(row['ymax']) |
|
conf = row['confidence'] |
|
label = row['name'] |
|
cv2.rectangle(image, (xmin, ymin), (xmax, ymax), (255, 0, 0), 2) |
|
text = f"{label} ({conf:.2f})" |
|
(text_width, text_height), baseline = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1) |
|
cv2.rectangle(image, (xmin, ymin - text_height - baseline - 5), (xmin + text_width, ymin - 5), (255, 0, 0), -1) |
|
cv2.putText(image, text, (xmin, ymin - 5 - baseline), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1) |
|
detection_info.append({ |
|
"class": label, |
|
"confidence": f"{conf:.2f}", |
|
"width_cm": "N/A", |
|
"height_cm": "N/A" |
|
}) |
|
|
|
|
|
detection_counts = Counter(det["class"] for det in detection_info) |
|
if detection_counts: |
|
top_text = ", ".join(f"{cls}: {count}" for cls, count in detection_counts.items()) |
|
(info_width, info_height), info_baseline = cv2.getTextSize(top_text, cv2.FONT_HERSHEY_SIMPLEX, 1, 2) |
|
cv2.rectangle(image, (5, 5), (5 + info_width, 5 + info_height + info_baseline), (0, 255, 0), -1) |
|
cv2.putText(image, top_text, (5, 5 + info_height), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2) |
|
|
|
return image, detection_info |
|
|
|
|
|
|
|
|
|
|
|
@app.route('/', methods=['GET', 'POST']) |
|
def index(): |
|
image_data = None |
|
detection_info = None |
|
if request.method == 'POST': |
|
if 'file' not in request.files: |
|
flash('No file part') |
|
return redirect(request.url) |
|
file = request.files['file'] |
|
if file.filename == '': |
|
flash('No selected file') |
|
return redirect(request.url) |
|
upload_path = "uploaded.jpg" |
|
file.save(upload_path) |
|
processed_image, detection_info = process_image(upload_path) |
|
if processed_image is None: |
|
flash("Error processing image.") |
|
else: |
|
retval, buffer = cv2.imencode('.jpg', processed_image) |
|
image_data = base64.b64encode(buffer).decode('utf-8') |
|
os.remove(upload_path) |
|
return render_template_string(''' |
|
<!doctype html> |
|
<html> |
|
<head> |
|
<title>Multi-Detection & Measurement</title> |
|
<!-- Bootstrap CSS --> |
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> |
|
<style> |
|
body { |
|
background-color: #f8f9fa; |
|
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; |
|
} |
|
.container { |
|
margin-top: 30px; |
|
} |
|
.header { |
|
text-align: center; |
|
margin-bottom: 30px; |
|
} |
|
.card { |
|
margin-bottom: 30px; |
|
} |
|
.result-img { |
|
width: 100%; |
|
border: 1px solid #ddd; |
|
padding: 5px; |
|
} |
|
.table-responsive { |
|
margin-top: 20px; |
|
} |
|
.footer { |
|
text-align: center; |
|
font-size: 0.9em; |
|
color: #777; |
|
margin-top: 30px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<h1 class="header">Multi-Detection & Measurement</h1> |
|
<!-- Upload Form --> |
|
<div class="card"> |
|
<div class="card-body"> |
|
<form method="post" enctype="multipart/form-data"> |
|
<div class="form-group"> |
|
<label for="file">Choose an image to upload:</label> |
|
<input type="file" class="form-control-file" name="file" accept="image/*" id="file"> |
|
</div> |
|
<button type="submit" class="btn btn-primary">Upload</button> |
|
</form> |
|
{% with messages = get_flashed_messages() %} |
|
{% if messages %} |
|
<div class="alert alert-danger mt-3"> |
|
<ul> |
|
{% for message in messages %} |
|
<li>{{ message }}</li> |
|
{% endfor %} |
|
</ul> |
|
</div> |
|
{% endif %} |
|
{% endwith %} |
|
</div> |
|
</div> |
|
{% if image_data or detection_info %} |
|
<div class="row"> |
|
<div class="col-md-8"> |
|
<div class="card"> |
|
<div class="card-header"> |
|
Processed Image |
|
</div> |
|
<div class="card-body"> |
|
<img src="data:image/jpeg;base64,{{ image_data }}" alt="Processed Image" class="result-img"> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="col-md-4"> |
|
<div class="card"> |
|
<div class="card-header"> |
|
Detection Results |
|
</div> |
|
<div class="card-body"> |
|
<p>Total Results: <strong>{{ detection_info|length }}</strong></p> |
|
<div class="table-responsive"> |
|
<table class="table table-striped table-bordered"> |
|
<thead class="thead-dark"> |
|
<tr> |
|
<th>#</th> |
|
<th>Class</th> |
|
<th>Confidence</th> |
|
<th>Width (cm)</th> |
|
<th>Height (cm)</th> |
|
</tr> |
|
</thead> |
|
<tbody> |
|
{% for det in detection_info %} |
|
<tr> |
|
<td>{{ loop.index }}</td> |
|
<td>{{ det.class }}</td> |
|
<td>{{ det.confidence }}</td> |
|
<td>{{ det.width_cm }}</td> |
|
<td>{{ det.height_cm }}</td> |
|
</tr> |
|
{% endfor %} |
|
</tbody> |
|
</table> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
{% endif %} |
|
<div class="footer"> |
|
<p>© 2023 Multi-Detection App. All rights reserved.</p> |
|
</div> |
|
</div> |
|
<!-- Bootstrap JS and dependencies --> |
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script> |
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> |
|
</body> |
|
</html> |
|
''', image_data=image_data, detection_info=detection_info) |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
app.run(host="0.0.0.0", port=7860) |
|
|