Normalize

Overview

The Normalize sample demonstrates per-channel mean-and-standard-deviation normalization using CV-CUDA’s GPU-accelerated normalize operator. The sample applies the standard ImageNet statistics (mean [123.675, 116.28, 103.53] and std [58.395, 57.12, 57.375] expressed in [0, 255] space) to an RGB image, producing float32 normalized values. Because the normalized output is not directly viewable as a JPEG, the sample linearly rescales the result back to the [0, 255] uint8 range before saving.

Usage

Basic Usage

Normalize an image using the default ImageNet statistics:

python3 normalize.py -i input.jpg

Custom Output Path

Specify a custom output file:

python3 normalize.py -i input.jpg -o my_normalized.jpg

Command-Line Arguments

Argument

Short Form

Default

Description

--input

-i

tabby_tiger_cat.jpg

Input image file path

--output

-o

cvcuda/.cache/cat_normalize.jpg

Output image file path

Implementation

Setup: Mean and Std Tensors

# The normalize operator expects base (mean) and scale (std) tensors with
# the same layout as the source.  We use per-channel ImageNet statistics
# expressed as pixel values in [0, 255] so no manual pre-scaling is needed.
# Shape (1, 1, 3) broadcasts over height and width for an HWC image.
imagenet_mean = np.array(
    [[[123.675, 116.28, 103.53]]], dtype=np.float32
)  # R, G, B means × 255
imagenet_std = np.array(
    [[[58.395, 57.12, 57.375]]], dtype=np.float32
)  # R, G, B stds × 255

base_tensor = cvcuda.Tensor(imagenet_mean.shape, np.float32, "HWC")
scale_tensor = cvcuda.Tensor(imagenet_std.shape, np.float32, "HWC")
upload_tensor(imagenet_mean, base_tensor)
upload_tensor(imagenet_std, scale_tensor)

# Convert the uint8 HWC input to float32 so that the operator emits float32
# output; a uint8 source would produce a clamped uint8 result that is
# unsuitable for visualising the normalized values.
uint8_host = download_tensor(input_image)
float32_host = uint8_host.astype(np.float32)

float32_tensor = cvcuda.Tensor(float32_host.shape, np.float32, "HWC")
upload_tensor(float32_host, float32_tensor)

Normalize Operator Call

# The normalize operator expects base (mean) and scale (std) tensors with
# the same layout as the source.  We use per-channel ImageNet statistics
# expressed as pixel values in [0, 255] so no manual pre-scaling is needed.
# Shape (1, 1, 3) broadcasts over height and width for an HWC image.
imagenet_mean = np.array(
    [[[123.675, 116.28, 103.53]]], dtype=np.float32
)  # R, G, B means × 255
imagenet_std = np.array(
    [[[58.395, 57.12, 57.375]]], dtype=np.float32
)  # R, G, B stds × 255

base_tensor = cvcuda.Tensor(imagenet_mean.shape, np.float32, "HWC")
scale_tensor = cvcuda.Tensor(imagenet_std.shape, np.float32, "HWC")
upload_tensor(imagenet_mean, base_tensor)
upload_tensor(imagenet_std, scale_tensor)

# Convert the uint8 HWC input to float32 so that the operator emits float32
# output; a uint8 source would produce a clamped uint8 result that is
# unsuitable for visualising the normalized values.
uint8_host = download_tensor(input_image)
float32_host = uint8_host.astype(np.float32)

float32_tensor = cvcuda.Tensor(float32_host.shape, np.float32, "HWC")
upload_tensor(float32_host, float32_tensor)

Key points:

  1. base and scale tensors: Broadcast-shaped (1, 1, 3) HWC tensors holding per-channel mean and standard deviation values; the operator broadcasts them across all pixels automatically.

  2. SCALE_IS_STDDEV flag: Tells the operator that the scale argument is a standard deviation rather than a raw scaling factor, so it computes out = (src - base) / (scale + epsilon).

  3. Float32 input requirement: Passing a float32 source keeps the output in float32 so the normalized values retain their signed range; a uint8 source would clamp the result back to uint8.

  4. epsilon: A small regularizer added to the denominator, preventing division by zero when the standard deviation is near zero.

  5. Visualization rescaling: The normalized output typically falls in [-2, 2]. The sample min-max rescales that range back to [0, 255] for JPEG encoding.

Expected Output

The output shows the pixel distribution shifted and scaled by the ImageNet statistics, then remapped to uint8 for viewing:

../../_images/tabby_tiger_cat.jpg

Original Input Image

../../_images/cat_normalize.jpg

Output: ImageNet-normalized (rescaled to uint8 for display)

CV-CUDA Operators Used

Operator

Purpose

cvcuda.normalize()

Apply per-channel mean-std normalization to a tensor

Common Utilities Used

  • read_image() - Load image as CV-CUDA tensor

  • write_image() - Save normalized image

  • cuda_memcpy_h2d / cuda_memcpy_d2h - Transfer base/scale parameters and results between host and device

See Also