Recognizion with multiple
This commit is contained in:
@@ -324,7 +324,12 @@ def main():
|
|||||||
ids1 = ids1.flatten().tolist()
|
ids1 = ids1.flatten().tolist()
|
||||||
ids2 = ids2.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)
|
# 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]]]:
|
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
|
# Collect markers seen by at least one camera
|
||||||
all_ids = set(ids1) | set(ids2)
|
all_ids = set(ids1) | set(ids2)
|
||||||
# Output structures
|
# 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 = []
|
marker_list = []
|
||||||
|
|
||||||
# Camera orientations in Euler (ZYX)
|
# Camera orientations in Euler (ZYX)
|
||||||
@@ -473,7 +478,7 @@ def main():
|
|||||||
continue
|
continue
|
||||||
roll, pitch, yaw = orientation_in_machine(mid)
|
roll, pitch, yaw = orientation_in_machine(mid)
|
||||||
x_mm, y_mm, z_mm = (X_mach * 1000.0).tolist()
|
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({
|
marker_list.append({
|
||||||
"id": int(mid),
|
"id": int(mid),
|
||||||
"position_mm": [float(x_mm), float(y_mm), float(z_mm)],
|
"position_mm": [float(x_mm), float(y_mm), float(z_mm)],
|
||||||
|
|||||||
@@ -4,32 +4,7 @@ const { exec } = require('child_process');
|
|||||||
const { logSnapshot } = require('./log');
|
const { logSnapshot } = require('./log');
|
||||||
|
|
||||||
|
|
||||||
function snapshot(outDir, cam0, cam1, ws){
|
function processPython(command, name0, name1, outDir, 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);
|
|
||||||
|
|
||||||
const relUrl = `/snapshots/${name0}`;
|
const relUrl = `/snapshots/${name0}`;
|
||||||
const relUrlApp = `/snapshots/${name0.replace('.jpg','_two_cam_annotated.jpg')}`;
|
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 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
|
// Run the Python post-processing and send the snapshot response only
|
||||||
// after the annotated files are present to avoid transient 404s in the browser.
|
// after the annotated files are present to avoid transient 404s in the browser.
|
||||||
exec(command, (error, stdout, stderr) => {
|
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);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ class FFmpegStreamer {
|
|||||||
this._quickFailCount = 0;
|
this._quickFailCount = 0;
|
||||||
this._quickFailLimit = 6;
|
this._quickFailLimit = 6;
|
||||||
this._suspendedUntil = 0;
|
this._suspendedUntil = 0;
|
||||||
|
|
||||||
|
this.latestHighResFrame = null;
|
||||||
|
this.hiSplitter = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get running() { return !!this.proc; }
|
get running() { return !!this.proc; }
|
||||||
@@ -111,6 +114,30 @@ class FFmpegStreamer {
|
|||||||
args.push('-vsync', 'passthrough', '-c:v', 'copy', '-f', 'mjpeg', 'pipe:1');
|
args.push('-vsync', 'passthrough', '-c:v', 'copy', '-f', 'mjpeg', 'pipe:1');
|
||||||
return args;
|
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) {
|
if (scaling) {
|
||||||
args.push('-vf', `scale=${Number(this.opts.width)}:${Number(this.opts.height)}`);
|
args.push('-vf', `scale=${Number(this.opts.width)}:${Number(this.opts.height)}`);
|
||||||
args.push('-pix_fmt', 'yuvj422p'); // für mjpeg-Encoder robust
|
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(' ')}`);
|
console.log(`[FFmpeg] Start ${this.devicePath} (${this.name}) :: ${args.join(' ')}`);
|
||||||
|
|
||||||
this._stderrBuf = [];
|
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.startedAt = Date.now();
|
||||||
|
|
||||||
this.splitter = new JpegFrameSplitter((frame) => {
|
this.splitter = new JpegFrameSplitter((frame) => {
|
||||||
@@ -150,6 +177,17 @@ class FFmpegStreamer {
|
|||||||
this.proc.stdout.on('data', (chunk) => this.splitter?.push(chunk));
|
this.proc.stdout.on('data', (chunk) => this.splitter?.push(chunk));
|
||||||
this.proc.stderr.on('data', (d) => this._logStderr(d));
|
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) => {
|
this.proc.on('exit', (code, signal) => {
|
||||||
console.warn(`[FFmpeg] ${this.devicePath} exited code=${code} sig=${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 - ')}`);
|
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 = [
|
async snapshotHighRes(toFile) {
|
||||||
'-hide_banner', '-loglevel', 'error',
|
const buf = this.latestHighResFrame || this.latestFrame;
|
||||||
'-f', 'video4linux2',
|
if (!buf) throw new Error('No frame available yet');
|
||||||
...(inFmt ? ['-input_format', String(inFmt)] : []),
|
fs.writeFileSync(toFile, buf);
|
||||||
...(fps ? ['-framerate', String(fps)] : []),
|
return toFile;
|
||||||
...(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()}`));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { FFmpegStreamer };
|
module.exports = { FFmpegStreamer };
|
||||||
Reference in New Issue
Block a user