Webcam: Buttons fuer Kamera-Aufloesung (VGA/SVGA)
Schaltet die ESP32-CAM-Aufloesung per Button um (VGA 640x480 = mehr FPS, SVGA 800x600 = mehr Detail). Server proxyt den /control-Aufruf an die Kamera (Host automatisch aus CAMERA_URL, Port 80). - /api/cam_framesize (Whitelist 10=VGA, 11=SVGA) - zwei Buttons + Status-Feedback im Frontend Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
33
app.py
33
app.py
@@ -6,6 +6,7 @@ import threading
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
from threading import Event, Lock, Condition
|
from threading import Event, Lock, Condition
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -839,6 +840,38 @@ def grabber_toggle():
|
|||||||
return jsonify({"paused": paused})
|
return jsonify({"paused": paused})
|
||||||
|
|
||||||
|
|
||||||
|
# Erlaubte framesize-Werte der ESP32-CAM (OV2640): Wert -> Label.
|
||||||
|
FRAMESIZES = {10: "VGA 640x480", 11: "SVGA 800x600"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/cam_framesize", methods=["POST"])
|
||||||
|
def cam_framesize():
|
||||||
|
"""Setzt die Aufloesung der ESP32-CAM ueber deren /control-Endpoint.
|
||||||
|
Der Server proxyt den Aufruf (Browser braucht keinen Direktzugriff)."""
|
||||||
|
data = request.get_json(silent=True) or {}
|
||||||
|
try:
|
||||||
|
val = int(data.get("val"))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
abort(400, description="val (int) erforderlich")
|
||||||
|
if val not in FRAMESIZES:
|
||||||
|
abort(400, description="nur 10 (VGA) oder 11 (SVGA)")
|
||||||
|
|
||||||
|
host = urlparse(CAMERA_URL).hostname
|
||||||
|
if not host:
|
||||||
|
abort(500, description="CAMERA_URL ohne gueltigen Host")
|
||||||
|
|
||||||
|
# Steuer-Endpoint liegt auf Port 80 (Stream laeuft separat auf :81).
|
||||||
|
ctrl_url = f"http://{host}/control?var=framesize&val={val}"
|
||||||
|
try:
|
||||||
|
r = requests.get(ctrl_url, timeout=5)
|
||||||
|
r.raise_for_status()
|
||||||
|
except requests.RequestException as exc:
|
||||||
|
print(f"[cam] framesize set failed: {exc}", flush=True)
|
||||||
|
return jsonify({"ok": False, "error": str(exc)}), 502
|
||||||
|
print(f"[cam] framesize -> {FRAMESIZES[val]}", flush=True)
|
||||||
|
return jsonify({"ok": True, "val": val, "label": FRAMESIZES[val]})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/reset_count", methods=["POST"])
|
@app.route("/api/reset_count", methods=["POST"])
|
||||||
def reset_count():
|
def reset_count():
|
||||||
data = request.get_json(silent=True) or {}
|
data = request.get_json(silent=True) or {}
|
||||||
|
|||||||
@@ -153,6 +153,8 @@
|
|||||||
<button id="setLineBtn">Zähllinie setzen</button>
|
<button id="setLineBtn">Zähllinie setzen</button>
|
||||||
<button id="resetCountBtn" class="danger">Zähler zurücksetzen</button>
|
<button id="resetCountBtn" class="danger">Zähler zurücksetzen</button>
|
||||||
<button id="grabberBtn">Kamera freigeben</button>
|
<button id="grabberBtn">Kamera freigeben</button>
|
||||||
|
<button id="resVgaBtn" title="640×480 – mehr FPS">VGA</button>
|
||||||
|
<button id="resSvgaBtn" title="800×600 – mehr Detail">SVGA</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="info" id="infoText">Klicke auf "Zähllinie setzen" und dann zweimal auf das Video, um die Zähllinie zu definieren.</div>
|
<div class="info" id="infoText">Klicke auf "Zähllinie setzen" und dann zweimal auf das Video, um die Zähllinie zu definieren.</div>
|
||||||
<a href="/">Back to Home</a>
|
<a href="/">Back to Home</a>
|
||||||
@@ -334,6 +336,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setFramesize(val, label) {
|
||||||
|
infoText.textContent = `Setze Auflösung auf ${label}…`;
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/cam_framesize', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ val })
|
||||||
|
});
|
||||||
|
const d = await r.json();
|
||||||
|
infoText.textContent = d.ok
|
||||||
|
? `Auflösung auf ${label} gesetzt`
|
||||||
|
: `Fehler: ${d.error || 'Kamera nicht erreichbar'}`;
|
||||||
|
} catch (e) {
|
||||||
|
infoText.textContent = 'Fehler beim Setzen der Auflösung';
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
infoText.textContent = 'Klicke auf "Zähllinie setzen" und dann zweimal auf das Video, um die Zähllinie zu definieren.';
|
||||||
|
}, 2500);
|
||||||
|
}
|
||||||
|
document.getElementById('resVgaBtn').addEventListener('click', () => setFramesize(10, 'VGA 640×480'));
|
||||||
|
document.getElementById('resSvgaBtn').addEventListener('click', () => setFramesize(11, 'SVGA 800×600'));
|
||||||
|
|
||||||
grabberBtn.addEventListener('click', async () => {
|
grabberBtn.addEventListener('click', async () => {
|
||||||
grabberBtn.disabled = true;
|
grabberBtn.disabled = true;
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user