Welcome to Doom9's Forum, THE in-place to be for everyone interested in DVD conversion. Before you start posting please read the forum rules. By posting to this forum you agree to abide by the rules.

 Doom9's Forum Converting YUV to RGB formulas?
 Register FAQ Calendar Search Today's Posts Mark Forums Read

 26th January 2024, 07:34 #1  |  Link zerowalker Registered User   Join Date: Jul 2011 Posts: 1,121 Converting YUV to RGB formulas? This is off topic as it's not really for avisynth development, but hopefully it's close enough for a pass, otherwise I can be taken down of course. I'm trying to figure out the correct formula for converting YUV to RGB, both Rec601/Rec709 with Limited/Full range. The ones I have found doesn't seem to match what avisynth produces, apart from one which is this: Code: ```R = 1.164 * (Y - 16) + 1.793 * (U - 128); G = 1.164 * (Y - 16) - 0.534 * (U - 128) - 0.213 * (V- 128); B = 1.164 * (Y - 16) + 2.115 * (V - 128);``` (where R,G,B are rounded when converting to 8bit). This produces as far as i can tell the same results Rec709 Limited. But it doesn't really match the formula shown here: https://en.wikipedia.org/wiki/Y%E2%80%B2UV And i also don't know how to make it full range, except for the Y part. I tried looking at the avisynth code to figure this out, but it was harder than I thought. So was hoping to get some help here if possible
 26th January 2024, 08:26 #2  |  Link Sharc Registered User   Join Date: May 2006 Posts: 3,997 Your matrix above applies for Rec709 limited range Y'CbCr -> Full range RGB conversion. (You may have mixed up U an V though, please double check) U and V are from the analog realm; one should actually be using Cb and Cr in the digital realm. Sometimes interchangeably used, unfortunately. For conversions you may want to take a look here: https://web.archive.org/web/20120403...rmatConversion or check out the tool here: https://res18h39.netlify.app/color Last edited by Sharc; 26th January 2024 at 11:13. Reason: Link added
 26th January 2024, 13:57 #3  |  Link Gavino Avisynth language lover   Join Date: Dec 2007 Location: Spain Posts: 3,433 See also here: http://avisynth.nl/index.php/Color_conversions __________________ GScript and GRunT - complex Avisynth scripting made easier
 26th January 2024, 15:41 #4  |  Link DTL Registered User   Join Date: Jul 2018 Posts: 1,159 Correct equations are in the free available ITU documents (601/709/2020) . AVS+ narrow range in current builds is not completely correct (waiting for pinterf patch merging). There possible very many forms of simplified equations for each use case and it is not easy to check the result. So if current ready to use simplified equations not provide correct/expected values - it is good to start from official documents from ITU. Simple C-form (reference) in the decodeYUVtoRGB plugin - https://github.com/DTL2020/ConvertYU...eYV12toRGB.cpp iY = *l_srcp_Y + Ybias; // Ybias = -16 iU = *l_srcp_U + UVbias; // UVbias=-128 iV = *l_srcp_V + UVbias; // UVbias=-128 int iR = iY + ((iV * Kr) >> 13); int iB = iY + ((iU * Kb) >> 13); int iG = iY - ((iU * Kgu) >> 13) - ((iV * Kgv) >> 13); and Kr, Kgu, Kgb, Kb /* Computing of (Kr, Kgu, Kgv, Kb) from (Yr and Yb) coefficients of YUV colour system: Yr Yg = 1.0 - (Yr + Yb) Yb Kb = (1.0 - Yb) * 2 Kr = (1.0 - Yr) * 2 Kgu = (Yb * Kb) / Yg Kgv = (Yr * Kr) / Yg for Digital UV (601/709/2020): RatioUVtoY = 219.0/224.0 Kr,Kb,Kgu,Kgv *= RatioUVtoY */ if (Matrix == 0) // 601 { fYr = 0.299f; fYb = 0.114f; } else if (Matrix == 1) // 709 { fYr = 0.2126f; fYb = 0.0722f; } else if (Matrix == 2) // 2020 (ncl ?) { fYr = 0.2627f; fYb = 0.0593f; } float fKb = (1.0f - fYb) * 2.0f; float fKr = (1.0f - fYr) * 2.0f; float fYg = 1.0f - (fYr + fYb); Kr = (short)(fKr * fRatioUVtoY * UVgain * mulfac + 0.5f); Kb = (short)(fKb * fRatioUVtoY * UVgain * mulfac + 0.5f); Kgu = (short)(((fYb * fKb) / fYg) * fRatioUVtoY * UVgain * mulfac + 0.5f); Kgv = (short)(((fYr * fKr) / fYg) * fRatioUVtoY * UVgain * mulfac + 0.5f); UVgain=1.0, mulfac = double(1 << int_arith_shift); // integer aritmetic precision scale Last edited by DTL; 27th January 2024 at 22:28.
