Search code examples
colorsrgbyuv

Is converting YCbCr to RGB reversible?


I was playing around with some different image formats and ran across something I found odd. When converting from RGB to YCbCr and then back to RGB, the results are very similar to what I started with (the difference in pixels values is almost always less than 4). However, when I convert from YCbCr to RGB and then back to YCbCr, I often get vastly different values. Sometimes a values will differ by over 40.

I'm not sure why this is. I was under the impression that the colors which could be expressed through YCbCr were a subset of those in RGB, but it looks like this is completely wrong. Is there some known subset of colors in YCbCr that can be converted to RGB and then back to their original values?

The code I'm using to convert (based on this site):

def yuv2rgb(yuv):
  ret = []
  for rows in yuv:
    row = []
    for y, u, v in rows:
      c = y - 16
      d = u - 128
      e = v - 128
      r = clamp(1.164*c +           1.596*e, 16, 235)
      g = clamp(1.164*c - 0.392*d - 0.813*e, 16, 240)
      b = clamp(1.164*c + 2.017*d          , 16, 240)
      row.append([r, g, b])
    ret.append(row)
  return ret

def rgb2yuv(rgb):
  ret = []
  for rows in rgb:
    row = []
    for r, g, b in rows:
      y = int( 0.257*r + 0.504*g + 0.098*b + 16)
      u = int(-0.148*r - 0.291*g + 0.439*b + 128)
      v = int( 0.439*r - 0.368*g - 0.071*b + 128)
      row.append([y, u, v])
    ret.append(row)
  return ret

EDIT:

I created a rudimentary 3D graph of this issue. All the points are spots with a value difference less than 10. It makes a pretty interesting shape. X is Cb, Y is Cr, and Z is Y.

Each point is a YCbCr value that converts nicely to RGB and back


Solution

  • No, it is not, at all. [All disscused below is for 8 bit.] And it is obvious in the case of Full range R'G'B' to limited range YCbCr (there is just no bijection). For example, you can test it here:

    https://res18h39.netlify.app/color

    Full range R'G'B' values 238, 77, 45 are encoded into limited YCbCr with BT.601 matrix: you will get limited range 120, 90, 201 after school rounding, but if you will round it you will get 238, 77, 44 back in R'G'B'. And 238, 77, 44 value will go to the same value. Oopsie. Here it is: game over.

    In the case with full range RGB to full range YCbCr... There are some values in YCbCr that will be negative R', G', B'. (For example in limited range YCbCr BT.709 values 139, 151, 24 will be limited (!) RGB -21, 182, 181 and full RGB -43, 194, 192.) So again, no bijection.

    Next, limited range R'G'B' to limited range YCbCr... Again, no bijection. Black in YCbCr is actually 16, 128, 128 and only this. All other 16, x, y are not allowed [they are in xvYCC, which is non-standard], while they are in R, G, B, and all 235, 128, 128 the same. And the previous negative R', G', B' also applies, of course.

    And with limited range to full range, I do not know.