Verbessere Streaming-Sicherheit

This commit is contained in:
2025-12-14 12:02:58 +00:00
parent c8f49e41f9
commit 7011b930af
4 changed files with 392 additions and 365 deletions

1
.flaskenv Normal file
View File

@@ -0,0 +1 @@
SECRET_KEY=4ea83cc33564c770781fe8205054cb43a1d2ee7e14ff9c1b1b86231686fdcf4f

674
app.py
View File

@@ -1,335 +1,343 @@
import os import os
import cv2 import uuid
import numpy as np import cv2
from flask import Flask, render_template, Response, request, redirect, url_for, send_from_directory, session, jsonify import numpy as np
from ultralytics import YOLO from threading import Event, Lock
from flask import (
app = Flask(__name__) Flask,
app.secret_key = 'vehicle_counting_secret_key_2024' # Required for session management render_template,
Response,
# Load the YOLOv8 model request,
model = YOLO("yolo11s.pt") redirect,
names = model.model.names url_for,
send_from_directory,
# Vehicle classes to count session,
VEHICLE_CLASSES = {'car', 'truck', 'bus', 'motorcycle'} jsonify,
abort,
# Helper function to check if two line segments intersect )
def line_intersect(p1, p2, p3, p4): from werkzeug.utils import secure_filename
""" from ultralytics import YOLO
Check if line segment p1-p2 intersects with line segment p3-p4
Returns True if they intersect, False otherwise app = Flask(__name__)
""" app.secret_key = os.environ.get('SECRET_KEY', 'vehicle_dev_secret')
x1, y1 = p1 app.config['MAX_CONTENT_LENGTH'] = 200 * 1024 * 1024 # 200MB upload cap
x2, y2 = p2
x3, y3 = p3 # Load the YOLOv8 model
x4, y4 = p4 model = YOLO("yolo11s.pt")
names = model.model.names
denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if abs(denom) < 1e-10: # Vehicle classes to count
return False VEHICLE_CLASSES = {'car', 'truck', 'bus', 'motorcycle'}
ALLOWED_EXTENSIONS = {'mp4', 'mov', 'avi', 'mkv'}
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom UPLOAD_DIR = 'uploads'
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom
# Track reset events per stream (webcam/video per session)
return 0 <= t <= 1 and 0 <= u <= 1 reset_events: dict[str, Event] = {}
reset_lock = Lock()
def ccw(A, B, C):
"""Check if three points are in counter-clockwise order"""
return (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0]) def allowed_file(filename: str) -> bool:
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def crossed_line(prev_pos, curr_pos, line_start, line_end):
"""
Check if movement from prev_pos to curr_pos crossed the line. def ensure_upload_dir() -> None:
Uses orientation check - more robust for frame skipping. if not os.path.exists(UPLOAD_DIR):
""" os.makedirs(UPLOAD_DIR)
# Check if the two line segments intersect
if line_intersect(prev_pos, curr_pos, line_start, line_end):
return True def get_line_from_session():
return False if 'counting_line' not in session:
session['counting_line'] = {'x1': 0, 'y1': 300, 'x2': 1020, 'y2': 300}
@app.route('/') return session['counting_line']
def index():
return render_template('index.html')
def fresh_vehicle_counts() -> dict[str, int]:
@app.route('/start_webcam') return {vehicle: 0 for vehicle in VEHICLE_CLASSES}
def start_webcam():
# Initialize default counting line in session if not set
if 'counting_line' not in session: def get_reset_event(stream_id: str) -> Event:
session['counting_line'] = {'x1': 0, 'y1': 300, 'x2': 1020, 'y2': 300} with reset_lock:
return render_template('webcam.html') event = reset_events.get(stream_id)
if event is None:
@app.route('/api/set_line', methods=['POST']) event = Event()
def set_counting_line(): reset_events[stream_id] = event
"""API endpoint to set the counting line coordinates""" return event
data = request.json
session['counting_line'] = {
'x1': int(data['x1']), def release_reset_event(stream_id: str) -> None:
'y1': int(data['y1']), with reset_lock:
'x2': int(data['x2']), reset_events.pop(stream_id, None)
'y2': int(data['y2'])
}
return jsonify({'status': 'success', 'line': session['counting_line']}) def get_webcam_stream_id() -> str:
stream_id = session.get('webcam_stream_id')
@app.route('/api/get_line', methods=['GET']) if not stream_id:
def get_counting_line(): stream_id = f'webcam-{uuid.uuid4().hex}'
"""API endpoint to get the current counting line coordinates""" session['webcam_stream_id'] = stream_id
if 'counting_line' not in session: return stream_id
session['counting_line'] = {'x1': 0, 'y1': 300, 'x2': 1020, 'y2': 300}
return jsonify(session['counting_line'])
def get_video_stream_id(filename: str) -> str:
@app.route('/api/reset_count', methods=['POST']) video_streams = session.get('video_stream_ids', {})
def reset_count(): stream_id = video_streams.get(filename)
"""API endpoint to reset the vehicle count (requires page reload to take effect)""" if not stream_id:
# Note: Count reset requires reloading the video stream stream_id = f'video-{uuid.uuid4().hex}'
# The count is stored in the generator function's local variables video_streams[filename] = stream_id
return jsonify({'status': 'success', 'message': 'Please reload the page to reset the count'}) session['video_stream_ids'] = video_streams
return stream_id
def detect_objects_from_webcam(line_data):
count = 0 # Helper function to check if two line segments intersect
cap = cv2.VideoCapture(0) # 0 for the default webcam def line_intersect(p1, p2, p3, p4):
"""
# Track vehicle positions and counted IDs Check if line segment p1-p2 intersects with line segment p3-p4
track_positions = {} # {track_id: (center_x, center_y)} Returns True if they intersect, False otherwise
counted_ids = set() """
vehicle_count = 0 x1, y1 = p1
vehicle_type_counts = {'car': 0, 'truck': 0, 'bus': 0, 'motorcycle': 0} x2, y2 = p2
x3, y3 = p3
# Get line coordinates x4, y4 = p4
line_start = (line_data['x1'], line_data['y1'])
line_end = (line_data['x2'], line_data['y2']) denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if abs(denom) < 1e-10:
while True: return False
ret, frame = cap.read()
if not ret: t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom
break u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom
count += 1
if count % 2 != 0: return 0 <= t <= 1 and 0 <= u <= 1
continue
# Resize the frame to (1020, 600) def crossed_line(prev_pos, curr_pos, line_start, line_end):
frame = cv2.resize(frame, (1020, 600)) """
Check if movement from prev_pos to curr_pos crossed the line.
# Run YOLOv8 tracking on the frame (BEFORE drawing the counting line!) Uses orientation check - more robust for frame skipping.
results = model.track(frame, persist=True) """
# Check if the two line segments intersect
if results[0].boxes is not None and results[0].boxes.id is not None: if line_intersect(prev_pos, curr_pos, line_start, line_end):
boxes = results[0].boxes.xyxy.int().cpu().tolist() return True
class_ids = results[0].boxes.cls.int().cpu().tolist() return False
track_ids = results[0].boxes.id.int().cpu().tolist()
for box, class_id, track_id in zip(boxes, class_ids, track_ids): def generate_frames(capture, line_data, stream_id: str):
c = names[class_id] """
x1, y1, x2, y2 = box Shared frame generator for webcam and uploaded videos.
Handles detection, drawing overlays, and reset events.
# Calculate center point of bounding box """
center_x = (x1 + x2) // 2 track_positions = {}
center_y = (y1 + y2) // 2 counted_ids = set()
vehicle_count = 0
# Check if this is a vehicle we want to count vehicle_type_counts = fresh_vehicle_counts()
if c in VEHICLE_CLASSES: line_start = (line_data['x1'], line_data['y1'])
# If we have a previous position for this track line_end = (line_data['x2'], line_data['y2'])
if track_id in track_positions and track_id not in counted_ids: frame_idx = 0
prev_x, prev_y = track_positions[track_id] reset_event = get_reset_event(stream_id)
# Draw the movement line (orange/blue)
cv2.line(frame, (prev_x, prev_y), (center_x, center_y), (255, 100, 0), 2) try:
while True:
# Check if the vehicle crossed the counting line ret, frame = capture.read()
if crossed_line((prev_x, prev_y), (center_x, center_y), line_start, line_end): if not ret:
counted_ids.add(track_id) break
vehicle_count += 1
vehicle_type_counts[c] += 1 frame_idx += 1
# Draw visual feedback when vehicle is counted (large green circle) if frame_idx % 2 != 0:
cv2.circle(frame, (center_x, center_y), 25, (0, 255, 0), 5) continue
# Update position if reset_event.is_set():
track_positions[track_id] = (center_x, center_y) track_positions.clear()
counted_ids.clear()
# Draw bounding box and label vehicle_count = 0
box_color = (0, 255, 0) if c in VEHICLE_CLASSES else (255, 0, 0) vehicle_type_counts = fresh_vehicle_counts()
cv2.rectangle(frame, (x1, y1), (x2, y2), box_color, 2) reset_event.clear()
# Show if already counted frame = cv2.resize(frame, (1020, 600))
label = f'{track_id} - {c}' results = model.track(frame, persist=True)
if track_id in counted_ids:
label += '' if results and results[0].boxes is not None and results[0].boxes.id is not None:
cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 1) boxes = results[0].boxes.xyxy.int().cpu().tolist()
class_ids = results[0].boxes.cls.int().cpu().tolist()
# Draw center point (yellow) track_ids = results[0].boxes.id.int().cpu().tolist()
cv2.circle(frame, (center_x, center_y), 3, (0, 255, 255), -1)
for box, class_id, track_id in zip(boxes, class_ids, track_ids):
# Draw counting line AFTER YOLO detection (dashed yellow line) label_name = names[class_id]
cv2.line(frame, line_start, line_end, (0, 255, 255), 3, cv2.LINE_AA) x1, y1, x2, y2 = box
# Draw dashed effect center_x = (x1 + x2) // 2
line_length = int(np.sqrt((line_end[0] - line_start[0])**2 + (line_end[1] - line_start[1])**2)) center_y = (y1 + y2) // 2
dash_length = 20
for i in range(0, line_length, dash_length * 2): if label_name in VEHICLE_CLASSES:
t1 = i / line_length if track_id in track_positions and track_id not in counted_ids:
t2 = min((i + dash_length) / line_length, 1.0) prev_x, prev_y = track_positions[track_id]
x1 = int(line_start[0] + t1 * (line_end[0] - line_start[0])) cv2.line(frame, (prev_x, prev_y), (center_x, center_y), (255, 100, 0), 2)
y1 = int(line_start[1] + t1 * (line_end[1] - line_start[1]))
x2 = int(line_start[0] + t2 * (line_end[0] - line_start[0])) if crossed_line((prev_x, prev_y), (center_x, center_y), line_start, line_end):
y2 = int(line_start[1] + t2 * (line_end[1] - line_start[1])) counted_ids.add(track_id)
cv2.line(frame, (x1, y1), (x2, y2), (0, 0, 0), 3) vehicle_count += 1
vehicle_type_counts[label_name] += 1
# Display vehicle count with type breakdown cv2.circle(frame, (center_x, center_y), 25, (0, 255, 0), 5)
cv2.rectangle(frame, (10, 10), (350, 140), (0, 0, 0), -1)
cv2.putText(frame, f'Gesamt: {vehicle_count}', (20, 35), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) track_positions[track_id] = (center_x, center_y)
cv2.putText(frame, f'Autos: {vehicle_type_counts["car"]}', (20, 65), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1)
cv2.putText(frame, f'LKW: {vehicle_type_counts["truck"]}', (20, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1) box_color = (0, 255, 0) if label_name in VEHICLE_CLASSES else (255, 0, 0)
cv2.putText(frame, f'Busse: {vehicle_type_counts["bus"]}', (20, 115), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1) cv2.rectangle(frame, (x1, y1), (x2, y2), box_color, 2)
cv2.putText(frame, f'Motorraeder: {vehicle_type_counts["motorcycle"]}', (20, 135), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1)
label = f'{track_id} - {label_name}'
_, buffer = cv2.imencode('.jpg', frame) if track_id in counted_ids:
frame = buffer.tobytes() label += ''
cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 1)
yield (b'--frame\r\n' cv2.circle(frame, (center_x, center_y), 3, (0, 255, 255), -1)
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
cv2.line(frame, line_start, line_end, (0, 255, 255), 3, cv2.LINE_AA)
@app.route('/webcam_feed') line_length = int(np.sqrt((line_end[0] - line_start[0]) ** 2 + (line_end[1] - line_start[1]) ** 2))
def webcam_feed(): dash_length = 20
# Get line data from session before starting generator for i in range(0, max(line_length, 1), dash_length * 2):
line_data = session.get('counting_line', {'x1': 0, 'y1': 300, 'x2': 1020, 'y2': 300}) t1 = i / line_length if line_length else 0
return Response(detect_objects_from_webcam(line_data), t2 = min((i + dash_length) / line_length, 1.0) if line_length else 0
mimetype='multipart/x-mixed-replace; boundary=frame') x1_dash = int(line_start[0] + t1 * (line_end[0] - line_start[0]))
y1_dash = int(line_start[1] + t1 * (line_end[1] - line_start[1]))
@app.route('/upload', methods=['POST']) x2_dash = int(line_start[0] + t2 * (line_end[0] - line_start[0]))
def upload_video(): y2_dash = int(line_start[1] + t2 * (line_end[1] - line_start[1]))
if 'file' not in request.files: cv2.line(frame, (x1_dash, y1_dash), (x2_dash, y2_dash), (0, 0, 0), 3)
return redirect(request.url)
cv2.rectangle(frame, (10, 10), (350, 140), (0, 0, 0), -1)
file = request.files['file'] cv2.putText(frame, f'Gesamt: {vehicle_count}', (20, 35), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
if file.filename == '': cv2.putText(frame, f"Autos: {vehicle_type_counts.get('car', 0)}", (20, 65), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1)
return redirect(request.url) cv2.putText(frame, f"LKW: {vehicle_type_counts.get('truck', 0)}", (20, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1)
cv2.putText(frame, f"Busse: {vehicle_type_counts.get('bus', 0)}", (20, 115), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1)
# Save the uploaded file to the uploads folder cv2.putText(frame, f"Motorraeder: {vehicle_type_counts.get('motorcycle', 0)}", (20, 135), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1)
if not os.path.exists('uploads'):
os.makedirs('uploads') _, buffer = cv2.imencode('.jpg', frame)
frame_bytes = buffer.tobytes()
file_path = os.path.join('uploads', file.filename)
file.save(file_path) yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
# Redirect to the video playback page after upload finally:
return redirect(url_for('play_video', filename=file.filename)) capture.release()
release_reset_event(stream_id)
@app.route('/uploads/<filename>')
def play_video(filename): @app.route('/')
# Initialize default counting line in session if not set def index():
if 'counting_line' not in session: return render_template('index.html')
session['counting_line'] = {'x1': 0, 'y1': 300, 'x2': 1020, 'y2': 300}
return render_template('play_video.html', filename=filename) @app.route('/start_webcam')
def start_webcam():
@app.route('/video/<path:filename>') get_line_from_session()
def send_video(filename): stream_id = get_webcam_stream_id()
return send_from_directory('uploads', filename) return render_template('webcam.html', stream_id=stream_id)
def detect_objects_from_video(video_path, line_data): @app.route('/api/set_line', methods=['POST'])
def set_counting_line():
"""API endpoint to set the counting line coordinates"""
data = request.get_json(silent=True) or {}
try:
session['counting_line'] = {
'x1': int(data['x1']),
'y1': int(data['y1']),
'x2': int(data['x2']),
'y2': int(data['y2'])
}
except (KeyError, ValueError, TypeError):
abort(400, description='Ungültige Linienkoordinaten')
return jsonify({'status': 'success', 'line': session['counting_line']})
@app.route('/api/get_line', methods=['GET'])
def get_counting_line():
"""API endpoint to get the current counting line coordinates"""
return jsonify(get_line_from_session())
@app.route('/api/reset_count', methods=['POST'])
def reset_count():
"""API endpoint to reset the vehicle count for a stream"""
data = request.get_json(silent=True) or {}
stream_id = data.get('stream_id')
if not stream_id:
abort(400, description='stream_id ist erforderlich')
valid_ids = {session.get('webcam_stream_id')}
valid_ids.update(session.get('video_stream_ids', {}).values())
valid_ids.discard(None)
if stream_id not in valid_ids:
abort(403, description='Stream gehört nicht zur aktuellen Sitzung')
event = get_reset_event(stream_id)
event.set()
return jsonify({'status': 'success', 'message': 'Zähler wird zurückgesetzt'})
def detect_objects_from_webcam(line_data, stream_id):
cap = cv2.VideoCapture(0) # 0 for the default webcam
if not cap.isOpened():
cap.release()
raise RuntimeError('Webcam konnte nicht geöffnet werden')
return generate_frames(cap, line_data, stream_id)
@app.route('/webcam_feed')
def webcam_feed():
line_data = get_line_from_session()
stream_id = get_webcam_stream_id()
try:
generator = detect_objects_from_webcam(line_data, stream_id)
except RuntimeError as exc:
abort(503, description=str(exc))
return Response(generator,
mimetype='multipart/x-mixed-replace; boundary=frame')
@app.route('/upload', methods=['POST'])
def upload_video():
if 'file' not in request.files:
abort(400, description='Keine Datei erhalten')
file = request.files['file']
if not file or file.filename == '':
abort(400, description='Keine Datei ausgewählt')
filename = secure_filename(file.filename)
if not filename:
abort(400, description='Ungültiger Dateiname')
if not allowed_file(filename):
abort(400, description='Ungültiger Dateityp')
ensure_upload_dir()
name, ext = os.path.splitext(filename)
stored_filename = f"{name}_{uuid.uuid4().hex}{ext.lower()}"
file_path = os.path.join(UPLOAD_DIR, stored_filename)
file.save(file_path)
return redirect(url_for('play_video', filename=stored_filename))
@app.route('/uploads/<filename>')
def play_video(filename):
safe_filename = os.path.basename(filename)
if safe_filename != filename:
abort(400, description='Ungültiger Dateiname')
file_path = os.path.join(UPLOAD_DIR, safe_filename)
if not os.path.isfile(file_path):
abort(404)
get_line_from_session()
stream_id = get_video_stream_id(safe_filename)
return render_template('play_video.html', filename=safe_filename, stream_id=stream_id)
@app.route('/video/<path:filename>')
def send_video(filename):
return send_from_directory(UPLOAD_DIR, filename)
def detect_objects_from_video(video_path, line_data, stream_id):
cap = cv2.VideoCapture(video_path) cap = cv2.VideoCapture(video_path)
count = 0 if not cap.isOpened():
cap.release()
raise RuntimeError('Video konnte nicht geöffnet werden')
return generate_frames(cap, line_data, stream_id)
# Track vehicle positions and counted IDs @app.route('/video_feed/<filename>')
track_positions = {} # {track_id: (center_x, center_y)} def video_feed(filename):
counted_ids = set() safe_filename = os.path.basename(filename)
vehicle_count = 0 if safe_filename != filename:
vehicle_type_counts = {'car': 0, 'truck': 0, 'bus': 0, 'motorcycle': 0} abort(400, description='Ungültiger Dateiname')
video_path = os.path.join(UPLOAD_DIR, safe_filename)
# Get line coordinates if not os.path.isfile(video_path):
line_start = (line_data['x1'], line_data['y1']) abort(404)
line_end = (line_data['x2'], line_data['y2']) line_data = get_line_from_session()
stream_id = get_video_stream_id(safe_filename)
while cap.isOpened(): try:
ret, frame = cap.read() generator = detect_objects_from_video(video_path, line_data, stream_id)
if not ret: except RuntimeError as exc:
break abort(503, description=str(exc))
count += 1 return Response(generator,
if count % 2 != 0: mimetype='multipart/x-mixed-replace; boundary=frame')
continue
if __name__ == '__main__':
# Resize the frame to (1020, 600) app.run('0.0.0.0',debug=False, port=8080)
frame = cv2.resize(frame, (1020, 600))
# Run YOLOv8 tracking on the frame (BEFORE drawing the counting line!)
results = model.track(frame, persist=True)
if results[0].boxes is not None and results[0].boxes.id is not None:
boxes = results[0].boxes.xyxy.int().cpu().tolist()
class_ids = results[0].boxes.cls.int().cpu().tolist()
track_ids = results[0].boxes.id.int().cpu().tolist()
for box, class_id, track_id in zip(boxes, class_ids, track_ids):
c = names[class_id]
x1, y1, x2, y2 = box
# Calculate center point of bounding box
center_x = (x1 + x2) // 2
center_y = (y1 + y2) // 2
# Check if this is a vehicle we want to count
if c in VEHICLE_CLASSES:
# If we have a previous position for this track
if track_id in track_positions and track_id not in counted_ids:
prev_x, prev_y = track_positions[track_id]
# Draw the movement line (orange/blue)
cv2.line(frame, (prev_x, prev_y), (center_x, center_y), (255, 100, 0), 2)
# Check if the vehicle crossed the counting line
if crossed_line((prev_x, prev_y), (center_x, center_y), line_start, line_end):
counted_ids.add(track_id)
vehicle_count += 1
vehicle_type_counts[c] += 1
# Draw visual feedback when vehicle is counted (large green circle)
cv2.circle(frame, (center_x, center_y), 25, (0, 255, 0), 5)
# Update position
track_positions[track_id] = (center_x, center_y)
# Draw bounding box and label
box_color = (0, 255, 0) if c in VEHICLE_CLASSES else (255, 0, 0)
cv2.rectangle(frame, (x1, y1), (x2, y2), box_color, 2)
# Show if already counted
label = f'{track_id} - {c}'
if track_id in counted_ids:
label += ''
cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 1)
# Draw center point (yellow)
cv2.circle(frame, (center_x, center_y), 3, (0, 255, 255), -1)
# Draw counting line AFTER YOLO detection (dashed yellow line)
cv2.line(frame, line_start, line_end, (0, 255, 255), 3, cv2.LINE_AA)
# Draw dashed effect
line_length = int(np.sqrt((line_end[0] - line_start[0])**2 + (line_end[1] - line_start[1])**2))
dash_length = 20
for i in range(0, line_length, dash_length * 2):
t1 = i / line_length
t2 = min((i + dash_length) / line_length, 1.0)
x1 = int(line_start[0] + t1 * (line_end[0] - line_start[0]))
y1 = int(line_start[1] + t1 * (line_end[1] - line_start[1]))
x2 = int(line_start[0] + t2 * (line_end[0] - line_start[0]))
y2 = int(line_start[1] + t2 * (line_end[1] - line_start[1]))
cv2.line(frame, (x1, y1), (x2, y2), (0, 0, 0), 3)
# Display vehicle count with type breakdown
cv2.rectangle(frame, (10, 10), (350, 140), (0, 0, 0), -1)
cv2.putText(frame, f'Gesamt: {vehicle_count}', (20, 35), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.putText(frame, f'Autos: {vehicle_type_counts["car"]}', (20, 65), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1)
cv2.putText(frame, f'LKW: {vehicle_type_counts["truck"]}', (20, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1)
cv2.putText(frame, f'Busse: {vehicle_type_counts["bus"]}', (20, 115), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1)
cv2.putText(frame, f'Motorraeder: {vehicle_type_counts["motorcycle"]}', (20, 135), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1)
_, buffer = cv2.imencode('.jpg', frame)
frame = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
@app.route('/video_feed/<filename>')
def video_feed(filename):
video_path = os.path.join('uploads', filename)
# Get line data from session before starting generator
line_data = session.get('counting_line', {'x1': 0, 'y1': 300, 'x2': 1020, 'y2': 300})
return Response(detect_objects_from_video(video_path, line_data),
mimetype='multipart/x-mixed-replace; boundary=frame')
if __name__ == '__main__':
app.run('0.0.0.0',debug=False, port=8080)

View File

@@ -99,8 +99,9 @@
<a href="/">Back to Home</a> <a href="/">Back to Home</a>
<script> <script>
const canvas = document.getElementById('lineCanvas'); const streamId = "{{ stream_id | default('') }}";
const ctx = canvas.getContext('2d'); const canvas = document.getElementById('lineCanvas');
const ctx = canvas.getContext('2d');
const setLineBtn = document.getElementById('setLineBtn'); const setLineBtn = document.getElementById('setLineBtn');
const resetCountBtn = document.getElementById('resetCountBtn'); const resetCountBtn = document.getElementById('resetCountBtn');
const infoText = document.getElementById('infoText'); const infoText = document.getElementById('infoText');
@@ -173,20 +174,28 @@
} }
}); });
resetCountBtn.addEventListener('click', () => { resetCountBtn.addEventListener('click', () => {
if (confirm('Zähler zurücksetzen? Die Seite wird neu geladen.')) { if (confirm('Zähler zurücksetzen?')) {
fetch('/api/reset_count', { fetch('/api/reset_count', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' },
}) body: JSON.stringify({ stream_id: streamId })
.then(res => res.json()) })
.then(data => { .then(res => {
console.log('Count reset:', data); if (!res.ok) {
// Reload page to reset the video stream and counter throw new Error('Zurücksetzen fehlgeschlagen');
location.reload(); }
}); return res.json();
} })
}); .then(() => {
infoText.textContent = 'Zähler wird zurückgesetzt...';
setTimeout(() => {
infoText.textContent = 'Klicke auf "Zähllinie setzen" und dann zweimal auf das Video, um die Zähllinie zu definieren.';
}, 2000);
})
.catch(err => alert(err.message));
}
});
function drawLine() { function drawLine() {
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);

View File

@@ -99,8 +99,9 @@
<a href="/">Back to Home</a> <a href="/">Back to Home</a>
<script> <script>
const canvas = document.getElementById('lineCanvas'); const streamId = "{{ stream_id | default('') }}";
const ctx = canvas.getContext('2d'); const canvas = document.getElementById('lineCanvas');
const ctx = canvas.getContext('2d');
const setLineBtn = document.getElementById('setLineBtn'); const setLineBtn = document.getElementById('setLineBtn');
const resetCountBtn = document.getElementById('resetCountBtn'); const resetCountBtn = document.getElementById('resetCountBtn');
const infoText = document.getElementById('infoText'); const infoText = document.getElementById('infoText');
@@ -173,20 +174,28 @@
} }
}); });
resetCountBtn.addEventListener('click', () => { resetCountBtn.addEventListener('click', () => {
if (confirm('Zähler zurücksetzen? Die Seite wird neu geladen.')) { if (confirm('Zähler zurücksetzen?')) {
fetch('/api/reset_count', { fetch('/api/reset_count', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' },
}) body: JSON.stringify({ stream_id: streamId })
.then(res => res.json()) })
.then(data => { .then(res => {
console.log('Count reset:', data); if (!res.ok) {
// Reload page to reset the video stream and counter throw new Error('Zurücksetzen fehlgeschlagen');
location.reload(); }
}); return res.json();
} })
}); .then(() => {
infoText.textContent = 'Zähler wird zurückgesetzt...';
setTimeout(() => {
infoText.textContent = 'Klicke auf "Zähllinie setzen" und dann zweimal auf das Video, um die Zähllinie zu definieren.';
}, 2000);
})
.catch(err => alert(err.message));
}
});
function drawLine() { function drawLine() {
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);