Iago Quintero
Iago Quintero

Reputation: 51

ycbcr to rgb color conversion error on edge cases

I have some problems converting a rgb photo to ycbcbr, modifying the y channel and converting back to rgb, generally it works perfect but for some edge cases it returns negative values or values greater than 255 so when i convert it back to uint8 for matplotlib it shows bright spots.

RGB to YCbCr

def rgb2ycbcr(data):
    res = np.empty(data.shape)
    res[...,0] = (data[...,0] * 0.299 + data[...,1] * 0.587 + data[...,2] * 0.114)
    res[...,1] = 128 + (data[...,0] * -0.169 + data[...,1] * -0.331 + data[...,2] * 0.5)
    res[...,2] = 128 + (data[...,0] * 0.5 + data[...,1] * -0.419 + data[...,2] * -0.081)
    return res

and YCbCr to RGB:

def ycbcr2rgb(data):
    res = np.empty(data.shape)
    data[...,1] = data[...,1] - 128
    data[...,2] = data[...,2] - 128
    res[...,0] = data[...,0] * 1 + data[...,2] * 1.4
    res[...,1] = data[...,0] * 1 + data[...,1] * -0.343 + data[...,2] * -0.711
    res[...,2] = data[...,0] * 1 + data[...,1] * 1.765
    return res

The strange thing is that when i dont touch the Y channel the photo converts ok (i know that when i modify the Y channel i keep the values in range)

Upvotes: 3

Views: 920

Answers (1)

Kel Solaar
Kel Solaar

Reputation: 4080

Because of precision issues you are getting negative values and values over 1, so you would have to round/convert to integer:

RGB = np.asarray([[0, 0, 0], [255, 255, 255]])
print(rgb2ycbcr(ycbcr2rgb(RGB)))

[[-0.1423 0.6689 0.1714]
[255.1412 254.3363 254.8299]]

Now a major issue problem is that you are using constants that have too much rounding and do not agree with Recommendation ITU-T T.871, tweaking your code slightly accordingly to the 4 decimals rounded reference:

RGB = np.asarray([[0, 0, 0], [255, 255, 255]])

def rgb2ycbcr(data):
    res = np.empty(data.shape)
    res[..., 0] = (
        data[..., 0] * 0.299 + data[..., 1] * 0.587 + data[..., 2] * 0.114)
    res[..., 1] = 128 + (
        data[..., 0] * -0.1687 + data[..., 1] * -0.3313 + data[..., 2] * 0.5)
    res[..., 2] = 128 + (
        data[..., 0] * 0.5 + data[..., 1] * -0.4187 + data[..., 2] * -0.0813)
    return res


def ycbcr2rgb(data):
    res = np.empty(data.shape)
    data[..., 1] = data[..., 1] - 128
    data[..., 2] = data[..., 2] - 128
    res[..., 0] = data[..., 0] * 1 + data[..., 2] * 1.402
    res[...,
        1] = data[..., 0] * 1 + data[..., 1] * -0.3441 + data[..., 2] * -0.7141
    res[..., 2] = data[..., 0] * 1 + data[..., 1] * 1.772
    return res

print(rgb2ycbcr(ycbcr2rgb(RGB)))

[[-0.0055 -0.0082 -0.0006]
[255.0054 255.0082 255.0006]]

You still have to do conversion to integer but should be in a better place. I noticed that you are modifying data in place in ycbcr2rgb, you should make a copy of the array when entering the definition or you will have very nasty surprises down the line.

I would suggest implementing the version without rounding from Recommendation ITU-T T.871.

Colour that we maintain has a very solid and complete implementation of Y'CbCr that you can use to verify your computations:

RGB = np.asarray([[0, 0, 0], [255, 255, 255]])

print(colour.YCbCr_to_RGB(
    colour.RGB_to_YCbCr(
        RGB,
        K=colour.YCBCR_WEIGHTS['ITU-R BT.601'],
        in_bits=8,
        in_int=True,
        in_legal=False,
        out_bits=8,
        out_int=True,
        out_legal=True),
    K=colour.YCBCR_WEIGHTS['ITU-R BT.601'],
    in_bits=8,
    in_int=True,
    in_legal=True,
    out_bits=8,
    out_int=True,
    out_legal=False))

[[  0   0   0]
[255 255 255]]

I would also suggest using BT.709 weights instead of those from ITU-T T.871/BT.601, the former are more widespread.

Upvotes: 6

Related Questions