2.4. Filter

The decoding yields the following results:

The brightness \(\hat{a}\) is a measure for the reflectance (resp. absorption) of the surface.

The modulation \(\hat{b}\) is a measure for the glossiness (resp. scattering) of the surface.

The registration \(\hat{x}\) is a mapping from camera pixels to screen positions \(\hat{x}\) (with subpixel accuracy). It contains the information where each camera pixel, i.e. each camera sight ray, looked onto the screen during the fringe pattern recording.

 1"""Decode fringe pattern sequence."""
 2
 3import fringes as frng
 4import matplotlib.pyplot as plt
 5
 6f = frng.Fringes()
 7I = f.encode()
 8
 9I_rec = I  # todo: replace this line with recording data as in 'record.py'
10
11a, b, x = f.decode(I_rec)
12
13# display first frame  and first color channel of each result
14plt.figure("registration 'x'")
15plt.imshow(x[0, :, :, 0])
16plt.colorbar()
17plt.figure("modulation 'b'")
18plt.imshow(b[0, :, :, 0])
19plt.colorbar()
20plt.figure("brightness 'a'")
21plt.imshow(a[0, :, :, 0])
22plt.colorbar()
23plt.show()

These results are in video shape, so for the brightness \(\hat{a}\) and the registration \(\hat{x}\), the usually two directions D are along the the first i.e. the time axis. For the modulation \(\hat{b}\), the modulation of the sets K of the directions D are flattened into the first dimension; you may reshape them as follows:

25T, Y, X, C = b.shape
26b = b.reshape(f.D, f.K, Y, X, C)
brightness

Fig. 2.11 Brightness.

modulation

Fig. 2.12 Modulation.

registration

Fig. 2.13 Registration.

2.4.1. Direct and Global Illumination Component

The direct illumination component is just twice the measured modulation:

\(I_D = 2 \hat{b}\).

The global illumination component can be determined with

\(I_G = 2 (\hat{a} - \hat{b})\)

under the condition that the spatial frequency \(\nu\) is high enough [Nay06].

Both can be normalized into the range [0, 1) by dividing through the maximal possible gray value \(I_{max}\) of the recording camera.

 1"""Direct and global illumination component.
 2https://dl.acm.org/doi/abs/10.1145/1179352.1141977"""
 3
 4import fringes as frng
 5import matplotlib.pyplot as plt
 6
 7f = frng.Fringes()
 8f.v = 99, 100, 101  # spatial frequency 'v' must be high enough but still resolved by the recording camera
 9
10I = f.encode()
11
12I_rec = I  # todo: replace this line with recording data as in 'record.py'
13
14res = f.decode(I_rec, verbose=True)
15
16# make use of namedtuple:
17a = res.brightness
18b = res.modulation
19x = res.registration
20d = res.direct
21g = res.glob
22
23plt.figure("global 'g'")
24plt.imshow(g[0, :, :, 0])
25plt.colorbar()
26plt.figure("direct 'd'")
27plt.imshow(d[0, :, :, 0])
28plt.colorbar()
29plt.show()
direct

Fig. 2.14 Direct illumination component.

global

Fig. 2.15 Global illumination component.

2.4.2. Visibility and Exposure

In an alternative formulation, the absolute quantities offset \(a\) and amplitude \(b\) of the phase shifting equation are replaced by the maximal possible gray value \(I_{max}\), the relative quantities exposure \(E\) (relative average intensity) and visibilty \(V\) (relative fringe contrast) [Fis12]:

\(I = a + b \cos(\varPhi) = I_{max} E [1 + V \cos(\varPhi)]\)

The two parameters \(E = \hat{a} / I_{max}\) and \(V = \hat{b_i} / \hat{a}\) describe the phase shifting signal \(I\) independently of the value range \([0, I_{max}]\) of the light source or camera. Both lie within the interval \([0, 1]\) with the additional condition \(E \le 1 / (1 + V)\); else, the radiance of the light source would be higher than the maximal possible value \(I_{max}\). Therefore, the valid values of \(V\) are limited for \(E > 0.5\). The optimal fringe contrast \(V = 1\) can be reached when \(E = 0.5\).

codomain

Fig. 2.16 Fringe pattern as a function of \(E\) and \(V\).

The advantage of this representation is the normalization of the descriptive parameters \(E\) and \(V\) and thereby the separation of additive and multiplicative influences.

The exposure \(E\) is affected by additional, constant light (not modulating the signal):

  • the maximum brightness of the light source,

  • the absorption of the sample

  • the absorption of optical elements (e.g. filters),

  • the exposure time and the aperture setting of the camera.

