-
Camera NPZ offen
-
- Ziel: Intrinsische Kameraparameter (Kameramatrix, Verzerrungskoeffizienten) für jede
- Kamera ermitteln und als .npz-Datei speichern.
- Geplante Aktionen: Fotos aufnehmen (verschiedene Posen) · Kalibrierung berechnen ·
- Reprojektionsfehler anzeigen · Datei speichern.
- Aktionen werden ergänzt sobald das Konzept feststeht.
-
-
-
-
-
+
Aktuelle Kalibrierung
+
+ Timestamp
+ –
+
+ Erstellt am
+ –
+
+ Bilder / Kameras
+ –
+
+
+
Aktionen
+
+
+
+
+
+
+
+
+
Ausgabe / Log
@@ -304,7 +340,7 @@
diff --git a/server/server.js b/server/server.js
index 30c82ed..e6f8878 100755
--- a/server/server.js
+++ b/server/server.js
@@ -194,6 +194,144 @@ app.post('/api/estimate', async (req, res) => {
}
});
+// ── Kalibrierung ─────────────────────────────────────────────────────────────
+
+const calibDataDir = path.join(__dirname, '..', 'data', 'calibration');
+
+/** Timestamp-String im Format YYYYMMDD_HHmmss */
+function makeTimestamp() {
+ const now = new Date();
+ const p = (n, l = 2) => String(n).padStart(l, '0');
+ return `${now.getFullYear()}${p(now.getMonth() + 1)}${p(now.getDate())}_${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`;
+}
+
+/** Neueste Kalibrierungs-Session (Verzeichnisname) oder null */
+async function findLatestCalibSession() {
+ try {
+ await fsPromises.access(calibDataDir);
+ const entries = await fsPromises.readdir(calibDataDir, { withFileTypes: true });
+ const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort().reverse();
+ return dirs[0] ?? null;
+ } catch {
+ return null;
+ }
+}
+
+/** Liest meta.json einer Session */
+async function readCalibMeta(sessionName) {
+ try {
+ const raw = await fsPromises.readFile(path.join(calibDataDir, sessionName, 'meta.json'), 'utf8');
+ return JSON.parse(raw);
+ } catch {
+ return null;
+ }
+}
+
+/** Schreibt meta.json einer Session */
+async function writeCalibMeta(sessionName, meta) {
+ await fsPromises.writeFile(
+ path.join(calibDataDir, sessionName, 'meta.json'),
+ JSON.stringify(meta, null, 2),
+ 'utf8'
+ );
+}
+
+/**
+ * Holt Snapshots aller verfügbaren Kameras und speichert sie im Session-Verzeichnis.
+ * Dateiname: {cameraId}_{setNr}.jpg (setNr = 001, 002, …)
+ */
+async function capturePhotos(sessionName) {
+ if (!WEBCAM_URL) throw new Error('WEBCAM_URL ist nicht konfiguriert – keine Kameras erreichbar');
+
+ const wc = new WebcamClient(WEBCAM_URL);
+ const data = await wc.getCameras();
+ const cameraIds = (data.cameras ?? []).map(c => c.id);
+ if (cameraIds.length === 0) throw new Error('Keine Kameras vom WebCam-Service gemeldet');
+
+ // Nächste Set-Nummer bestimmen (höchste vorhandene + 1)
+ const sessionDir = path.join(calibDataDir, sessionName);
+ const existing = await fsPromises.readdir(sessionDir);
+ const maxSet = existing.reduce((max, f) => {
+ const m = f.match(/_(\d+)\.jpg$/);
+ return m ? Math.max(max, parseInt(m[1], 10)) : max;
+ }, 0);
+ const setNr = String(maxSet + 1).padStart(3, '0');
+
+ const savedFiles = [];
+ for (const camId of cameraIds) {
+ const response = await new WebcamClient(WEBCAM_URL).getSnapshot(camId, true);
+ const buffer = Buffer.from(await response.arrayBuffer());
+ const filename = `${camId}_${setNr}.jpg`;
+ await fsPromises.writeFile(path.join(sessionDir, filename), buffer);
+ savedFiles.push(filename);
+ }
+
+ return { cameraIds, savedFiles, setNr: parseInt(setNr, 10) };
+}
+
+/** GET /api/calibration/current — aktuelle Session-Info */
+app.get('/api/calibration/current', async (req, res) => {
+ try {
+ const session = await findLatestCalibSession();
+ if (!session) return res.json({ session: null, meta: null });
+ const meta = await readCalibMeta(session);
+ return res.json({ session, meta });
+ } catch (err) {
+ return res.status(500).json({ error: String(err) });
+ }
+});
+
+/** POST /api/calibration/new — neue Session anlegen + erste Fotos */
+app.post('/api/calibration/new', async (req, res) => {
+ try {
+ const ts = makeTimestamp();
+ const sessionDir = path.join(calibDataDir, ts);
+ await fsPromises.mkdir(sessionDir, { recursive: true });
+
+ const meta = { timestamp: ts, createdAt: new Date().toISOString(), cameras: [], imageSets: 0, imageCount: 0 };
+ await writeCalibMeta(ts, meta);
+
+ try {
+ const capture = await capturePhotos(ts);
+ meta.cameras = capture.cameraIds;
+ meta.imageSets = capture.setNr;
+ meta.imageCount = capture.setNr * capture.cameraIds.length;
+ await writeCalibMeta(ts, meta);
+ return res.json({ session: ts, meta, savedFiles: capture.savedFiles });
+ } catch (captureErr) {
+ // Session angelegt, aber Fotos nicht verfügbar → trotzdem OK zurück
+ return res.json({ session: ts, meta, warning: String(captureErr) });
+ }
+ } catch (err) {
+ return res.status(500).json({ error: String(err) });
+ }
+});
+
+/** POST /api/calibration/foto — weitere Fotos für aktuelle Session */
+app.post('/api/calibration/foto', async (req, res) => {
+ try {
+ const session = await findLatestCalibSession();
+ if (!session) {
+ return res.status(400).json({ error: 'Keine Session vorhanden. Bitte zuerst "Neue Kalibrierung anlegen".' });
+ }
+
+ const capture = await capturePhotos(session);
+
+ const meta = await readCalibMeta(session) ?? {
+ timestamp: session, createdAt: new Date().toISOString(),
+ cameras: [], imageSets: 0, imageCount: 0
+ };
+ meta.cameras = capture.cameraIds;
+ meta.imageSets = capture.setNr;
+ meta.imageCount = capture.setNr * capture.cameraIds.length;
+ await writeCalibMeta(session, meta);
+
+ return res.json({ session, meta, savedFiles: capture.savedFiles });
+ } catch (err) {
+ return res.status(500).json({ error: String(err) });
+ }
+});
+
async function checkServiceReachability(name, url) {
try {
const controller = new AbortController();