-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathphotometric_stereo.py
53 lines (42 loc) · 1.81 KB
/
photometric_stereo.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import numpy as np
from scipy.linalg import pinv, norm
from vector_utils import normalise_vector
def photometric_stereo(images, lights):
"""
Images is a 3 or more channel image representing the same object taken
under different lighting conditions. Lights is a n_channels x 3 matrix that
represents the direction from which each image is lit.
Only the masked pixels are recovered.
Parameters
----------
images : (M, N, C) :class:`pybug.image.MaskedNDImage`
An image where each channel is an image lit under a unique lighting
direction.
lights : (C, 3) ndarray
A matrix representing the light directions for each of the channels in
``images``.
Returns
-------
normal_image : (M, N, 3) :class:`pybug.image.MaskedNDImage`
A 3-channel image representing the components of the recovered normals.
albedo_image : (M, N, 1) :class:`pybug.image.MaskedNDImage`
A 1-channel image representing the albedo at each pixel.
"""
# Ensure the light are unit vectors
lights = normalise_vector(lights)
LL = pinv(lights)
# n_masked_pixels x n_channels
pixels = images.as_vector(keep_channels=True)
n_images = pixels.shape[1]
if n_images < 3:
raise ValueError('Photometric Stereo is undefined with less than 3 '
'input images.')
if LL.shape[1] != n_images:
raise ValueError('You must provide a light direction for each input '
'channel.')
normals = np.dot(pixels, LL.T)
magnitudes = np.sqrt((normals * normals).sum(axis=1))
albedo = magnitudes
normals[magnitudes != 0.0, :] /= magnitudes[magnitudes != 0.0][..., None]
return (images.from_vector(normals, n_channels=3),
images.from_vector(albedo, n_channels=1))