The visibility \(V\) of the fringes is influenced by:

  • the maximum contrast of the light source

  • the surface quality of the sample (roughness, scattering),

  • the position of the sample with regard to focal plane of the lens (defocus and depth of field),

  • the camera lens’ modulation transfer function,

  • the camera’s resolution (the camera pixel size projected onto the light source acts as a low pass filter).

 1"""Visibility and Exposure."""
 2
 3import fringes as frng
 4import matplotlib.pyplot as plt
 5
 6f = frng.Fringes()
 7I = f.encode()
 8
 9I_rec = I  # todo: replace this line with recording data as in 'record.py'
10
11res = f.decode(I_rec, verbose=True)
12
13# make use of namedtuple:
14a = res.brightness
15b = res.modulation
16x = res.registration
17E = res.exposure
18V = res.visibility
19
20plt.figure("exposure 'E'")
21plt.imshow(E[0, :, :, 0])
22plt.colorbar()
23plt.figure("visibility 'V'")
24plt.imshow(V[0, :, :, 0])
25plt.colorbar()
26plt.show()
visibility

Fig. 2.17 Visibility.

exposure

Fig. 2.18 Exposure.

2.4.3. Verbose Results

Additionally to the already mentioned results brightness, modulation, registration, visibility and exposure, more intermediate and verbose results an be returned by setting the flag verbose in the method decode() to True:

 1"""Decode verbose results."""
 2
 3import fringes as frng
 4import matplotlib.pyplot as plt
 5
 6f = frng.Fringes()
 7I = f.encode()
 8
 9I_rec = I  # todo: replace this line with recording data as in 'record.py'
10
11# decode and return additional results:
12res = f.decode(I_rec, verbose=True)
13
14# make use of namedtuple:
15a = res.brightness
16b = res.modulation
17x = res.registration
18p = res.phase
19k = res.order
20r = res.residuals
21u = res.uncertainty
22
23# display first frame of results
24plt.figure("phase 'p'")
25plt.imshow(p[0, :, :, 0])
26plt.colorbar()
27plt.figure("fringe order 'k'")
28plt.imshow(k[0, :, :, 0])
29plt.colorbar()
30plt.figure("residuals 'r'")
31plt.imshow(r[0, :, :, 0])
32plt.colorbar()
33plt.figure("uncertainty 'u'")
34plt.imshow(u[0, :, :, 0])
35plt.colorbar()
36plt.show()

The phase … after temporal demodulation … link to fundamentals

phase

Fig. 2.19 Phase.


The fringe order … spatial demodulation, i.e. unwrapping … link to fundamentals

fringe order

Fig. 2.20 Fringe order.


The residuals … after optimization-based unwrapping … link to fundamentals

residuals

Fig. 2.21 Residuals.


The uncertainty … after temporal demodulation i.e. unwrapping … best/minimal uncertainty if ui is set and correct fringe orders are found … link to fundamentals

uncertainty

Fig. 2.22 Uncertainty.

2.4.4. Slope

If the deflectometric setup is calibrated, the slope of the surface can be computed from the registration.

“[Deflectometry] measures slopes (first order derivatives of the shape). The sensitivity to higher spatial frequencies is therefore amplified, resulting in excellent (sometimes excessive) sensitivity for small-scale irregularities and at the same time poor sensitivity and stability for low-order surface features.” [Bur23]

 1"""Slope."""
 2
 3import fringes as frng
 4import matplotlib.pyplot as plt
 5
 6f = frng.Fringes()
 7
 8I = f.encode()
 9
10I_rec = I  # todo: replace this line with recording data as in 'record.py'
11
12a, b, x = f.decode(I_rec)
13
14s = x  # todo: compute slope from calibrated setup
15
16plt.figure("slope 'y'")
17plt.imshow(s[1, :, :, 0])
18plt.colorbar()
19plt.figure("slope 'x'")
20plt.imshow(s[0, :, :, 0])
21plt.colorbar()
22plt.show()
slope_x

Fig. 2.23 Slope map in x-direction. From [SAC].

slope_y

Fig. 2.24 Slope map in y-direction. From [SAC].

2.4.5. Curvature

“As an alternative use of [deflectometry] data, one may differentiate them and recover surface curvatures (combinations of second order shape derivatives […]. Unlike point positions and slopes, the latter are intrinsic local characteristics of the surface […] that are independent of its embedding in 3D space. As such, curvature maps are useful observables for various quality inspection tasks. Derivation of curvatures is less error-prone than shape integration and does not require accurate prior knowledge of the distance to the object.” [Bur23]

 1"""Curvature."""
 2
 3import fringes as frng
 4import matplotlib.pyplot as plt
 5
 6f = frng.Fringes()
 7I = f.encode()
 8
 9I_rec = I  # todo: replace this line with recording data as in 'record.py'
10
11a, b, x = f.decode(I_rec)
12
13c = frng.curvature(x)
14
15plt.figure("curvature 'c'")
16plt.imshow(c[0, :, :, 0])
17plt.colorbar()
18plt.show()
curvature

Fig. 2.25 Curvature map. From [SAC].