Reputation: 51
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
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