From 929b738bcc1cf0cc68d49fb0863f1de575f73938 Mon Sep 17 00:00:00 2001 From: ChK Date: Thu, 19 Mar 2026 20:30:27 +0100 Subject: [PATCH] Recognizion with multiple --- programs/readTwoImages.py | 11 +++-- programs/screenShot.js | 61 +++++++++++++------------- programs/videoServer.js | 90 +++++++++++++++++++-------------------- 3 files changed, 83 insertions(+), 79 deletions(-) diff --git a/programs/readTwoImages.py b/programs/readTwoImages.py index cda3198..748a6f6 100755 --- a/programs/readTwoImages.py +++ b/programs/readTwoImages.py @@ -324,7 +324,12 @@ def main(): ids1 = ids1.flatten().tolist() ids2 = ids2.flatten().tolist() - + # Neu: merken, welche Kamera welchen Marker gesehen hat + seen_by = {} # id -> 1, 2 oder 3 (3 = beide) + for mid in ids1: + seen_by[mid] = seen_by.get(mid, 0) | 1 + for mid in ids2: + seen_by[mid] = seen_by.get(mid, 0) | 2 # Build dicts: id -> corners, center, rvec/tvec (per-camera PnP) def build_marker_dict(img, corners_list, ids, K, D, draw = False) -> Tuple[Dict[int,np.ndarray], Dict[int,np.ndarray], Dict[int,Tuple[np.ndarray,np.ndarray]]]: @@ -411,7 +416,7 @@ def main(): # Collect markers seen by at least one camera all_ids = set(ids1) | set(ids2) # Output structures - rows = [("id", "x_mm", "y_mm", "z_mm", "roll_deg", "pitch_deg", "yaw_deg")] + rows = [("id", "x_mm", "y_mm", "z_mm", "roll_deg", "pitch_deg", "yaw_deg", "seen_by")] marker_list = [] # Camera orientations in Euler (ZYX) @@ -473,7 +478,7 @@ def main(): continue roll, pitch, yaw = orientation_in_machine(mid) x_mm, y_mm, z_mm = (X_mach * 1000.0).tolist() - rows.append((mid, f"{x_mm:.2f}", f"{y_mm:.2f}", f"{z_mm:.2f}", f"{roll:.3f}", f"{pitch:.3f}", f"{yaw:.3f}")) + rows.append((mid, f"{x_mm:.2f}", f"{y_mm:.2f}", f"{z_mm:.2f}", f"{roll:.3f}", f"{pitch:.3f}", f"{yaw:.3f}", seen_by.get(mid, 0))) marker_list.append({ "id": int(mid), "position_mm": [float(x_mm), float(y_mm), float(z_mm)], diff --git a/programs/screenShot.js b/programs/screenShot.js index 39aecc4..7702b1d 100755 --- a/programs/screenShot.js +++ b/programs/screenShot.js @@ -4,32 +4,7 @@ const { exec } = require('child_process'); const { logSnapshot } = require('./log'); -function snapshot(outDir, cam0, cam1, ws){ - - if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); - const picDate = Date.now(); - var name0 = `snapshot_video0_${picDate}.jpg`; - var name1 = `snapshot_video1_${picDate}.jpg`; - cam0.snapshot(path.join(outDir, name0)); - cam1.snapshot(path.join(outDir, name1)); - - - console.log('Taking snapshot from cam0 async'); - (async () => { - try { - console.log('Taking snapshot from cam1 a…'); - var name1 = `snapshot_video1a_${picDate}.jpg`; - await cam1.snapshotHighRes(path.join(outDir, name1)); - console.log('Snapshot gespeichert:', name1); - } catch (err) { - console.error('Snapshot fehlgeschlagen:', err); - } - })(); - - - - strFile0 = path.join(outDir, name0); - strFile1 = path.join(outDir, name1); +function processPython(command, name0, name1, outDir, ws) { const relUrl = `/snapshots/${name0}`; const relUrlApp = `/snapshots/${name0.replace('.jpg','_two_cam_annotated.jpg')}`; @@ -42,11 +17,6 @@ function snapshot(outDir, cam0, cam1, ws){ const csvPath = path.join(outDir, name0.replace('.jpg','_two_cam.csv')); - - //const command = `python3 /usr/src/app/programs/readTwoImages.py -i ${strFile0} -i ${strFile1} -npz /usr/src/app/data/settings/callibration_cam0.npz -npz /usr/src/app/data/settings/callibration_cam1.npz -settings /usr/src/app/data/settings/settings.json`; - const command = `python3 /usr/src/app/programs/readTwoImages.py -i ${strFile0} -i ${strFile1} -npz /usr/src/app/data/settings/callibration_cam0.npz -npz /usr/src/app/data/settings/callibration_cam1.npz -settings /usr/src/app/data/settings/settings1m.json`; - console.log("Executing Python " + command); - // Run the Python post-processing and send the snapshot response only // after the annotated files are present to avoid transient 404s in the browser. exec(command, (error, stdout, stderr) => { @@ -143,6 +113,35 @@ function snapshot(outDir, cam0, cam1, ws){ } }); +} + +function snapshot(outDir, cam0, cam1, ws){ + + if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); + const picDate = Date.now(); + var name0 = `snapshot_video0_${picDate}.jpg`; + var name1 = `snapshot_video1_${picDate}.jpg`; + + + console.log('Taking snapshot from cam0 async'); + (async () => { + try { + console.log('Taking snapshot from cam1 a…'); + await cam0.snapshotHighRes(path.join(outDir, name0)); + await cam1.snapshotHighRes(path.join(outDir, name1)); + console.log('Snapshot gespeichert:', name1); + + strFile0 = path.join(outDir, name0); + strFile1 = path.join(outDir, name1); + + //const command = `python3 /usr/src/app/programs/readTwoImages.py -i ${strFile0} -i ${strFile1} -npz /usr/src/app/data/settings/callibration_cam0.npz -npz /usr/src/app/data/settings/callibration_cam1.npz -settings /usr/src/app/data/settings/settings.json`; + const command = `python3 /usr/src/app/programs/readTwoImages.py -i ${strFile0} -i ${strFile1} -npz /usr/src/app/data/settings/callibration_cam0.npz -npz /usr/src/app/data/settings/callibration_cam1.npz -settings /usr/src/app/data/settings/settings1m.json`; + console.log("Executing Python " + command); + processPython(command, name0, name1, outDir, ws); + } catch (err) { + console.error('Snapshot fehlgeschlagen:', err); + } + })(); } diff --git a/programs/videoServer.js b/programs/videoServer.js index a991656..05b6889 100755 --- a/programs/videoServer.js +++ b/programs/videoServer.js @@ -76,6 +76,9 @@ class FFmpegStreamer { this._quickFailCount = 0; this._quickFailLimit = 6; this._suspendedUntil = 0; + + this.latestHighResFrame = null; + this.hiSplitter = null; } get running() { return !!this.proc; } @@ -111,6 +114,30 @@ class FFmpegStreamer { args.push('-vsync', 'passthrough', '-c:v', 'copy', '-f', 'mjpeg', 'pipe:1'); return args; } + +if (scaling) { + const w = Number(this.opts.width), h = Number(this.opts.height); + const mjpegInput = (inFmt === 'mjpeg'); // nur wenn Input schon MJPEG ist + + // Filtergraph: vor dem Scale splitten + // - [lo] wird skaliert für den Stream + // - [hi] bleibt unskaliert (High-Res), stark gedrosselt, mit FIFO damit nichts blockiert + args.push( + '-filter_complex', `[0:v]split=2[hi][lo];[lo]scale=${w}:${h}:flags=fast_bilinear,setsar=1[vlo];[hi]fps=0.5,fifo[vhi]`, + // Low-Res Stream -> stdout (pipe:1), hier mjpeg encoden + '-map', '[vlo]', + '-pix_fmt', 'yuvj422p', + ...(outFps ? ['-r', String(outFps)] : []), + '-f', 'mjpeg', '-q:v', String(quality), 'pipe:1', + // High-Res -> pipe:3 + '-map', '[vhi]', + // Wenn Input bereits MJPEG ist, können wir kopieren (kein Re-Encode!): + ...(mjpegInput ? ['-c:v', 'copy'] : ['-pix_fmt', 'yuvj422p', '-q:v', '2']), + '-f', 'mjpeg', 'pipe:3' + ); + return args; +} + if (scaling) { args.push('-vf', `scale=${Number(this.opts.width)}:${Number(this.opts.height)}`); args.push('-pix_fmt', 'yuvj422p'); // für mjpeg-Encoder robust @@ -139,7 +166,7 @@ class FFmpegStreamer { console.log(`[FFmpeg] Start ${this.devicePath} (${this.name}) :: ${args.join(' ')}`); this._stderrBuf = []; - this.proc = spawn('ffmpeg', args, { stdio: ['ignore', 'pipe', 'pipe'], detached: true }); + this.proc = spawn('ffmpeg', args, { stdio: ['ignore', 'pipe', 'pipe', 'pipe'], detached: true }); this.startedAt = Date.now(); this.splitter = new JpegFrameSplitter((frame) => { @@ -150,6 +177,17 @@ class FFmpegStreamer { this.proc.stdout.on('data', (chunk) => this.splitter?.push(chunk)); this.proc.stderr.on('data', (d) => this._logStderr(d)); + + + // High-Res (fd=3) mit 1 FPS drainen und letzten Frame merken + if (this.proc.stdio[3]) { + this.hiSplitter = new JpegFrameSplitter((frame) => { + this.latestHighResFrame = frame; // überschreibt nur den letzten → minimaler Speicher/CPU + }); + this.proc.stdio[3].on('data', (chunk) => this.hiSplitter?.push(chunk)); + } + + this.proc.on('exit', (code, signal) => { console.warn(`[FFmpeg] ${this.devicePath} exited code=${code} sig=${signal}`); if (this._stderrBuf.length) console.warn(`[FFmpeg] ${this.name} last errors:\n - ${this._stderrBuf.join('\n - ')}`); @@ -234,52 +272,14 @@ class FFmpegStreamer { } } - /** - * Nimmt einen Snapshot in hoher Auflösung auf, unabhängig vom Stream. - * Startet kurz einen separaten ffmpeg-Prozess und speichert 1 Frame als JPEG. - * - * @param {string} toFile - Pfad zur Zieldatei (z.B. '/tmp/snap.jpg') - * @param {object} [opts] - * @param {string} [opts.size] - 'WxH' z.B. '1280x960' (Default: opts.input.size) - * @param {string} [opts.format] - z.B. 'mjpeg' | 'yuyv422' (Default: opts.input.format) - * @param {number} [opts.quality] - FFmpeg JPEG-Qualität 2..31 (kleiner = besser). Default: 2 - * @param {number} [opts.timeoutMs] - Abbruch nach ms. Default: 3000 - */ - async snapshotHighRes(toFile, { size, format, quality = 2, timeoutMs = 3000 } = {}) { - return new Promise((resolve, reject) => { - const inFmt = format ?? this.opts.input.format ?? 'mjpeg'; - const inSize = size ?? this.opts.input.size; // wenn undefined, nimmt ffmpeg die Kamera-Default - const fps = Math.min(5, this.opts.input.fps || 5); // niedrig reicht für Einzelbild - const args = [ - '-hide_banner', '-loglevel', 'error', - '-f', 'video4linux2', - ...(inFmt ? ['-input_format', String(inFmt)] : []), - ...(fps ? ['-framerate', String(fps)] : []), - ...(inSize ? ['-video_size', String(inSize)] : []), - '-i', this.devicePath, - '-frames:v', '1', - '-q:v', String(quality), - '-y', toFile, - ]; - - const p = spawn('ffmpeg', args, { stdio: ['ignore', 'ignore', 'pipe'] }); - - let stderr = ''; - const t = setTimeout(() => { - try { p.kill('SIGKILL'); } catch {} - reject(new Error(`snapshotHighRes timeout after ${timeoutMs}ms`)); - }, timeoutMs); - - p.stderr.on('data', d => { stderr += d.toString(); }); - - p.on('close', code => { - clearTimeout(t); - if (code === 0) resolve(toFile); - else reject(new Error(`ffmpeg exited ${code}: ${stderr.trim()}`)); - }); - }); + async snapshotHighRes(toFile) { + const buf = this.latestHighResFrame || this.latestFrame; + if (!buf) throw new Error('No frame available yet'); + fs.writeFileSync(toFile, buf); + return toFile; } + } module.exports = { FFmpegStreamer }; \ No newline at end of file