Coloring with linear transformations


In the realm of digital imagery, every pixel seen on screens is fundamentally a mathematical construct, specifically a vector in a carefully defined color space. While there is a intuitive notion of colors as singular entities, they are, in fact, points in a three-dimensional vector space where each dimension represents the intensity of a primary color. The most common representation, the RGB color space, encodes colors as triplets $(r,g,b)$, where each component represents the intensity of red, green, and blue light respectively, typically scaled from 0 to 255 in 8-bit color depth.

But in linear algebraic terms, each color vector in this space can undergo various transformations, and these transformations are powered by the matrix multiplications and eigenvalues that define them. For any matrix $A$ acting on a vector $v$, it is noticeable that the output is not arbitrary, but in reality scaled and rotated in ways determined by $A$’s structure. When a transformation matrix has a dominant eigenvalue, $\lambda$, this eigenvalue shapes how the image data projects in that color space. Through these transformations, certain components or dimensions in the RGB color space may become reduced or highlighted, creating new perceptions of color by emphasizing or de-emphasizing specific wavelenghts. This effect is central to simulating color deficiencies like deuteranopia, where certain wavelengths are perceived at a reduced intensity or even entirely omitted.

The mathematical beauty of this representation becomes apparent when we consider that any color transformation (brightness adjustment, saturation modification, or even simulation of color blindness) can be expressed as linear transformations. In linear algebra, this means applying a matrix multiplication to each color vector. For example, converting a color from RGB to grayscale isn’t simply averaging the three components, but a carefully weighted sum that accounts for human perception of different wavelengths. This transformation can be expressed as $Y = 0.2126R + 0.7152G + 0.0722B$, these specific coefficients being derived from the human eye’s different sensitivities to red, green, and blue light.

Delving further into more complex color spaces, like YUV, as used in television transmission, we encounter transformations that separate luminance (Y) from chrominance (U and V). Represented as the matrix equation:

This demonstrates an essential principle of linear transformations: the ability to selectively emphasize or diminish certain dimensions in color space, allowing for compatibility with black-and-white broadcasting when color television was first introduced. The inverse transformation exists and remains linear, allowing the original RGB values to be reconstructed.

When simulating color blindness, linear transformations project the full color space onto a reduced subspace. In deuteranopia, for example, the transformation matrix alters the spectral sensitivities by effectively “removing” the green cone response, reducing the RGB space along this missing perceptual dimension. The transformation matrix is design to project onto a two-dimensional plane within the RGB space, in effect simulating the loss of one color channel and highlighting the perceptual impacts on images.

Linear algebra further extends its utility in color manipulation through its ability to model more complex transformations. For example, a photographer adjusting the white balance is essentially applying a $3\times3$ matrix transformation to shift the color vectors in response to different lightning conditions. This transformation matrix ensures the relationships between colors remain consistent while offsetting the overall color distribution.

Temperature and tint adjustments in photo editing can be viewed as specific rotations around an axis in the color space. When temperature is adjusted, the color vectors rotate around an axis orthogonal to both the blue-yellow and luminance axes, demonstrating another aspect of linear algebra’s power in manipulating vector spaces. The transformations’ additive and scalar properties mean multiple adjustments can be composed into a single transformation matrix, allowing cumulative effects to be captured efficiently through matrix multiplication.

By utilizing a single matrix multiplication, the transformation of colors can respect both addition and scalar multiplication, while accurately projecting color onto a restricted subspace that mirrors the experience of individuals with deuteranopia, as seen in the following Python program. The algorithm implemented demonstrates uses a transformation matrix derived from the research on color vision deficiencies and represents the projection of the full color space onto the reduced space available to individuals with this specific color blindness.

from PIL import Image
import numpy as np

def simulate_deuteranopia(image_path, output_path):
    # Load the image
    img = Image.open(image_path)
    img_array = np.array(img).astype(float)

    # Deuteranopia transformation matrix
    deuteranopia_matrix = np.array([
        [0.625, 0.375, 0],
        [0.7, 0.3, 0],
        [0, 0.3, 0.7]
    ])

    # Apply the transformation
    original_shape = img_array.shape
    pixels = img_array.reshape(-1, 3)
    transformed_pixels = np.dot(pixels, deuteranopia_matrix.T)
    transformed_pixels = np.clip(transformed_pixels, 0, 255)
    transformed_image = transformed_pixels.reshape(original_shape).astype(np.uint8)

    # Save and return the transformed image
    output_img = Image.fromarray(transformed_image)
    output_img.save(output_path)

    return output_img

def analyze_color_distribution(original_img, transformed_img):
    original_array = np.array(original_img)
    transformed_array = np.array(transformed_img)

    # Calculate average colors
    orig_avg = np.mean(original_array.reshape(-1, 3), axis=0)
    trans_avg = np.mean(transformed_array.reshape(-1, 3), axis=0)
    print("Original average RGB:", orig_avg)
    print("Transformed average RGB:", trans_avg)

    # Calculate color variance
    orig_var = np.var(original_array.reshape(-1, 3), axis=0)
    trans_var = np.var(transformed_array.reshape(-1, 3), axis=0)
    print("\nOriginal RGB variance:", orig_var)
    print("Transformed RGB variance:", trans_var)

def main():
    input_path = "input_image.jpg"  # Replace with your image path
    output_path = "deuteranopia_simulation.jpg"

    # Apply transformation and analyze results
    original_img = Image.open(input_path)
    transformed_img = simulate_deuteranopia(input_path, output_path)
    analyze_color_distribution(original_img, transformed_img)

if __name__ == "__main__":
    main()

The beauty of this approach lies in its elegance: a single matrix operation captures the complex physical and perceptual phenomena involved in color vision deficiency. The transformation preserves the mathematical properties expected — it’s linear, which means it respects the addition and scalar multiplication of colors, and it maps the color space in a way that accurately represents the perceptual experience of individuals with deuteranopia.

Original image (left) and altered sample (right).

Leave a Reply

Your email address will not be published. Required fields are marked *