26th January 2024, 17:27   #5  |  Link
pinterf
Registered User

Join Date: Jan 2014
Posts: 2,322
Quote:
 Originally Posted by DTL AVS+ narrow range in current builds is not completely correct (waiting for pinterf patch merging).
Actually, there was a patch on this area.

27th January 2024, 08:21   #6  |  Link
zerowalker
Registered User

Join Date: Jul 2011
Posts: 1,121
Quote:
 Originally Posted by Sharc Your matrix above applies for Rec709 limited range Y'CbCr -> Full range RGB conversion. (You may have mixed up U an V though, please double check) U and V are from the analog realm; one should actually be using Cb and Cr in the digital realm. Sometimes interchangeably used, unfortunately. For conversions you may want to take a look here: https://web.archive.org/web/20120403...rmatConversion or check out the tool here: https://res18h39.netlify.app/color
Always been confused with YUV and Y'CbCr,
Thought Y'CbCr was "Component Video" and YUV was, well the normal stuff, both Digital and Analogue where it's Y, U and V.

@DTV

Will try it out, was more steps there than "usual", will try to find the ITU documentations as well for the equations.

Last edited by zerowalker; 27th January 2024 at 08:24.

27th January 2024, 12:31   #7  |  Link
Sharc
Registered User

Join Date: May 2006
Posts: 3,997
Quote:
 Originally Posted by zerowalker Always been confused with YUV and Y'CbCr, Thought Y'CbCr was "Component Video" and YUV was, well the normal stuff, both Digital and Analogue where it's Y, U and V.
RGB or YUV or Y'CbCr or Y'PbPr all belong to "component" video.
The symbols U and V are sometimes used quite loosely. Avisynth adopted U and V for the digital realm (probably for convenience and historic reasons) rather than Cb and Cr. For the discussion here you can assume U=Cb and V=Cr.

 27th January 2024, 18:04 #8  |  Link DTL Registered User   Join Date: Jul 2018 Posts: 1,159 "Thought Y'CbCr was "Component Video"" Most important difference between Analog (abstract YUV or defined in 0..XXX mV voltage range typically with zero black and zero UV for math zero value) and Digital YUV (CbCr for Coded B-Y and R-Y?) in storage and transfer domains data form. The 601/709/2020 recs are about digital exchange formats for motion pictures - not only abstract YUV-descriptions. Analog YUV looks like exist in the equal scale Y and UV signals (as provided from RGB->YUV matrix). Digital YUV uses quantization and some Digital YUV systems uses non-equal scale for Y and UV data in the output (storage and transfer/distribution/interfacing) form. For performance of processing in lowest possibe steps number real YUV<->RGB equations are significantly compacted. Real steps are: 1. Decode YUV from Digital YUV storage domain into abstract YUV 0..1 float domain. 2. Apply YUV->RGB matrix conversion in abstract 0..1 float domain. 3. Convert (encode) RGB into output digital domain (or other required at output). Some designers with good understanding in math can warp 1+2+3 in the single step processing by some compute engine. It make best processing performance but may suffer from issues if something important is missing. And also harder or impossible to debug in steps. The 601/709/2020 recs define Digital (quantized form) YUV domain as having non-equal scale for Y and UV data (219 for Y and 224 for UV/CbCr). It may be designed to have a bit less quantization noise in UV low-bit data with natural colour gamuts for these systems (or for YUV as having larger colour gamut in compare with RGB). So the very typical error for convertor designers is missing Y and UV input data normalization to equal scale. It is 219/224 ratio in the math displayed. The resulted error is not very big but visible even in 8bit samples. Last edited by DTL; 27th January 2024 at 22:30.
