David Poole
David Poole

Reputation: 3510

3x3 matrix conversion without loop (RGB color conversion)

I have an RGB image loaded into a numpy array via PIL. I get a rows x cols x 3 array. After tinkering, I arrived at the following code. I'd like to learn how to do such an array/matrix manipulation without the loop.

# Note using matrix not array.
rgb_to_ycc = np.matrix(
     (0.2990,  0.5870,  0.1140,
    -0.1687, -0.3313,  0.5000,
     0.5000, -0.4187, -0.0813,)
).reshape( 3,3 )

ycc_to_rgb = np.matrix(
    ( 1.0, 0.0, 1.4022,
      1.0, -0.3456, -0.7145,
      1.0, 1.7710, 0, )
).reshape( 3, 3 )

def convert_ycc_to_rgb( ycc ) :
    # convert back to RGB
    rgb = np.zeros_like( ycc )
    for row in range(ycc.shape[0]) :
        rgb[row] = ycc[row] * ycc_to_rgb.T
    return rgb

def convert_rgb_to_ycc( rgb ) :
    ycc = np.zeros_like( rgb )
    for row in range(rgb.shape[0]):
        ycc[row] = rgb[row] * rgb_to_ycc.T
    return ycc

I could use http://pypi.python.org/pypi/colormath (via Using Python to convert color formats?) but I'm using this as an exercise to learn numpy.

The aforementioned Colormath library uses a dot product.

# Perform the adaptation via matrix multiplication.
result_matrix = numpy.dot(var_matrix, rgb_matrix)

My math isn't where it should be. Is np.dot() my best bet?

EDIT. After deeper reading colormath's apply_RGB_matrix()-color_conversions.py, I found the np.dot() works if my conversion 3x3s are not matrices. Weird.

def convert_rgb_to_ycc( rgb ) :
    return np.dot( rgb, np.asarray( rgb_to_ycc ).T )

Upvotes: 3

Views: 3193

Answers (2)

unutbu
unutbu

Reputation: 879701

I'm not sure about the formula you are using to convert RGB to YCC, so I don't want to claim this is the complete calculation, but to simplify the function you posted, yes, use np.dot with numpy arrays instead of numpy matrices.

np.dot is more general than * with numpy matrices. When using * with numpy matrices, the two matrices have to be 2 dimensional. But np.dot can produce a result with arrays of different shape. This is important for your application since rgb is 3-dimensional (e.g. when it has shape (1470, 2105, 3)).

The docs for np.dot say:

    For N dimensions it is a sum product over the last axis of `a` and
    the second-to-last of `b`::

        dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m])

This is a generalization of regular matrix multiplication.


I suggest calling your ultimate function rgb_to_ycc, instead of giving that designation to the constant matrix. (It's shorter and says exactly what you want the function to do.)

So below, rgb_to_ycc is my suggested function, and I made some minor modifications to make convert_rgb_to_ycc not raise exceptions and do the computation I think you are intending.

The final line, np.allclose(...) shows the two functions return the same result.

import numpy as np

def rgb_to_ycc(rgb):
    M = np.array(
         (0.2990,  0.5870,  0.1140,
        -0.1687, -0.3313,  0.5000,
         0.5000, -0.4187, -0.0813,)
        ).reshape( 3,3 )
    return np.dot(rgb, M.T)

def convert_rgb_to_ycc( rgb ) :
    M = np.matrix(
         (0.2990,  0.5870,  0.1140,
        -0.1687, -0.3313,  0.5000,
         0.5000, -0.4187, -0.0813,)
        ).reshape( 3,3 )
    shape=rgb.shape
    rgb=rgb.reshape((-1,3))
    ycc = np.zeros_like( rgb )
    for i in range(len(rgb)):
        ycc[i] = rgb[i] * M.T
    return ycc.reshape(shape)

rgb=np.random.random((100,100,3))
assert np.allclose(rgb_to_ycc(rgb),convert_rgb_to_ycc(rgb))

Upvotes: 4

fortran
fortran

Reputation: 76067

def convert_ycc_to_rgb(ycc):
    return ycc * ycc_to_rgb.T

def convert_rgb_to_ycc(rgb):
    return rgb * rgb_to_ycc.T

as simple as that, remember how matrix multiplication is defined in terms of inner products of rows and columns.

edit:

I was assuming that the rgb and ycc matrices were just a matrix that had as many rows as pixels and a column per colour component. So what we need to do first is to reshape them to a (rows*cols,3) and then back again to (rows, cols, 3).

So the code finally is:

def convert_ycc_to_rgb(ycc):
    shape = ycc.shape
    return np.array(ycc.reshape(-1,3) * ycc_to_rgb.T).reshape(shape)

def convert_rgb_to_ycc(rgb):
    shape = rgb.shape
    return np.array(rgb.reshape(-1,3) * rgb_to_ycc.T).reshape(shape)

Upvotes: 3

Related Questions