# Inaccurate YUV -> RGB Conversion.

1. I'm having trouble converting bt.709 YUV (or YCrCb) to RGB. I have two equations.

This one gives accurate colors but leaves tiny artifacts on edges:

Code:
rf.f = (255/219)*y + (255/112)*v*(1-#Kr) - (255*16/219 + 255*128/112*(1-#Kr))
gf.f = (255/219)*y - (255/112)*u*(1-Kb)*Kb/Kg - (255/112)*v*(1-Kr)*Kr/Kg - (255*16/219 - 255/112*128*(1-Kb)*Kb/Kg - 255/112*128*(1-Kr)*Kr/Kg)
bf.f = (255/219)*y + (255/112)*u*(1-Kb) - (255*16/219 + 255*128/112*(1-Kb))
This one has no artifacts but the colors are inaccurate:
Code:
;rf.f = y + 2*(v-128)*(1-0.2126)
;gf.f = y - 2*(u-128)*(1-Kb)*Kb/Kg - 2*(v-128)*(1-Kr)*Kr/Kg
;bf.f = y + 2*(u-128)*(1-Kb)
Both come from here:

http://avisynth.nl/index.php/Color_conversions

Working with Y,U,V and R,G,B in the 0 - 255 range. The video comes from a camcorder which outputs video in the 0 - 255 range.

Thoughts?

The colors are accurate and there are no artifacts when played back through VLC and SMPlayer
2. Can you describe the "tiny artifacts on edges ?" , or post screenshot or sample ?

Can you reproduce the same edge artifact with the same equation (or actually, the implementation of the equation) in avisynth ?

If no, then there might be a problem with your implementation of the equation, or something else in the process (e.g. maybe source loading issue or other factor)

If yes, then look at the VLC or SMplayer code to see what they are using
3. Hopefully you can see the file Artifact.jpg which I have attempted to upload.

Look at the blue box. Note the magenta-colored fringes on the edges.
4. This lovely board software has shrunken the image so it might be hard to see. Below is a link to a full-size screen capture. See the magenta fringes at the edges of the blue patch.

http://www.chrisnology.info/photos/Artifact.JPG

It is possible to eliminate these fringes but then the colors become inaccurate, viz.:

Code:
rf.f = y + 2*(v-128)*(1-0.2126)
gf.f = y - 2*(u-128)*(1-Kb)*Kb/Kg - 2*(v-128)*(1-Kr)*Kr/Kg
bf.f = y + 2*(u-128)*(1-Kb)
The variables rf, gf and bf are floats; y, u and v are fetched from a buffer as unsigned 8-bit variables:

Code:
y.a = PeekA(*pntr)
u.a = frame((yCoord / 2) * (#W / 2) + (x / 2) + numpixels)
v.a = frame((yCoord / 2) * (#W / 2) + (x / 2) + numpixels + (numpixels / 4))
Then we limit the values to 255:

Code:
If rf > 255:rf = 255:EndIf :rd.a = rf
If gf > 255:gf = 255:EndIf :gd.a = gf
If bf > 255:bf = 255:EndIf :bd.a = bf
there might be a problem with your implementation of the equation
That's what I'm thinking. As I say, I can make those fringes disappear at the cost of inaccurate colors.

The code that gives me more accurate colors gives me
14-180-14
for green. The original bmp color is 16-180-16. I can live with that because there is rounding error, but we have these fringes.

The code that gives inaccurate colors gives:
26-171-26 for green.
I can't live with that.
5. To add to the confusion, I see the fringes only in the red channel. The green and blue channels have no such fringes.

Here is the offending code for the red channel:

Code:
rf = (255/219)*y + (255/112)*v*(1-#Kr) - (255*16/219 + 255*128/112*(1-#Kr))
6. Follow standard as provided (page 4):

7. Pandy: there is nothing on page 4 of the standard that would solve this dilemma.
8. An optimizing compiler may be treating calculations like (255/219) as integers, converting to (1) -- this is language and/or compiler dependent. Change all of them to floating point, eg. (255.0/219.0) to be sure. I suspect this isn't your problem because it would result in pretty large errors everywhere.

Also, you are using the limited range (Y=16-235, UV=16-240) conversion, not full range. The commented out equation you're not using is full range.

I would also check for underflow (<0) as well as overflow (>255).
9. Originally Posted by chris319
To add to the confusion, I see the fringes only in the red channel. The green and blue channels have no such fringes.

Here is the offending code for the red channel:

Code:
rf = (255/219)*y + (255/112)*v*(1-#Kr) - (255*16/219 + 255*128/112*(1-#Kr))
Pardon my ignorance, but what does the # prefix indicate? This token is not found in the green and blue equations.
10. What a dumb guy I am!

Sometimes the simplest solution is the one most easily overlooked.

Code:
If rf < 0:rf = 0:EndIf
If gf < 0:gf = 0:EndIf
If bf < 0:bf = 0:EndIf
If r, g or b is allowed to go negative you're going to have problems!

Problem solved. I am happy to have accurate colors now, and no fringing.

Thank you to everyone who helped.

Regarding #Kr, in PureBasic the # means Kr has been defined as a constant.
11. Originally Posted by chris319
Pandy: there is nothing on page 4 of the standard that would solve this dilemma.
I clearly see proper equations - chapter 3 "Signal format" , page 4 (physical 6).

Those equations are OFFICIAL - there is no rounding in formal standard which assume truncating - this is one of common issues within video software - ROUNDING instead TRUNCATING - developers rarely following standards (as they are frequently unaware of standard existence - they mostly copying and pasting faulty code).

btw your problem is obviously NOT related to equations (unless you not using arithmetic with SATURATION) but to rescaling - don't use resizer which produce under and overshoots https://en.wikipedia.org/wiki/Overshoot_%28signal%29 . Usually pulses and steps are shaped with Gaussian, raised cosine or Hann, Blackman window function

This PDF may be nice to read...
12. Originally Posted by pandy
Those equations are OFFICIAL - there is no rounding in formal standard
It says:

The operator INT returns the value of 0 for fractional parts in the range of 0 to 0.4999... and +1 for fractional parts
in the range of 0.5 to 0.9999..., i.e. it rounds up fractions above 0.5.
13. Originally Posted by jagabo
Originally Posted by pandy
Those equations are OFFICIAL - there is no rounding in formal standard
It says:

The operator INT returns the value of 0 for fractional parts in the range of 0 to 0.4999... and +1 for fractional parts
in the range of 0.5 to 0.9999..., i.e. it rounds up fractions above 0.5.
To be more precise - 8 bit from 10 and higher bits are truncated without rounding - so you can calculate for 10 bit (black level 64 and White level 940) and divide by 4 AND truncate/loose fractional part... trust me - this is how Hardware implementation looks - most of (if not all) open source encoders accept only 8 bit not 8.2 (10 bit) sample format.
14. Trust you? You lamented programmers not following the standard, and then you misrepresented it. No wonder there is such enduring confusion in implementations.
15. Page 4 of the standard has item numbers in the leftmost column. For clarity, please refer to those.

If you're referring to Item 3.4, you need E'R, E'G and E'B to calculate D'R, D'G and D'B. How am I going to calculate R, G and B given only Y, Cr and Cb?

Here is the code I am using now. Where do you see rounding or anything that is suboptimal? Better yet, why don't you rewrite my code if you think it can be written better. rf, gf, bf, #Kr, #Kg and #Kb are floats. y, u and V are 8-bit bytes output by the camcorder. I don't know why you're bringing 10-bits into the discussion because they don't come into play.

Code:
;http://avisynth.nl/index.php/Color_conversions
rf.f = (255/219)*y + (255/112)*v*(1-#Kr) - (255*16/219 + 255*128/112*(1-#Kr))
gf.f = (255/219)*y - (255/112)*u*(1-#Kb)*#Kb/#Kg - (255/112)*v*(1-#Kr)*#Kr/#Kg - (255*16/219 - 255/112*128*(1-#Kb)*#Kb/#Kg - 255/112*128*(1-#Kr)*#Kr/#Kg)
bf.f = (255/219)*y + (255/112)*u*(1-#Kb) - (255*16/219 + 255*128/112*(1-#Kb))

If rf > 255: rf = 255:EndIf
If gf > 255: gf = 255:EndIf
If bf > 255: bf = 255:EndIf

If rf < 0:rf = 0:EndIf
If gf < 0:gf = 0:EndIf
If bf < 0:bf = 0:EndIf
The above code is giving me accurate colors without artifacts. What do I need to do differently?
16. Do I have these matrices right?

See here:

https://en.wikipedia.org/wiki/YUV#HDTV_with_BT.709

I know very little about matrix notation so here goes:

R = 1 * Y' +1.28033 * v

G = 1 * Y' + -0.21482 * u + -0.38059 * v

B = 1 * Y' + 2.12798 + 0 * v

Where all except u and v are in the range 0 - 1.

At this point I'm just experimenting. I could make these INT's if need be.
17. Your conversion from matrix to algebraic notation is correct.
18. Actually I believe there was an error in the calculation of B. I omitted the multiplication by u.

Code:
R = 1 * Y' +1.28033 * v

G = 1 * Y' + -0.21482 * u + -0.38059 * v

B = 1 * Y' + 2.12798 * u + 0 * v
In any event I'm getting the wrong colors.
19. Originally Posted by JVRaines
Trust you? You lamented programmers not following the standard, and then you misrepresented it. No wonder there is such enduring confusion in implementations.
lol - seem you trying to pin me but you have no clue how broadcast hardware works... for format 8.2 (so called 10 bit) fractional part is truncated, no rounding is performed - accordingly to you 50% grey will have 126 value but in real life it will have 125 - guess why...

Don't trust me i don't care - perform quick test: 50% grey (value 502 on 8.2), use SDI with 8 bit analyser - accordingly to equations 8 bit will be 126 value but on analyser you will see 125 - why? Are you able to explain this?

Originally Posted by chris319
Page 4 of the standard has item numbers in the leftmost column. For clarity, please refer to those.

If you're referring to Item 3.4, you need E'R, E'G and E'B to calculate D'R, D'G and D'B. How am I going to calculate R, G and B given only Y, Cr and Cb?
E'R, E'G and E'B are denominated ("analog") values from 0 to 1 - 50% red will be 0.5 etc - your glitch is related to lack of saturation arithmetic - you need to clamp your values between 0 and 1 (digital 1 and 254 or 0 and 255 etc).

Which is performed by this code (assumption is that you performing calculation with more than 8 bits - don't know what is default for your compiler - signed long?):

Code:
If rf > 255: rf = 255:EndIf
If gf > 255: gf = 255:EndIf
If bf > 255: bf = 255:EndIf

If rf < 0:rf = 0:EndIf
If gf < 0:gf = 0:EndIf
If bf < 0:bf = 0:EndIf
That's why you have proper values (without this you will have negative video signal).
For example you may have pure chrominance signal without luminance (example of such signal is CCIR330 - if you try to convert CCIR330 to RGB without saturation arithmetic then for example Green will be significantly bellow zero (in real life you can't have negative Green light source).

I introduced 10 bit during discussion (or rather 8.2) as this is default digital video signal representation for professional signal sources and as i explained earlier: 8 bit in broadcast is produced by truncating (removing) from 8.2 its fractional part without additional error processing. Standard provide proper error processing and error is within +- 1/2 LSB but real life may be different than expected. Simply i saw your problem from different point than others in topic.

YCbCr to RGB equation is provided on page 3 (physical 5) in:

[Attachment 47062 - Click to enlarge]

General rule provide here: http://discoverybiz.net/enu0/faq/faq_YUV_YCbCr_YPbPr.html
20. So it's the hardware engineers that can't follow the spec.
21. Originally Posted by jagabo
So it's the hardware engineers that can't follow the spec.
Nope - conversion from 8.2 to 8.0 is not specified and usually you have no resources to perform such processing. This is not about following spec's. There is many ways to deal with such things - for example introduce dither but this is completely different topic.
Btw - this is visible only when 10 bit source meet 8 bit receiver - usually broadcasters using 10 bit equipment (like video encoders) and customers don't care.

OP issue is lack of proper arithmetic - clamping will solve issue. Such problem is described by R&S paper.
22. you need to clip your values between 0 and 1.
We covered that a week ago, Pandy. All is well now.

Today we're focused on this code from Wikipedia which is giving wrong colors:

Code:
R = 1 * Y' +1.28033 * v

G = 1 * Y' + -0.21482 * u + -0.38059 * v

B = 1 * Y' + 2.12798 * u + 0 * v
23. Originally Posted by chris319
you need to clip your values between 0 and 1.
We covered that a week ago, Pandy. All is well now.

Today we're focused on this code from Wikipedia which is giving wrong colors:

Code:
R = 1 * Y' +1.28033 * v

G = 1 * Y' + -0.21482 * u + -0.38059 * v

B = 1 * Y' + 2.12798 * u + 0 * v
You asked i replied even if this is week after (sorry but currently it is a bit hectic time for me and can't be on VH so frequently as before).

quantization range? coefficients from ITU standards are for 16,235 (64,940) - this one perhaps is for 0,255?
Are you able to use colour picker and read vales for RGB decoded correctly and not correctly (accordingly to you)?

Wiki doesn't provide source for those coefficients (this was my remark about developers) - some people simply don't care...
24. Are you able to use colour picker and read values for RGB decoded correctly and not correctly
Yes. I don't guess at the values.

The coefficients are for BT.709. Follow the link. It explicitly says BT.709:

https://en.wikipedia.org/wiki/YUV#HDTV_with_BT.709

coefficients from ITU standards are for 16,235 (64,940) - this one perhaps is for 0,255
There is one set of BT.709 coefficients for either 16-235 or 0-255.
25. Originally Posted by chris319
Yes. I don't guess at the values.
Are you able to provide those values?

Originally Posted by chris319

The coefficients are for BT.709. Follow the link. It explicitly says BT.709:

https://en.wikipedia.org/wiki/YUV#HDTV_with_BT.709
I prefer to follow more reputable sources than Wikipedia - that's why I've provided you standards. Perhaps my comment on developers was harsh but this is outcome of my experience - if some developer feel offended - apologies.

Originally Posted by chris319
coefficients from ITU standards are for 16,235 (64,940) - this one perhaps is for 0,255
There is one set of BT.709 coefficients for either 16-235 or 0-255.
Performed quick test (color bar 100/75, Open Offce Calc) with your (wiki) coefficients and those coefficients are incorrect, they don't provide reversible RGB<>YCbCr for ITU 709 (limited quantization range)...

Attaching two files, xilinx app with detailed information about RGB <> YCbCr conversion (seems HW guys knows how to do things) and my test for conversion from RGB to YCbCr to RGB with your (wiki) incorrect coefficients.
Seem main coefficients for 709 are different for limited (16,235) Kr=0.2126, Kb=0.0722 and full (0,255) quantization range Kr=0.1819, Kb=0.0618.

26. Are you able to provide those values?
I can summarize by saying they are generally +/- 1 or 2 of the actual value. The error is due to rounding. Bear in mind that there is rounding error in the encoding and again in the decoding.

Performed quick test (color bar 100/75, Open Offce Calc) with your (wiki) coefficients and those coefficients are incorrect, they don't provide reversible RGB<>YCbCr for ITU 709 (limited quantization range)...
We've already established that. I think it best to disregard that code.

Could you please post some compilable code encompassing this theory in the form I am already using? Here again is the code I am using now and the source of it (avisynth). If this code is not optimal then I'm sure the avisynth folks would like to know about it.

Code:
;http://avisynth.nl/index.php/Color_conversions
rf.f = (255/219)*y + (255/112)*v*(1-#Kr) - (255*16/219 + 255*128/112*(1-#Kr))
gf.f = (255/219)*y - (255/112)*u*(1-#Kb)*#Kb/#Kg - (255/112)*v*(1-#Kr)*#Kr/#Kg - (255*16/219 - 255/112*128*(1-#Kb)*#Kb/#Kg - 255/112*128*(1-#Kr)*#Kr/#Kg)
bf.f = (255/219)*y + (255/112)*u*(1-#Kb) - (255*16/219 + 255*128/112*(1-#Kb))

If rf > 255: rf = 255:EndIf
If gf > 255: gf = 255:EndIf
If bf > 255: bf = 255:EndIf

If rf < 0:rf = 0:EndIf
If gf < 0:gf = 0:EndIf
If bf < 0:bf = 0:EndIf
In PureBasic the ".f" declares a variable as a float. The "#" signifies a constant.

Let us assume a data range of 0 - 255 (not confined to 16 - 235).
27. Originally Posted by chris319
Are you able to provide those values?
I can summarize by saying they are generally +/- 1 or 2 of the actual value. The error is due to rounding. Bear in mind that there is rounding error in the encoding and again in the decoding.
Ok, so seem errors are related to encoding/decoding and you understand their nature thus not a problem.

Originally Posted by chris319
We've already established that. I think it best to disregard that code.
Perfect then - seem here is no issue, wikipedia has some equations but those equations are incorrect (unknown purpose).
That's why i provided you ITU as reference not wikipedia (don't get me wrong - i like wikipedia however articles related to colour space and colour space conversions are not best ones).

Originally Posted by chris319
Could you please post some compilable code encompassing this theory in the form I am already using? Here again is the code I am using now and the source of it (avisynth). If this code is not optimal then I'm sure the avisynth folks would like to know about it.

Code:
;http://avisynth.nl/index.php/Color_conversions
rf.f = (255/219)*y + (255/112)*v*(1-#Kr) - (255*16/219 + 255*128/112*(1-#Kr))
gf.f = (255/219)*y - (255/112)*u*(1-#Kb)*#Kb/#Kg - (255/112)*v*(1-#Kr)*#Kr/#Kg - (255*16/219 - 255/112*128*(1-#Kb)*#Kb/#Kg - 255/112*128*(1-#Kr)*#Kr/#Kg)
bf.f = (255/219)*y + (255/112)*u*(1-#Kb) - (255*16/219 + 255*128/112*(1-#Kb))

If rf > 255: rf = 255:EndIf
If gf > 255: gf = 255:EndIf
If bf > 255: bf = 255:EndIf

If rf < 0:rf = 0:EndIf
If gf < 0:gf = 0:EndIf
If bf < 0:bf = 0:EndIf
In PureBasic the ".f" declares a variable as a float. The "#" signifies a constant.

Let us assume a data range of 0 - 255 (not confined to 16 - 235).
I never said it is not optimal - i suggested to follow standard not relay on code that may have issues as frequently such equations are blindly (wiki code example - this can be OK but for special case not for regular use) copied.
I didn't check this code as one of things that hit me first was wrong signal naming convention - YUV is present ONLY in Composite video signal encoders/decoders - see no justification to use YUV as synonym for YCbCr or YPbPr except software PAL encoders/decoders which is not your case.
This code should work under some limitations - it expect Limited Quantization Range YCbCr (Y 16..235 ; Cb, Cr 16..240) and produce Full Quantization Range RGB output (0..255). It may be suboptimal when speed is your goal but i assume with modern CPU's capabilities this is not big issue.
Perhaps it is better (as code seem to use floats and provide sufficient precision) to remove scaling coefficients (255/219, 255/112) and rescale RGB components explicitly later but this is cosmetics (may improve code readability and increase flexibility- code should be able to deal with Full Quantization Range YCbCr data - be aware that in video world Full Scale YCbCr is usually 1..254 not 0..255 thus scaling coefficient like 255/254 may be required anyway if your intention is to get Full Scale RGB).
28. Ok, so seem errors are related to encoding/decoding and you understand their nature thus not a problem.
Due to rounding, not encoding/decoding.

This code should work under some limitations - it expect Limited Quantization Range YCbCr (Y 16..235 ; Cb, Cr 16..240)
Ah, this could be causing me problems as I am working with video from a camcorder which puts out > 235 and < 16.

How would you rewrite this code to handle 0 - 255? My math is not the greatest so I could use some help. The camcorder does put out 0 and 255 as it is intended for consumers, not for broadcast.

Code:
rf = Int((255/219)*yf + (255/112)*Crf*(1-#Kr) - (255*16/219 + 255*128/112*(1-#Kr))+0.5)

gf = Int((255/219)*yf - (255/112)*Cbf*(1-#Kb)*#Kb/#Kg - (255/112)*Crf*(1-#Kr)*#Kr/#Kg - (255*16/219 - 255/112*128*(1-#Kb)*#Kb/#Kg - 255/112*128*(1-#Kr)*#Kr/#Kg)+0.5)

bf = Int((255/219)*yf + (255/112)*Cbf*(1-#Kb) - (255*16/219 + 255*128/112*(1-#Kb))+0.5)
29. Originally Posted by chris319
Due to rounding, not encoding/decoding.
Don't get me wrong but colour space conversion is form of encoding and rounding itself is a form of error encoding - agree - semantics is important but encoder/decoder is black box with appropriate abstraction layer from your issue perspective...

Originally Posted by chris319
Ah, this could be causing me problems as I am working with video from a camcorder which puts out > 235 and < 16.
Yes and this is clearly specified on provided by you Avisynth wiki

Originally Posted by chris319
How would you rewrite this code to handle 0 - 255? My math is not the greatest so I could use some help. The camcorder does put out 0 and 255 as it is intended for consumers, not for broadcast.
Such code is provided on same Avisynth page - yuv [0,255] <-> rgb [0,255] (0 <= [r,g,b] <= 255, 0 <= y <= 255, 0 <= [u,v] <= 255)

Code:
r = y + 2*(v-128)*(1-Kr)

g = y - 2*(u-128)*(1-Kb)*Kb/Kg - 2*(v-128)*(1-Kr)*Kr/Kg

b = y + 2*(u-128)*(1-Kb)
You can rescale RGB later if your YCbCr data are range limited.
You can also pre-process YCbCr and apply clamp before (limit Y between 16 and 235; Cb, Cr between 16, 240).
30. Code:
r = y + 2*(v-128)*(1-Kr)

g = y - 2*(u-128)*(1-Kb)*Kb/Kg - 2*(v-128)*(1-Kr)*Kr/Kg

b = y + 2*(u-128)*(1-Kb)
As noted previously in this thread, this code gives inaccurate colors.

I have reworked my test pattern to include patches of #FFFFFF and #000000. This code gives accurate colors over the range #000000 through #FFFFFF, so I think we're good.

https://youtu.be/69FU5Sjw0oc

Code:
rf = (255/219)*y + (255/112)*v*(1-#Kr) - (255*16/219 + 255*128/112*(1-#Kr))
gf = (255/219)*y - (255/112)*u*(1-#Kb)*#Kb/#Kg - (255/112)*v*(1-#Kr)*#Kr/#Kg - (255*16/219 - 255/112*128*(1-#Kb)*#Kb/#Kg - 255/112*128*(1-#Kr)*#Kr/#Kg)
bf = (255/219)*y + (255/112)*u*(1-#Kb) - (255*16/219 + 255*128/112*(1-#Kb))

If rf > 255: rf = 255:EndIf
If gf > 255: gf = 255:EndIf
If bf > 255: bf = 255:EndIf

If rf < 0:rf = 0:EndIf
If gf < 0:gf = 0:EndIf
If bf < 0:bf = 0:EndIf
I can change the variable names from u, v to Cb, Cr.