29th January 2024, 07:32   #9  |  Link
zerowalker
Registered User

Join Date: Jul 2011
Posts: 1,121
Quote:
 Originally Posted by DTL "Thought Y'CbCr was "Component Video"...
Thanks for the explanation

When doing the y offset (Y-16) in float (0-1).
Isn't it (Y-(16/255))?

Cause when I do this Rec709 is valid,
but Rec601 is wrong by a tiny amount,
but (Y-(16/256)) makes it valid.

(valid as in, identical to avisynth).

EDIT:

Okay even PC.601 becomes a tiny bit wrong.

EDIT 2:
Okay it seems this happens even with ffmpeg, is it cause some small math error in avisynths conversion?

Last edited by zerowalker; 29th January 2024 at 16:58.

 29th January 2024, 17:20 #10  |  Link DTL Registered User   Join Date: Jul 2018 Posts: 1,159 "When doing the y offset (Y-16) in float (0-1). Isn't it (Y-(16/255))?" 0..1 range in float is 16 to 235 code values in 8bit Digital YUV (scaled + shifted/offsetted). 235-19=219. Typically we subtract 16 and maps result of 0..219 to 0..1 float (so divide by 219.0). The range above 235 code values in storage Digital YUV 8bit is for super-whites (above nominal 100% white for SDR). If you first convert 0..255 integer to float 0.0..255.0 it maybe also easy to subtract 16.0 and divide result to 219.0 (for performance - multiply to 1/219.0 constant). You will got black and nominal white in 0.0..1.0 range and negative under-blacks and 1.0+ super-whites. For UV it is subtract 128 and divide by 224. See https://www.itu.int/dms_pubrec/itu-r...3-I!!PDF-E.pdf 2.5.3 and 2.5.4 "it seems this happens even with ffmpeg, is it cause some small math error in avisynths conversion?" AVS and ffmpeg may use integer computing with less precision for performance. Try ConvertBits(32) before ConvertToRGB and check output float32 RGB values. See also some posts around https://forum.doom9.org/showthread.p...20#post1987720 about YUV to RGB decoding in AVS. Some +-1 rounding/computing errors still visible in 8bits. As fix maybe used 10bits (12..16) computing and rounding to 8bits with ConvertBits(8). So it is possible to get exactly 16/180 8bit RGB narrow code values for all colours of ColorBarsHD YUV output. Last edited by DTL; 29th January 2024 at 17:49.
 24th April 2024, 02:59 #11  |  Link Argaricolm Registered User   Join Date: Apr 2018 Posts: 39 Formulas for rec 601: R /= 255; G /= 255; B /= 255; Y = R * .299 + G * .587 + B * .114; U = (B - Y) / 1.772 + .5; V = (R - Y) / 1.402 + .5; Here you can do Y,U,V *= 255 to get 8 bit YUV, and round to int. Clamping to 0,255 is not needed. I'v checked all results will be in 0...255 range. and back to RGB: U -= 0.5; V -= 0.5; R = Y + 1.402 * V; G = Y - 0.114 * 1.772 / 0.587 * U - 0.299 * 1.402 / 0.587 * V; B = Y + 1.772 * U; Here you need to do R,G,B *= 255, round to int. If you will not convert from float to int between conversions then they will be lossless. But be sure to use rounding before converting to int (like Math.Round in C#). Don't use just float to int conversion without rounding. WRONG: int r = (int)R; CORRECT: int r = (int)Math.Round(R); Then your conversions will be lossless (checked). Rec 709: Y = 0.2126 * R + 0.7152 * G + 0.0722 * B; U = (B - Y) / 1.8556 + .5; V = (R - Y) / 1.5748 + .5; R = Y + 1.5748 * V; G = Y - 0.0722 * 1.8556 / 0.7152 * U - 0.2126 * 1.5748 / 0.7152 * V; B = Y + 1.8556 * U; Rec 2020: Y = 0.2627 * R + 0.6780 * G + 0.0593 * B; U = (B - Y) / 1.8814 + .5; V = (R - Y) / 1.4746 + .5; R = Y + 1.4746 * V; G = Y - 0.0593 * 1.8814 / 0.6780 * U - 0.2627 * 1.4746 / 0.6780 * V; B = Y + 1.8814 * U; All formulas were tested. When you convert float value to INT you need to use clamping (ensure that value don't go over limit). For example 8 bit limits = 0-255, 10 bit limits 0 - 1023. In normal cases value will not go over limit. But I'v found one source where it happens. Like there is Y = 233 U = 130 V = 142 This makes R to be > 255. I don't know how it happend that source has such values. Maybe there was some other formula used. Or maybe it was just a bug in conversion software that created source. With clamping it looks normal. Last edited by Argaricolm; 28th May 2024 at 22:00.
 28th May 2024, 22:16 #12  |  Link Argaricolm Registered User   Join Date: Apr 2018 Posts: 39 Also I see that there is a question how to convert result to full range. As I'v found the documented correct way to do it is: Convert YUV to RGB floats (without converting them to ints). Then you will have limited RGB but within range 0.0 - 1.0. And now you need to rerange it. You can do it this way for each channel (R,G,B): 8 bit: float lowlimit = 16.0 / 255.0; float range = 219.0 / 255.0; float R = (R - lowlimit) / range; 10 bit: float lowlimit = 64.0 / 1023.0; float range = 879.0 / 1023.0; float R = (R - lowlimit) / range; Then also documentation suggests to do this (midtones correction): if (R <= 1.0 && R >= 0.018) 1.099 * pow(R, 0.45) - 0.099; else if (R < 0.018 R cf >= 0) R *= 4.5; Then you can use formulas above to convert back to YUV (YUV will be full range). Don't forget to set "Full range" in encoder settings so it will set such flag. Or most hardware will treat your YUV video as limited range (in staxrip you can set it in VUI settings). Last edited by Argaricolm; 28th May 2024 at 22:21.
