Rotating a normal map vector #982
Closed
ghost
started this conversation in
Show and tell
Replies: 3 comments 1 reply
-
Can you pls reformulate what is exactly that you try to achieve? |
Beta Was this translation helpful? Give feedback.
1 reply
-
Photometric stereo script[1] https://github.com/NamanMakkar/Stereo from argparse import ArgumentParser
from concurrent.futures import ThreadPoolExecutor
import cv2
from glob import glob
import numpy as np
from os import chdir
import sys
def loadImage(image):
return np.float32(cv2.imread(image, cv2.IMREAD_UNCHANGED))
def photometricStereo(lights, images):
height, width, channel = images[0].shape
albedo = np.zeros((height, width, channel), dtype=np.float32)
normals = np.zeros((height, width, 3), dtype=np.float32)
images_np = np.array(images)
G_first_term = np.linalg.inv(lights.T @ lights)
I = images_np.reshape(len(images), height, width, channel)
G = np.tensordot(G_first_term, np.tensordot(lights, I, axes=([0], [0])), axes=([1], [0]))
k_d = np.linalg.norm(G, axis=0)
mask = k_d < 1e-7
albedo[mask] = 0
k_d[mask] = np.inf
normals = G / k_d[np.newaxis, :, :, :]
normals[:, k_d == np.inf] = 0
normals = np.transpose(normals[:, :, :, 0], (1, 2, 0))
albedo[~mask] = k_d[~mask]
return albedo, normals
if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument('--input', type=str, required=True, help='path to the image directory')
parser.add_argument('--extension', type=str, default='png', help='image extension')
parser.add_argument('--lights', type=str, required=True, help='path to the lights file')
parser.add_argument('--albedo_output', type=str, default='albedo.png', help='path to the output image file')
parser.add_argument('--normals_output', type=str, default='normals.png', help='path to the output image file')
args = parser.parse_args()
chdir(args.input)
lights = np.loadtxt(args.lights)
imageList = glob(f'*.{args.extension}')
listRemove = ['mask', 'albedo', 'normals']
for fileName in listRemove:
if f'{fileName}.{args.extension}' in imageList:
imageList.remove(f'{fileName}.{args.extension}')
if len(imageList) != lights.shape[0]:
print('Error: The number of light vectors does not match the number of input images.')
sys.exit()
image = cv2.imread(imageList[0], cv2.IMREAD_UNCHANGED)
bitDepth = 65535 if image.dtype == 'uint16' else 255
with ThreadPoolExecutor() as executor:
images = [result for result in executor.map(loadImage, imageList)]
albedo, normals = photometricStereo(lights, images)
albedo = np.clip(albedo, 0, bitDepth).astype(image.dtype)
normals = cv2.cvtColor(((normals + 1) * 0.5 * bitDepth).astype(image.dtype), cv2.COLOR_RGB2BGR)
print(f'Saving albedo to {args.albedo_output}')
cv2.imwrite(args.albedo_output, albedo)
print(f'Saving normals to {args.normals_output}')
cv2.imwrite(args.normals_output, normals) |
Beta Was this translation helpful? Give feedback.
0 replies
-
Light calibration scriptfrom argparse import ArgumentParser
from concurrent.futures import ThreadPoolExecutor
import cv2
from glob import glob
import numpy as np
from os import chdir
def spherePos(image):
a = np.where(image == image.max())
x1, y1, x2, y2 = np.min(a[1]), np.min(a[0]), np.max(a[1]), np.max(a[0])
radius = [(x2 - x1) * 0.5, (y2 - y1) * 0.5]
center = [x2 - radius[0], y2 - radius[1]]
return radius, center
def imageProcess(image):
image = cv2.imread(image, 0)
mask = cv2.imread('mask.png', 0)
imageMasked = cv2.bitwise_and(image,image, mask=mask)
sR, sC = spherePos(mask)
lR, lC = spherePos(imageMasked)
X, Y = np.subtract(lC, sC) * [1, -1] / sR
Z = np.sqrt(1 - pow(X, 2) - pow(Y, 2))
return [X, Y, Z]
if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument('--input', type=str, required=True, help='path to the image directory')
parser.add_argument('--extension', type=str, default='png', help='image extension')
parser.add_argument('--lights_output', type=str, default='lights.txt', help='path to the output text file')
args = parser.parse_args()
chdir(args.input)
imageList = glob(f'*.{args.extension}')
listRemove = ['mask', 'albedo', 'normals']
for fileName in listRemove:
if f'{fileName}.{args.extension}' in imageList:
imageList.remove(f'{fileName}.{args.extension}')
with ThreadPoolExecutor() as executor:
L = [result for result in executor.map(imageProcess, imageList)]
print(f'Saving lights to {args.lights_output}')
with open(args.lights_output, 'w') as file:
for X, Y, Z in L:
file.write(f'{X} {Y} {Z}\n') |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
The idea was to automate the transformation of camera space normal maps from a simple photometric stereo rig to object space, by utilizing the rotation matrix from a pose to rotate a vector encoded in RGB. The object space normal maps could then be projected onto the mesh and converted to tangent space later on. ÙwÓ
Example script 0
Example script 1
Beta Was this translation helpful? Give feedback.
All reactions