Files
filmsim/README-filmgrain.md

7.1 KiB

filmgrain - Realistic Film Grain Simulation

filmgrain is a Python script that applies realistic film grain to digital images. It implements the procedural grain synthesis method described by Zhang et al. (2023) in "Procedural Synthesis of Film Grain Noise". This method leverages GPU acceleration via the warp library for efficient processing.

Features

  • Implements the film grain model by Zhang et al. (2023).
  • GPU accelerated using warp-lang (requires a CUDA-capable NVIDIA GPU).
  • Adjustable Grain Parameters:
    • --mu_r: Mean grain radius (relative to pixel size), controlling grain size.
    • --sigma: Standard deviation of the Gaussian filter for noise blurring (sigma_filter), controlling grain softness/sharpness.
  • Monochrome Option: Apply spectrally uniform (monochromatic) grain based on image luminance, or apply independent RGB grain.
  • Precomputes a variance lookup table (LUT) for performance.
  • Supports RAW (Sony ARW, DNG), TIFF, PNG, and JPG inputs.
  • Outputs TIFF (16-bit uncompressed), PNG (8-bit), or JPG (8-bit).

Requirements

  • NVIDIA GPU: A CUDA-enabled NVIDIA graphics card is required as warp-lang currently targets CUDA.
  • CUDA Toolkit: Ensure you have a compatible version of the CUDA Toolkit installed.
  • uv: For easy execution and dependency management.

Usage

The script is designed to be run with uv.

You can run filmgrain directly from its Git repository URL:

uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/filmgrain -- <input_image> <output_image> [options]

Example:

# Ensure input.png is in your current directory or provide a full path
# Output will be output_grained.jpg
uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/filmgrain -- input.png output_grained.jpg --mu_r 0.1 --sigma 0.8 --mono

Local Execution

If you have cloned the repository or downloaded the script:

  1. Navigate to the script's directory or ensure it's in your PATH.

  2. Run the script:

    uv run ./filmgrain -- <input_image> <output_image> [options]
    

    Or, if filmgrain is in the current directory and executable:

    ./filmgrain <input_image> <output_image> [options]
    

Arguments

Positional Arguments:

  • input_image: Path to the input image.
    • Supported formats: TIFF, PNG, JPG, Sony ARW, DNG.
  • output_image: Path to save the output image with film grain.
    • Output format is determined by the file extension:
      • .tiff / .tif: 16-bit uncompressed TIFF.
      • .png: 8-bit PNG.
      • .jpg / .jpeg: 8-bit JPG (quality 95).

Options:

  • --mu_r FLOAT: Mean grain radius (relative to pixel size). (Default: 0.1)
    • Controls the apparent size of the grain particles. Larger values mean larger grains.
    • Must be positive.
  • --sigma FLOAT: Standard deviation of the Gaussian Filter for noise blurring (sigma_filter). (Default: 0.8)
    • Controls the sharpness or softness of the grain. Higher values result in softer, more blurred grain.
    • Must be positive.
    • The paper suggests sigma_filter should ideally be >= 3 * mu_r for the model's approximations to hold well. The script will issue a warning if this condition is not met.
  • --seed INT: Random seed for noise generation. (Default: 42)
    • Allows for reproducible grain patterns.
  • --mono: Apply monochrome film grain across channels based on luminance. (Default: False - apply RGB grain)
    • If enabled, grain is generated based on the image's luminance and applied uniformly to R, G, and B channels.
    • If disabled, grain is generated and applied independently for each color channel (R, G, B), leading to colored grain.

Workflow

  1. Load Image:
    • Reads the input image. RAW files (ARW, DNG) are post-processed using rawpy (16-bit output, camera WB, no auto-bright, linear gamma). Other formats (TIFF, PNG, JPG) are read using imageio.
    • The image is converted to a 0-1 float32 range.
  2. Precompute Variance LUT:
    • Calculates a lookup table (LUT) for the variance of the Boolean-Poisson (BP) noise model. This involves numerically integrating a complex function derived from the grain model, significantly speeding up per-pixel variance calculation later.
  3. Prepare Gaussian Kernel:
    • Creates a 2D Gaussian kernel based on the sigma_filter parameter. This kernel is used to blur the initial noise, shaping the grain's appearance. The kernel radius is determined by sigma_filter.
  4. Process Channels (RGB or Monochrome):
    • Monochrome Mode (--mono):
      • The input image is converted to grayscale (luminance).
      • generate_noise_kernel (Warp kernel): Generates initial random noise for the grayscale image. The noise variance at each pixel is determined by the pixel's intensity (u_val) and the precomputed variance LUT, scaled by mu_r and sigma_filter.
      • convolve_2d_kernel (Warp kernel): Convolves the generated noise with the 2D Gaussian kernel to produce the filtered (shaped) grain.
      • This single-channel filtered noise is then used for all R, G, and B output channels.
    • RGB Mode (default):
      • The process above (noise generation and convolution) is performed independently for each color channel (R, G, B) of the input image.
      • Different random seeds are used for each channel (seed, seed+1, seed+2) to ensure decorrelated grain patterns.
      • If the input is grayscale, the R channel's noise is copied to G and B.
  5. Add Noise and Clip:
    • add_rgb_noise_and_clip_kernel (Warp kernel): Adds the filtered noise (either monochrome or per-channel RGB) to the original image channels.
    • The result is clipped to the 0.0 - 1.0 range.
  6. Save Output:
    • The final image with added grain is copied from the GPU back to the CPU.
    • It's converted to the appropriate bit depth (16-bit for TIFF, 8-bit for PNG/JPG) and saved.

Technical Details (from Zhang et al. 2023)

  • Boolean-Poisson (BP) Model: The grain is modeled as randomly placed circular grains (disks) of radius mu_r. The local density of these grains depends on the image intensity.
  • Variance Calculation: The core of the model involves calculating the variance of the noise that would result from this BP process. This variance is intensity-dependent. The w_func and CB_const_radius_unit functions in the script are related to the geometric calculations of overlapping disks, which are part of this variance derivation. The integral of CB_const_radius_unit * x gives the key component for variance, which is precomputed into the LUT.
  • Filtering: The raw noise generated based on this variance is then filtered (convolved) with a Gaussian kernel (h in the paper, controlled by sigma_filter) to achieve the final grain appearance. The paper notes that the filter's standard deviation sigma_filter should be significantly larger than mu_r (e.g., sigma_filter >= 3 * mu_r) for certain approximations in their model to be accurate.