28th May 2024, 22:56   #13  |  Link
DTL
Registered User

Join Date: Jul 2018
Posts: 1,159
Quote:
 Originally Posted by Argaricolm When you convert float value to INT you need to use clamping (ensure that value don't go over limit). For example 8 bit limits = 0-255, 10 bit limits 0 - 1023. In normal cases value will not go over limit. But I'v found one source where it happens. Like there is Y = 233 U = 130 V = 142 This makes R to be > 255. I don't know how it happend that source has such values. Maybe there was some other formula used. Or maybe it was just a bug in conversion software that created source. With clamping it looks normal.
YUV colour space is larger in comparison of RGB. So if you make some legal range YUV from RGB and make some adjustments in the YUV colour space - you may get out of range RGB while trying to convert to RGB. The adjustments may be simple sharpness processing with over/under shoots in YUV.

The possible solution is to keep RGB in float so you will not get integer overflow. But you need to remember this RGB have out of range values and if you need integer RGB output - you need to apply some limiter.

If you read ITU docs this issue also described. And the limiter may be mot very simple RGB clamp to min..max but recommended to make more advanced like attempt to keep hue if possible and degrade saturation first.

So as some advanced YUV->RGB converter it is possible to design not simple clamp-mode but more smart HSB/HSL domain aware transform keeping more important colour data if possible and make less degradation to image.

The order of degragation (errors) is about
1. Saturation (less important ?)
2. Brightness
3. Hue (most important ?).

So the possible algorithm:
If (out of range YUV to RGB triplet detected) -> convert to HSB (?) and use some iterative process to get valid range RGB triplet with minimized delta-Hue and delta-Brightness errors (with some (more ?) weight to delta-Hue ?). It may be some time and compute complex but will give better result in compare with simple clamp to min..max.

Also this smart RGB limiter may be stand alone plugin so you can make YUV to RGB unlimited in RGBPS and process with smart-RGB limiter to go to integer RGB.

Quote:
 Originally Posted by Argaricolm Convert YUV to RGB floats (without converting them to ints). Then you will have limited RGB but within range 0.0 - 1.0.
narrow/limited is sort of only integer low-bits domain term. If you get RGB in 'abstract' 0.0 (black) and 1.0 (nominal white) it is in this 'abstract domain'. Not full and not limited/narrow (because typically floats are unlimited).

To convert 0..1.0 float to full integer RGB you simply multiply to 2^N where n is number of bits in the ingeter encoding. So black will be 0 and nominal white will be 255 in 8bit or 1023 in 10bit. And you will lost both under-blacks and over-whites.

Last edited by DTL; 28th May 2024 at 23:05.

29th May 2024, 12:07   #14  |  Link
wonkey_monkey
Formerly davidh*****

Join Date: Jan 2004
Posts: 2,512
Quote:
 To convert 0..1.0 float to full integer RGB you simply multiply to 2^N where n is number of bits in the [integer] encoding. So black will be 0 and nominal white will be 255 in 8bit or 1023 in 10bit.
Not 2^N-1? And what rounding is most advisable?
__________________
My AviSynth filters / I'm the Doctor

29th May 2024, 14:12   #15  |  Link
DTL
Registered User

Join Date: Jul 2018
Posts: 1,159
Quote:
 Originally Posted by wonkey_monkey Not 2^N-1? And what rounding is most advisable?
Yes - to get 255 from 1.0f it is multiplication to (2^8)-1 = 255. I am typically bad at +-1 errors in many cases. Sorry.

About rounder for float it is typically a good working addition of +0.5f (half of LSB value). So to round 254.8f to 255 integers it is typically passed 255.2+0.5f=255.7f to engine converting float to int. It looks like 'rounding to the nearest'.

Though it is only my current ideas - the industry documents have precomputed tables for 10 (and sometimes 8) bit YUV and RGB values for colour bars test pattern so any math may be checked with these tables.

Also some 'losses' check is for given 180/16 narrow RGB - conversion to (10bit ?) YUV and back to RGB must also return 180/16 RGB.
It is for 180/16 RGB 8bit colour bars:
ConvertBits(10)
ConvertToYUV() // in 10bits or more
ConvertToRGB() // in 10bits or more
ConvertBits(8)

The industry standards typically allow to get 180/16 RGB from 10bit YUV. But with 8bit RGB<->YUV conversion will not be lossless and give about +-1 LSB error.

29th May 2024, 21:20   #16  |  Link
Argaricolm
Registered User

Join Date: Apr 2018
Posts: 39
Quote:
 Originally Posted by Argaricolm Then also documentation suggests to do this (midtones correction): if (R <= 1.0 && R >= 0.018) 1.099 * pow(R, 0.45) - 0.099; else if (R < 0.018 R cf >= 0) R *= 4.5;
Its an OETF function. As I understand from docs its real light -> electronic signal transfer.
There is EOTF function that is reverse of OETF.
But strange thing is that OETF makes image light and EOTF makes it very dark.
Quite interesting results with OETF used after full range...