Convert Luminous and Color Opponent Channels to RGB Image

asked 2016-08-21 20:36:06 -0600

JoshB gravatar image

updated 2016-08-25 11:35:37 -0600

Update - I changed the RGB to opponent conversion to that posted here: https://www.mathworks.com/matlabcentral/answers/118889-rgb-to-opponent-color-space-code

I converted the RGB channels of an image to luminous and color opponent channels; now I am trying to convert the opponent channels back to RGB (using only luminous and the opponent channels), but I do not know how to compute R, G, and B given luminous, red-green, and yellow-blue. How do we go back to RGB from luminuous, red-green, and yellow-blue?

My Method

I converted the RGB image to the opponent channels using the method here. The conversion goes:

//Red minus green
opponentChannels[0].at<float>(y, x) = (r - g) / std::sqrt(2.0f);

//Yellow minus blue
opponentChannels[1].at<float>(y, x) = (r + g - (2 * b)) / std::sqrt(6.0f);

//Luminous
opponentChannels[2].at<float>(y, x) = (r + g + b) / std::sqrt(3.0);

where r, g, and b are floats and x and y are the coodinates of the pixel being set.

To get the original image back, i converted each channel to RGB. For luminous:

cv::Vec3f& pixel = rgbEquivalent->at<cv::Vec3f>(y, x);
//Multiply by sqrt(3.0f) / 3.0f to put in the range of [0.0,1.0].
pixel[0] = pixel[1] = pixel[2] = luminous.at<float>(y, x) * (std::sqrt(3.0f) / 3.0f);

For red-green and yellow-blue (I expect this is where the problem is):

if (type == RED_MINUS_GREEN)
{
    //Multiply by sqrt(2.0f) to put in the range [0.0, 1.0]
    float red = color * std::sqrt(2.0f);
    float green = -red;

    if (red < 0.0f)
        red = 0.0f;

    if (green < 0.0f)
        green = 0.0f;

    pixel[0] = 0.0;
    pixel[1] = green;
    pixel[2] = red;
}
else if (type == YELLOW_MINUS_BLUE)
{
    //Multiply by sqrt(6.0f) / 2.0f to put in the range of [0.0, 1.0]
    float yellow = color * (std::sqrt(6.0f) / 2.0f);
    float blue = -yellow;

    if (yellow < 0.0f)
        yellow = 0.0f;

    if (blue < 0.0f)
        blue = 0.0f;

    pixel[0] = blue;
    pixel[1] = yellow;
    pixel[2] = yellow;
}

For the image:

Original image

I achieved these RGB representations of each channel

luminous redminusgreen yellowminusblue

I then get the original back by taking the average RGB value, but it seems too dark:

reconst1

Summing all the RGB values results in an image that seems slightly too bright (or yellow?):

reconst2

edit retag flag offensive close merge delete

Comments

Your explicit cast to uchar, ensures that precision can get lost ... Therefore you will lose data that you will never be able to get back!

StevenPuttemans gravatar imageStevenPuttemans ( 2016-08-23 08:18:43 -0600 )edit

Switched to cv::saturate_cast. It seems to keep the last image in bounds, so it avoids the incorrect color blobs. The image still seems too bright, or it just has too much yellow.

JoshB gravatar imageJoshB ( 2016-08-23 11:25:06 -0600 )edit

using saturate cast will not solve the problem of precision loss ... the range of a 16 bit float is different and much larger than the range of a 8 bit unsigned char...

StevenPuttemans gravatar imageStevenPuttemans ( 2016-08-24 04:38:20 -0600 )edit

I think I understand. Perhaps the best way to store these channels is with CV_32FC3? Using CV_32FC3, it might be better to go with the usual opponent conversion (e.g. redversusgreen = (R - G) / sqrt(2)). EDIT: If I use redversusgreen = (R - G + 1.0f) / 2.0f I get what seems to be the same result with CV_32FC3, but it is more precise than CV_8UC3. I am unclear as to why the link shows the usual transform as redversusgreen = (R - G) / sqrt(2) or why/if it makes a difference.

JoshB gravatar imageJoshB ( 2016-08-24 16:54:05 -0600 )edit