fullseye

黒光沢とガラスの形を偏光で測る(偏光からの形状復元・曖昧さの集合・屈折率の推定)

できること

4 方位のオンチップ偏光子を持つセンサの 1 ショットから、面の法線と高さを出します。黒い光沢の樹脂やガラスのレンズは拡散反射がほとんど無く、照明を変えても陰影が出ないので、照度差ステレオや陰影からの形状復元が拠り所を失います。けれど表面で 1 回反射した光の偏光は、面の傾きで決まっています(Fresnel の式)。

What it does

Shape from polarisation for a 4-direction on-chip polariser sensor: the Fresnel DoLP of specular reflection (exactly 1 at Brewster’s angle, two zenith solutions straddling it) and of diffuse emission (monotone, one solution), checked against the s/p reflectances of glassmirror written in a different form to 1e-14; AoLP to the two candidate azimuths (diffuse and specular 90° apart, the π ambiguity kept); the explicit candidate set of normals per pixel; disambiguation by a stated prior (convexity with Brewster-ridge segmentation, or a coarse depth / normal map); refractive-index estimation from DoLP at known zenith angles; and integration to height with a mirror-extended Frankot–Chellappa. Round trips through an independent Mueller-matrix forward path (optics.mueller_element / mueller_apply) recover analytic spheres, cylinders and rotated ellipsoids to 1e-9° (diffuse) and to 0.1° on the discrete Brewster ridge (specular). Where it breaks is measured: a wrong index, noise concentrated on the Brewster ring and at the apex, and a diffuse component mixed into a specular pixel.

向くところ / 向かないところ

最初の 1 本

import numpy as np
import fullseye as fs

# 球冠(半径 40 px の球の、像面の半径 36 px まで)の解析的な法線
yy, xx = np.mgrid[0:96, 0:96].astype(float)
x, y = (xx - 47.5) / 40.0, (yy - 47.5) / 40.0
mask = x * x + y * y < 0.9 ** 2
nz = np.sqrt(np.clip(1.0 - x * x - y * y, 0.0, 1.0))
normals = np.where(mask[..., None], np.dstack([x, y, nz]), [0.0, 0.0, 1.0])
normals /= np.linalg.norm(normals, axis=-1, keepdims=True)

sweep = fs.ledger.sfp_render(normals, 1.5, mask=mask)                 # 0/45/90/135° の 4 枚(黒光沢)
cands = fs.ledger.sfp_normals(sweep, 1.5, "specular")                 # 画素ごとに候補 4 本
est = fs.ledger.sfp_disambiguate(cands, "convex", mask=mask)          # 凸 + Brewster の稜線で 1 本
height = fs.ledger.sfp_height(est, mask=mask)
err = np.degrees(np.arccos(np.clip((est * normals).sum(-1), -1.0, 1.0)))[mask]
print("候補 %d 本、法線の誤差 最大 %.3f°、高さ %.1f px" % (cands["candidates"].shape[0], err.max(), height.max()))

裏づけ