binatheis
binatheis

Reputation: 1

Issues with Normal Pass Mapping from EXR Channels to RGB in Python Script

Problem

I'm working on a Python script to generate images from EXR channel data for a larger project that requires accurate normal pass mappings. The script processes the X, Y, and Z channels from an EXR file and maps them to the RGB channels of the resulting image. However, the output image exhibits significant artifacts and distortions, making the normal pass mapping incorrect compared to the expected result.

Minimal Reproducible Example:

Here's a minimal example script that demonstrates the issue. This script is intended to be run within Blender's Python environment. It uses OpenEXR to read the 16 bit EXR file, processes the normal pass, and saves the result as a PNG image using Pillow. You can use this 16 bit EXR:https://wetransfer.com/downloads/2855df23fed4b0c348e1cfa4371bc35020241110001843/0172f8

import bpy
import OpenEXR
import Imath
import numpy as np
from PIL import Image
import os

def process_normal_pass(exr_path, output_path):
    # Open EXR file
    exr = OpenEXR.InputFile(exr_path)
    header = exr.header()
    channels = header['channels']
    
    # Get image dimensions
    dw = header['dataWindow']
    width = dw.max.x - dw.min.x + 1
    height = dw.max.y - dw.min.y + 1
    
    # Process Normal Pass channels (assuming 16-bit)
    channel_data = []
    for channel in ['X', 'Y', 'Z']:
        full_channel_name = f'ViewLayer.Normal.{channel}'
        pixel_type = Imath.PixelType(Imath.PixelType.HALF)  # 16-bit
        channel_str = exr.channel(full_channel_name, pixel_type)
        channel_array = np.frombuffer(channel_str, dtype=np.float16).reshape(height, width)
        channel_data.append(channel_array)
    
    # Convert normal vectors to RGB colors
    x, y, z = channel_data
    
    # Normalize the vectors
    magnitude = np.sqrt(x*x + y*y + z*z)
    magnitude = np.where(magnitude > 0, magnitude, 1)
    x = x / magnitude
    y = y / magnitude
    z = z / magnitude
    
    # Map normal components to RGB (world space)
    r = np.abs(x)  # X component to Red
    g = np.zeros_like(y)  # Suppress green channel
    b = np.abs(z)  # Z component to Blue
    
    # Create the RGB representation
    layer_data = np.dstack([r, g, b])
    
    # Scale to 16-bit range and convert
    layer_data = layer_data * 65535.0
    layer_data = np.clip(layer_data, 0, 65535.0)
    layer_data = layer_data.astype(np.uint16)
    
    # Save as 16-bit PNG
    img = Image.fromarray(layer_data, mode='RGB')
    img.save(output_path, format='PNG', optimize=False)
    print(f"Normal pass image saved to {output_path}")

# Example usage
exr_file = bpy.path.abspath('//normal_pass.exr')
output_png = bpy.path.abspath('//normal_pass_output.png')

process_normal_pass(exr_file, output_png)

Dependencies:

Environment:

Instructions to Run:

  1. Ensure the required Python packages (OpenEXR, Imath, numpy, Pillow) are installed in Blender's Python environment or your own envoroment.
  2. Replace '//path_to_your_exr_file.exr' with the actual path to your EXR file.
  3. Run the script within Blender's scripting editor or your own editor.
  4. Check the output PNG image for artifacts.

Expected Result: A smooth and accurate representation of the normal vectors without visible artifacts or distortions. IMAGE:https://new.imagehostx.com/upload/2024/11/09/normalexr_1731171391.png

Actual Result: The output image contains concentrated lines of red and blue, disrupting the uniformity and smoothness of the normal pass mapping. IMAGE: https://new.imagehostx.com/upload/2024/11/09/putitoREAL_1731171538.png

I attempted two modifications to the normal pass processing in the script to resolve the artifacts:

  1. Preserving the Green Channel: Instead of suppressing the green channel (g = np.zeros_like(y)), I changed it to preserve the Y component by using g = np.abs(y). However, this resulted in an output that was ten times worse, with the image not resembling the expected normal pass at all. IMAGE:https://new.imagehostx.com/upload/2024/11/09/vicentesHOY_1731171598.png

    # Original code
    r = np.abs(x)  # X component to Red
    g = np.zeros_like(y)  # Suppress green channel
    b = np.abs(z)  # Z component to Blue
    
    # Modification 1
    r = np.abs(x)
    g = np.abs(y)  # Preserving the Y component
    b = np.abs(z)
    
  2. Alternate Mapping Approach: I tried remapping the normal components from the range [-1, 1] to [0, 1] using the following formulas: r = (x + 1.0) * 0.5, g = (y + 1.0) * 0.5, and b = (z + 1.0) * 0.5. This approach also led to undesirable results, as the normal pass interpretation should be in world space, not camera space or tangent space. IMAGE:https://new.imagehostx.com/upload/2024/11/09/Basta_1731171625.png

    # Modification 2
    r = (x + 1.0) * 0.5
    g = (y + 1.0) * 0.5
    b = (z + 1.0) * 0.5
    

Despite these attempts, the output images still do not accurately represent the normal pass as intended. I need assistance in identifying the root cause of these distortions and finding a reliable solution to correctly map the X, Y, and Z channels to RGB without introducing artifacts.

Upvotes: 0

Views: 129

Answers (0)

Related Questions