I render surface Normals using Cycles and store the results in EXR format. The values in the EXR format are within the range of [-1, 1] meaning that I cannot directly store the values in a PNG file. Here's how I read the .exr files and store store the values in a numpy array:

import OpenEXR, array, Imath
exrFile = OpenEXR.InputFile('normal.exr')
FLOAT = Imath.PixelType(Imath.PixelType.FLOAT)
(RGB) = [array.array('f', exrFile.channel(Chan, FLOAT)).tolist() for Chan in ("R", "G", "B") ]
normalNPArray = np.array(RGB)
normalNPArray = normalNPArray.reshape((3, resolution, resolution))

I normalize the values as follow and store the surface Normal as a PNG image:

normalNPArray += 1
normalNPArray /= 2
normalNPArray *= 255
normalNPArray = normalNPArray.astype(np.uint8)
im = Image.fromarray(normalNPArray.transpose(1, 2, 0), mode='RGB')

Here's what I get after opening the stored image: enter image description here

If I simply ignore the values below zero (remove/comment the first two lines) I get the following: enter image description here

However, if I directly store the surface Normal as a PNG image instead of EXR I get the following:

enter image description here

I wonder, how can I get a visualization that looks like the PNG rendering? How does Blender normalize the values?


It turns out Numpy does not automatically set the negative values to 0 when casting the array type to uint8. For instance, suppose I have a numpy array as follow:

array([  46.73017823, -250.31618571, -247.44416527,   97.52841554,
       -204.92988386,  191.09452493,  103.15708521,  -86.10470495,
         46.211924  , -195.30653599,  240.44499889, -169.42729244,
        210.44996545, -182.04892973, -166.20581924, -221.11524425,
        164.79367242, -199.80888341,   94.1786936 ,   43.45477102])

I was expecting that doing normalNPArray = normalNPArray.astype(np.uint8) will automatically set the negative values to 0 since uint8 does not naturally support negative values. However, casting the array as np.uint8 gives me the following:

array([ 46,   6,   9,  97,  52, 191, 103, 170,  46,  61, 240,  87, 210,
            74,  90,  35, 164,  57,  94,  43], dtype=uint8)

Doing normalNPArray[normalNPArray < 0] = 0 and then casting the array to np.uint8 will resolve the issue.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.