c++crgbyuvbidirectional

How to convert RGB -> YUV -> RGB (both ways)


I want a pair of conversion algorithms, one from RGB to YUV, the other from YUV to RGB, that are inverses of each other. That is, a round-trip conversion should leave the value unchanged. (If you like, replace YUV with Y'UV, YUV, YCbCr, YPbPr.)

Does such a thing exist? If so, what is it?

Posted solutions (How to perform RGB->YUV conversion in C/C++?, http://www.fourcc.org/fccyvrgb.php, http://en.wikipedia.org/wiki/YUV) are only inverses (the two 3x3 matrices are inverses), when omitting the clamping to [0,255]. But omitting that clamping allows things like negative luminance, which plays merry havoc with image processing in YUV space. Retaining the clamping makes the conversion nonlinear, which makes it tricky to define an inverse.


Solution

  • Yes, invertible transformations exist.

    equasys GmbH posted invertible transformations from RGB to YUV, YCbCr, and YPbPr, along with explanations of which situation each is appropriate for, what this clamping is really about, and links to references. (Like a good SO answer.)

    For my own application (jpg images, not analog voltages) YCbCr was appropriate, so I wrote code for those two transformations. Indeed, there-and-back-again values differed by less than 1 part in 256, for many images; and the before-and-after images were visually indistinguishable.

    PIL's colour space conversion YCbCr -> RGB gets credit for mentioning equasys's web page.

    Other answers, that could doubtfully improve on equasys's precision and concision:

    2019 Edit: Here's the C code from github, mentioned in my comment.

    void YUVfromRGB(double& Y, double& U, double& V, const double R, const double G, const double B)
    {
      Y =  0.257 * R + 0.504 * G + 0.098 * B +  16;
      U = -0.148 * R - 0.291 * G + 0.439 * B + 128;
      V =  0.439 * R - 0.368 * G - 0.071 * B + 128;
    }
    void RGBfromYUV(double& R, double& G, double& B, double Y, double U, double V)
    {
      Y -= 16;
      U -= 128;
      V -= 128;
      R = 1.164 * Y             + 1.596 * V;
      G = 1.164 * Y - 0.392 * U - 0.813 * V;
      B = 1.164 * Y + 2.017 * U;
    }
    

    2023 Edit: A fast integer-only (but approximate, hence not exactly invertible) implementation, from comments by Antonio:

    void YUVfromRGB(double& Y, double& U, double& V, const double R, const double G, const double B)
    {
      Y = (( 66 * R + 129 * G +  25 * B + 128) / 256) +  16;
      U = ((-38 * R -  74 * G + 112 * B + 128) / 256) + 128;
      V = ((112 * R -  94 * G -  18 * B + 128) / 256) + 128;
    }