photometric op• Datenarten: normalmap → image2d
• Aufruf: import fullseye as fs; fs.ledger.integrate_normals(normals, mask=None) (die Implementierung direkt: import photometric; photometric.integrate_normals(normals, mask=None); aus dem Register: ops3d.get("integrate_normals"))
• GPU: nein (nur CPU-Referenzimplementierung)
Normalenfeld → Höhenfeld z per Frankot-Chellappa-Integration. → z, HxW (ein konstanter Offset bleibt frei; auf Mittelwert null bezogen).
Vorgehen: wandelt die Normalen (H,W,3) in Gradienten p = -nx/nz, q = -ny/nz um (bei Pixeln mit |nz| < 1e-6 wird nz durch ±1e-6 ersetzt, um Division durch null zu vermeiden), bestimmt im FFT-Bereich die Lösung kleinster Quadrate Z(wx,wy) = (-j·wx·P - j·wy·Q) / (wx² + wy²) (Gleichanteil 0), transformiert zurück und zieht den Mittelwert ab, sodass er 0 wird. Ist mask (bool HxW) gegeben, werden die Gradienten außerhalb der Maske vor dem Integrieren auf 0 gesetzt (der Bereich wird als eben behandelt). Rückgabewert ist float64 (H,W).
Hinweise:
• Da periodische Ränder angenommen werden, verzerren sich die Ränder, als wären oben/unten und links/rechts des Bildes verbunden (die Gesamtneigung wird zurückgegeben, siehe ★ unten).
• Die absolute Höhe ist nicht bestimmt (Mittelwert 0). Integriert wird mit einem Gitterabstand von 1 Pixel, daher ist auch z eine Höhe in Pixeleinheiten; für reale Maße mit dem Pixelabstand multiplizieren.
• Bei nz ≈ 0 (nahe Konturen, Flächen parallel zur Blickrichtung) divergiert der Gradient und verbreitet Wellen in die Umgebung. Mit mask ausschließen oder die Normalen vorher glätten.
surface_normals ist die Umkehrung; übergibt man die Normalen von photometric_stereo hierher, erhält man die Form (Höhenfeld). Leere Eingabe (normals mit 0 Elementen) löst ValueError aus.
★Gesamtneigung (2026-11-09): das Frankot-Chellappa mit periodischen Rändern setzt den Mittelwert des Gradienten (Gleichanteil) auf 0 und gab eine geneigte Ebene als waagerecht zurück (Neigung 0.2 px/px → rekonstruiert 0.0000, rms 5.5 px). Nach dem Integrieren wird der lineare Term des mittleren Gradienten mean(p)·(x − x̄) + mean(q)·(y − ȳ) wieder addiert (Mittelwert über das ganze Gitter, einschließlich der Nullen außerhalb von mask). Eine geneigte Ebene kommt exakt zurück, eine Fläche mit mittlerem Gradienten 0 (eine maskierte Kugel usw.) bewegt sich nur um Rundungsreste. Für die reine periodische Lösung integrate_gradients verwenden. ★Die zurückaddierte Neigung ist ein Mittelwert und daher anfällig für Ausreißer (unabhängige Prüfung 2026-11-09): auf einer ebenen Fläche genügen 20 Pixel stark gleichsinnig geneigter Normalen (nz 0.002, passiert den Schnitt bei 1e-3), um den rms-Fehler von 10.7 auf 27.9 px zu heben (2.6-mal schlechter). Steile Normalen auf einer Seite einer Kontur sind in realen Daten häufig; daher vorher mit mask entfernen. Ein robuster Schätzer wie der Median wird nicht verwendet, weil auf Flächen, deren wahre Neigung wenige große Gradienten tragen (wie die Wand einer Stufe), die Neigung verloren ginge. Eine weitere Grenze: die Neigung ist der Mittelwert der gemessenen Normalen; Stufenwände, die Photometric Stereo nicht sieht, fehlen darin —— bei einem Teil mit Stufen weicht die zurückaddierte Neigung vom wahren mittleren Gradienten ab (im PoC zum Vergleich der 3-D-Verfahren −0.066 bis −0.084 gegenüber wahr +0.0125 px/px).
★Eingangsprüfung: alles außer (H,W,3) und nicht-endliche Normalen (ein NaN-Pixel machte über die FFT jedes Pixel zu NaN) lösen ValueError aus. Pixel mit |nz| < 1e-3 (Gradient über 1000: Konturen, Flächen parallel zur Blickrichtung) erhalten Gradient 0 und werden aus der Integration genommen; ihre Anzahl meldet eine RuntimeWarning (ein Pixel mit nz=0 ließ die Höhe um 2.3e5 px ausschlagen).
• Katalog der Beispieldaten (Download-URLs / Lizenzen) — 2-D nutzt skimage.data (BSD/Public Domain) plus synthetische Bilder, 3-D nennt Download-URLs echter Datenquellen (Stanford, PDS, …).
• Herkunft und Literatur der Operatoren — die Quellen der Forschung/Verfahren, auf denen diese Operatorfamilie beruht.
• photometric_stereo — py -3.11 examples_3d/photometric_stereo.py
image2d als Eingabe)fuse_to_voxel · fit_poly_surface · eval_poly_surface · surface_form_error · background_flatten · polar_unwrap · fit_zernike · matcap_shade
photometric)photometric_stereo · surface_normals · render_lambertian
*Provenance: photometric.py — 3D Operator-Registry. Diese Notiz wird von tools/opdocs.py md erzeugt (nicht von Hand bearbeiten).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.