photometric op• Data kinds: normalmap → image2d
• Call: import fullseye as fs; fs.ledger.integrate_normals(normals, mask=None) (implementation को सीधे call करना हो तो import photometric; photometric.integrate_normals(normals, mask=None), registry से लेना हो तो ops3d.get("integrate_normals"))
• GPU: नहीं (केवल CPU reference implementation)
normal field → height field z, Frankot-Chellappa integration से। → z HxW (एक constant की स्वतंत्रता, औसत 0 को संदर्भ)।
प्रक्रिया: normals (H,W,3) को gradients p = -nx/nz, q = -ny/nz में बदलता है (|nz| < 1e-6 वाले pixels में nz को ±1e-6 से बदलकर शून्य से भाग से बचा जाता है), FFT domain में least-squares हल Z(wx,wy) = (-j·wx·P - j·wy·Q) / (wx² + wy²) निकालता है (DC component 0), inverse transform करके mean घटाकर उसे 0 करता है। mask (bool HxW) देने पर mask के बाहर के gradients को 0 करके फिर integrate किया जाता है (उस region को flat माना जाता है)। return value float64 (H,W) है।
सावधानियाँ:
• periodic boundary माना जाता है, इसलिए किनारे ऐसे distort होते हैं मानो image के ऊपर-नीचे और बाएँ-दाएँ जुड़े हों (पूरा झुकाव नीचे ★ में वापस जोड़ा जाता है)।
• absolute height तय नहीं होती (mean 0)। 1 pixel की grid spacing से integrate किया जाता है, इसलिए z भी pixel units में height होती है; असली आकार के लिए pixel pitch से गुणा करें।
• nz ≈ 0 (contour के पास, line of sight के समानांतर surfaces) पर gradient diverge होता है और लहरें आसपास फैलती हैं। इन्हें mask से हटाएँ या normals को पहले smooth करें।
surface_normals इसका inverse है; photometric_stereo के normals यहाँ देने पर shape (height field) मिलता है। खाली input (normals में 0 elements) पर ValueError आता है।
★पूरा झुकाव (2026-11-09): periodic boundary वाला Frankot-Chellappa gradient का mean (DC) 0 कर देता है और झुके plane को समतल लौटाता था (slope 0.2 px/px → recovered 0.0000, rms 5.5 px)। integrate करने के बाद mean gradient का first-order term mean(p)·(x − x̄) + mean(q)·(y − ȳ) वापस जोड़ा जाता है (mean पूरे grid पर, mask के बाहर के 0 सहित)। झुका plane ठीक-ठीक लौटता है, और जिस surface का mean gradient 0 है (mask किया sphere आदि) वह सिर्फ़ rounding जितना हिलता है। कच्चा periodic हल चाहिए तो integrate_gradients। ★वापस जोड़ा गया झुकाव एक mean है, इसलिए outliers के प्रति कमज़ोर है (स्वतंत्र review 2026-11-09): समतल surface पर एक ही दिशा में बहुत झुके normals (nz 0.002, जो 1e-3 की सीमा पार कर लेते हैं) के सिर्फ़ 20 pixels से rms error 10.7 से 27.9 px हो जाती है (2.6 गुना बदतर)। contour के एक ओर कतार में खड़े तीखे normals असली data में आम हैं, इसलिए उन्हें mask से हटाकर दें। median जैसे robust अनुमान पर इसलिए नहीं बदला गया कि जिन surfaces पर कुछ बड़े gradients ही सही झुकाव ढोते हैं (जैसे step की दीवार), वहाँ झुकाव खो जाता। एक और सीमा: झुकाव मापे गए normals का mean है, photometric stereo जिन step की दीवारों को नहीं देख पाता वे इसमें नहीं आतीं —— steps वाले part पर जोड़ा गया झुकाव सही mean gradient से अलग होता है (3-D विधियों की तुलना वाले PoC में −0.066 से −0.084 बनाम सही +0.0125 px/px)। ★input की जाँच: (H,W,3) के अलावा कुछ भी और non-finite normals (एक NaN pixel FFT से हर pixel को NaN कर देता था) पर ValueError। |nz| < 1e-3 (gradient 1000 से ज़्यादा: contours, line of sight के समानांतर surfaces) वाले pixels का gradient 0 करके उन्हें integration से बाहर रखा जाता है, और उनकी संख्या RuntimeWarning से बताई जाती है (nz=0 वाला एक pixel height को 2.3e5 px हिला देता था)।
• Sample data catalog (DL URL / license) — 2-D के लिए skimage.data (BSD/public) + synthetic, 3-D के लिए real data sources (Stanford/PDS आदि) के DL URL।
• Operators का स्रोत और संदर्भ — इस op family के मूल research/methods के स्रोत।
• photometric_stereo — py -3.11 examples_3d/photometric_stereo.py
image2d को input के रूप में लेते हैं)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. यह per-op note tools/opdocs.py md से अपने-आप generate होता है (हाथ से edit न करें)।*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.