Removing the *4 and /4 scaling, here is what I get when testing from 0 to 255:
Testing round trip...
16777216 colors tested
14133458 colors differed
Testing from 16 to 235 gives:
Testing round trip...
10648000 colors tested
8980712 colors differed
Try StreamFab Downloader and download from Netflix, Amazon, Youtube! Or Try DVDFab and copy Blu-rays! or rip iTunes movies!
+ Reply to Thread
Results 31 to 60 of 71
Thread
-
-
That's exactly what you expect to see. Like I said, roughly six RGB colors map to each legal YUV color when using 8 bit limited range YUV.
Consider this one dimensional list of 12 numbers:
Code:0 1 2 3 4 5 6 7 8 9 10 11
Code:0 0 0 0 0 0 1 1 1 1 1 1
Code:0 0 0 0 0 0 6 6 6 6 6 6
That's what's happening in the conversion of 8 bit RGB to 8 bit limited range YUV, except that it's happening in three dimensions, not one.Last edited by jagabo; 7th Feb 2017 at 09:47.
-
Agreed there will be some errors, but we still don't know for sure the magnitude of those errors. A few people have made educated guesses.
-
I worked it out in PureBasic. The errors are <= 17, or about 6.6% (17/255).
Code:;UPDATED 2/7/2017 OpenConsole() ;BT.709 CONSTANTS ;#Kr = 0.2126: #Kg = 0.7152: #Kb = 0.0722 ;REC 601 CONSTANTS ;#KR = 0.299: #KG = 0.587: #KB = 0.114 ;NTSC CONSTANTS ;#KR = 3: #KG = 6: #KB = 1 ;BT.2020 CONSTANTS #Kr= 0.2627 : #Kg= 0.678 : #Kb= 0.0593 error = 0 #err = 17 Global Rd.w,Gd.w,Bd.w,R1.w,G1.w,B1.w Global U.b, V.b, Y.a Procedure rgb_to_yuv() Y = #Kr*R1 + #Kg*G1 + #Kb*B1 U = (B1*#Kb - Y) / 2 V = (R1*#Kr - Y) / 2 EndProcedure Procedure yuv_to_rgb() Ut.w = U * 2 Vt.w = V * 2 Rd= ((Y+Vt)/#Kr) + 0.5 Gd= (-(Y + Vt + Ut)/#Kg) + 0.5 Bd= ((Y + Ut)/#Kb) + 0.5 If Rd > 255: Rd = 255 ElseIf Rd < 0: Rd = 0 ElseIf Gd > 255: Gd = 255 ElseIf Gd < 0: Gd = 0 ElseIf Bd > 255: Bd = 255 ElseIf Bd < 0: Bd = 0 EndIf EndProcedure For R1 = 0 To 255 For G1 = 0 To 255 For B1 = 0 To 255 rgb_to_yuv(): yuv_to_rgb() If R1-Rd > #err Or Rd-R1 > #err Or G1-Gd > #err Or Gd-G1 > #err Or B1-Bd > #err Or Bd-B1 > #err error + 1 EndIf Next Next Next PrintN("Errors: "+Str(error)) Repeat Delay(100) Until Inkey() <> ""
-
The errors drop to <= 9 if BT.601 coefficients are used, or about 3.5% (9/255).
-
Last edited by jagabo; 7th Feb 2017 at 12:49.
-
Your program uses 16-bit ints for Y, U and V and gets the errors down to zero. The errors creep in when going from 16 to 8 bits for Y, U and V. As you said,
The issue is that you can't go from 8 bit integer RGB to 8 bit integer YUV and back to 8 bit integer RGB without errors.Last edited by chris319; 7th Feb 2017 at 15:06.
-
Just because it uses16 bit shorts to hold the YUV values doesn't mean the YUV values are 16 bit. It uses only 10 of the 16 bits (ie, the values are always between 0 and 1023). If you remove the *4 and /4 (or change them to *1 and /1) then it only uses 8 of the 16 bits.
At 10 bits the round trip is 100 percent lossless. At 8 bits about 5/6 of the RGB values differ from the original. Never by more that 2 units of R, G, and/or B.
Because it's already written. All you have to do is remove the *4 and /4.Last edited by jagabo; 7th Feb 2017 at 15:30.
-
Here is a program written in PureBasic which analyzes the errors in RGB/YUV round-trip conversions. It uses the avisynth code described here: http://avisynth.nl/index.php/Color_conversions
The maximum error for each of the R, G and B channels is +/- 1 for any set of coefficients (601, 709, 2020, NTSC). This is not a count of the errors; it is the maximum deviation from the original RGB values.
Good enough for me.
Code:OpenConsole() ;BT.709 CONSTANTS #Kr = 0.2126: #Kg = 0.7152: #Kb = 0.0722 ;REC 601 CONSTANTS ;The Cb And Cr samples are unsigned ;#KR = 0.299: #KG = 0.587: #KB = 0.114 ;NTSC CONSTANTS ;#KR = 0.30: #KG = 0.59: #KB = 0.11 ;BT.2020 CONSTANTS ;#Kr= 0.2627 : #Kg= 0.678 : #Kb= 0.0593 Maxrederror = 0 Maxgreenerror = 0 Maxblueerror = 0 Global Rd.a,Gd.a,Bd.a,R1.a,G1.a,B1.a Global U.a, V.a, Y.a Procedure rgb_to_yuv() ;avisynth code ;http://avisynth.nl/index.php/Color_conversions Y = #Kr*R1 + #Kg*G1 + #Kb*b1 V = 0.5*R1 - 0.5*G1*#Kg/(1-#Kr) - 0.5*b1*#Kb/(1-#Kr) + 128 U = -0.5*R1*#Kr/(1-#Kb) - 0.5*G1*#Kg/(1-#Kb) + 0.5*B1 + 128 EndProcedure Procedure yuv_to_rgb() ;avisynth code ;http://avisynth.nl/index.php/Color_conversions Rd = y + 2*(v-128)*(1-#Kr) Gd = y - 2*(u-128)*(1-#Kb)*#Kb/#Kg - 2*(v-128)*(1-#Kr)*#Kr/#Kg Bd = y + 2*(u-128)*(1-#Kb) EndProcedure G1=0:B1=0 For R1 = 1 To 254 rgb_to_yuv(): yuv_to_rgb() If Rd-R1 > maxRedError: maxRedError = Rd-R1:EndIf If R1-Rd > maxRedError: maxRedError = R1-Rd:EndIf Next PrintN("Max red error: "+Str(maxRedError)) PrintN(StrF(maxredError*100 / 235,2)+"%") Print(Chr(10)) R1=0:B1=0 For G1 = 1 To 254 rgb_to_yuv(): yuv_to_rgb() If Gd-G1 > maxGreenError: maxGreenError = Gd-G1:EndIf If G1-Gd > maxGreenError: maxGreenError = G1-Gd:EndIf Next PrintN("Max green error: "+Str(maxGreenError)) PrintN(StrF(maxgreenError*100 / 235,2)+"%") Print(Chr(10)) R1=0:G1=0 For B1 = 1 To 254 rgb_to_yuv(): yuv_to_rgb() If Bd-B1 > maxBlueError: maxBlueError = Bd-B1:EndIf If B1-Bd > maxBlueError: maxBlueError = B1-Bd:EndIf Next PrintN("Max blue error: "+Str(maxBlueError)) PrintN(StrF(maxblueError*100 / 235,2)+"%") Repeat Delay(100) Until Inkey() <> ""
-
You only tested pure reds, pure greens, and pure blues. Only 763 of the >16M colors.
-
Good catch. Take a look at this. Here are the results:
Colors tested: 16387064
Max red error: 1
0.39%
Max green error: 1
0.39%
Max blue error: 1
0.39%
Code:OpenConsole() ;BT.709 CONSTANTS #Kr = 0.2126: #Kg = 0.7152: #Kb = 0.0722 ;REC 601 CONSTANTS ;The Cb And Cr samples are unsigned ;#KR = 0.299: #KG = 0.587: #KB = 0.114 ;NTSC CONSTANTS ;#KR = 0.30: #KG = 0.59: #KB = 0.11 ;BT.2020 CONSTANTS ;#Kr= 0.2627 : #Kg= 0.678 : #Kb= 0.0593 Maxrederror = 0 Maxgreenerror = 0 Maxblueerror = 0 Global Rd.a,Gd.a,Bd.a,R1.a,G1.a,B1.a Global U.a, V.a, Y.a Procedure rgb_to_yuv() ;avisynth code ;http://avisynth.nl/index.php/Color_conversions Y = #Kr*R1 + #Kg*G1 + #Kb*b1 V = 0.5*R1 - 0.5*G1*#Kg/(1-#Kr) - 0.5*b1*#Kb/(1-#Kr) + 128 U = -0.5*R1*#Kr/(1-#Kb) - 0.5*G1*#Kg/(1-#Kb) + 0.5*B1 + 128 EndProcedure Procedure yuv_to_rgb() ;avisynth code ;http://avisynth.nl/index.php/Color_conversions Rd = y + 2*(v-128)*(1-#Kr) Gd = y - 2*(u-128)*(1-#Kb)*#Kb/#Kg - 2*(v-128)*(1-#Kr)*#Kr/#Kg Bd = y + 2*(u-128)*(1-#Kb) EndProcedure R1=1:G1=1:B1=1:count = 0 For Rct = 1 To 254 For Gct = 1 To 254 For Bct = 1 To 254 R1 = Rct: G1 = Gct: B1 = Bct rgb_to_yuv(): yuv_to_rgb() If Rd-R1 > maxRedError: maxRedError = Rd-R1:EndIf If R1-Rd > maxRedError: maxRedError = R1-Rd:EndIf If Gd-G1 > maxGreenError: maxGreenError = Gd-G1:EndIf If G1-Gd > maxGreenError: maxGreenError = G1-Gd:EndIf If Bd-B1 > maxBlueError: maxBlueError = Bd-B1:EndIf If B1-Bd > maxBlueError: maxBlueError = B1-Bd:EndIf count + 1 Next Next Next PrintN("Colors tested: " + Str(count)+Chr(10)) PrintN("Max red error: "+Str(maxRedError)) PrintN(StrF(maxredError*100 / 254,2)+"%") Print(Chr(10)) PrintN("Max green error: "+Str(maxGreenError)) PrintN(StrF(maxgreenError*100 / 254,2)+"%") Print(Chr(10)) PrintN("Max blue error: "+Str(maxBlueError)) PrintN(StrF(maxblueError*100 / 254,2)+"%") Repeat: Delay(100): Until Inkey() <> ""
Last edited by chris319; 17th Feb 2017 at 20:27.
-
I'm not real familiar with Visual Basic but shouldn't you explicitly cast you variables as integers?
-
PureBasic, not Visual Basic.
PureBasic is variable challenged. The only unsigned variables available are 8-bit bytes and 16-bit shorts. All other variables are signed. By default, unless otherwise declared, all variables are 64 bit signed because I'm using the 64-bit version of PB. So maxrederror, maxgreenerror, maxblue error are 64 bits signed. This won't affect the tally of errors, however.
In the lines beginning with "Global" we have Rd.a, etc. The ".a" declares the variable Rd as an unsigned 8-bit byte. The "a" stands for "ascii" as in "ascii character" in the minds of the PureBasic developers. Many PureBasic users have clamored for a full range of unsigned variables but the PB developers can't figure it out.
PB does some behind-the-scenes rounding when going from float to int. What I should do is rewrite this program in Pascal. -
Same thing in Pascal, same result: +/- 1
Pascal doesn't cut you any slack regarding variable types.
Code:const Kr = 0.30; Kg = 0.59; Kb = 0.11; var Y,U,V, R1,G1,B1,Rd,Gd,Bd : byte; maxrederror : longword = 0; maxgreenerror : longword = 0; maxblueerror : longword = 0; count : longword = 0; procedure rgb_to_yuv; begin (*avisynth code http://avisynth.nl/index.php/Color_conversions*) Y := round(Kr*R1 + Kg*G1 + Kb*B1); V := round(0.5*R1 - 0.5*G1*Kg/(1-Kr) - 0.5*B1*Kb/(1-Kr) + 128); U := round(-0.5*R1*Kr/(1-Kb) - 0.5*G1*Kg/(1-Kb) + 0.5*B1 + 128); end; procedure yuv_to_rgb; begin (*avisynth code http://avisynth.nl/index.php/Color_conversions*) Rd := round(Y + 2*(v-128)*(1-Kr)); Gd := round(Y - 2*(u-128)*(1-Kb)*Kb/Kg - 2*(v-128)*(1-Kr)*Kr/Kg); Bd := round(Y + 2*(u-128)*(1-Kb)); end; begin (*main program*) for R1 := 1 to 254 do for G1 := 1 to 254 do for B1 := 1 to 254 do begin rgb_to_yuv; yuv_to_rgb; inc(count); If Rd-R1 > maxRedError then maxrederror := Rd-R1 else if R1-Rd > maxRedError then maxrederror := R1-Rd; If Gd-G1 > maxGreenError then maxGreenerror := Gd-G1 else if G1-Gd > maxGreenError then maxGreenerror := G1-Gd; If Bd-B1 > maxBlueError then maxBlueerror := Bd-B1 else if B1-Bd > maxBlueError then maxBlueerror := B1-Bd; end; writeln('Colors tested: ',count); writeln('Maximum red error: ',maxrederror); writeln('Maximum green error: ',maxGreenerror); writeln('Maximum blue error: ',maxBlueerror); end.
Last edited by chris319; 18th Feb 2017 at 05:41.
-
Keep in mind that no production software would use floating point for this. They would use scaled integers or lookup tables.
-
Last edited by chris319; 18th Feb 2017 at 03:16.
-
Nobody would use floating point coefficients to do this in production code that works with large amounts of data. They would use scaled integers or table lookups. Integer math is on the order of 10 times faster than floating point. For example:
Code:Y := round(Kr*R1 + Kg*G1 + Kb*B1);
Code:;#KR = 0.299: #KG = 0.587: #KB = 0.114 each multiplied by 65536 Y := (19595*R1 + 38470*G1 + 7471*B1) / 65536;
-
Avisynth actually uses scaled integer math (convert.h)
Code:inline void YUV2RGB(int y, int u, int v, BYTE* out) { const int crv = int(1.596*65536+0.5); const int cgv = int(0.813*65536+0.5); const int cgu = int(0.391*65536+0.5); const int cbu = int(2.018*65536+0.5); int scaled_y = (y - 16) * int((255.0/219.0)*65536+0.5); out[0] = ScaledPixelClip(scaled_y + (u-128) * cbu); // blue out[1] = ScaledPixelClip(scaled_y - (u-128) * cgu - (v-128) * cgv); // green out[2] = ScaledPixelClip(scaled_y + (v-128) * crv); // red } //... inline int RGB2YUV(int rgb) { const int cyb = int(0.114*219/255*65536+0.5); const int cyg = int(0.587*219/255*65536+0.5); const int cyr = int(0.299*219/255*65536+0.5); // y can't overflow int y = (cyb*(rgb&255) + cyg*((rgb>>8)&255) + cyr*((rgb>>16)&255) + 0x108000) >> 16; int scaled_y = (y - 16) * int(255.0/219.0*65536+0.5); int b_y = ((rgb&255) << 16) - scaled_y; int u = ScaledPixelClip((b_y >> 10) * int(1/2.018*1024+0.5) + 0x800000); int r_y = (rgb & 0xFF0000) - scaled_y; int v = ScaledPixelClip((r_y >> 10) * int(1/1.596*1024+0.5) + 0x800000); return ((y*256+u)*256+v) | (rgb & 0xff000000); } //... static __inline BYTE ScaledPixelClip(int i) { return PixelClip((i+32768) >> 16); }
Last edited by raffriff42; 18th Feb 2017 at 10:46.
-
raffriff: are you able to compile and execute this code? Could you add my checking code and test it for accuracy?
-
The code snippet I posted will not compile. It is part of the AviSynth project with many unknown (to me) dependencies.
I have never done it, but compiling the complete AviSynth project is doable with some work:
http://avisynth.nl/index.php/Filter_SDK/Compile_AviSynth -
It has this ScaledPixelClip() function which I don't understand, either.
Last edited by chris319; 19th Feb 2017 at 00:59.
-
Oh, now I get it. (internal.h)
Code:static __inline BYTE ScaledPixelClip(int i) { return PixelClip((i+32768) >> 16); }
Code:class _PixelClip { enum { buffer=320 }; BYTE clip[256+buffer*2]; public: _PixelClip() { memset(clip, 0, buffer); for (int i=0; i<256; ++i) clip[i+buffer] = (BYTE)i; memset(clip+buffer+256, 255, buffer); } BYTE operator()(int i) { return clip[i+buffer]; } }; extern _PixelClip PixelClip;
Code:for (int i=0; i<256; ++i) clip[i+buffer] = (BYTE)i;
Code:return PixelClip((i+32768) >> 16);
Code:BYTE operator()(int i) { return clip[i+buffer]; }
Code:static __inline BYTE PixelClip(int i) { return (i<=0) ? (BYTE)0 : (i>=255) ? (BYTE)255 : (BYTE)i; }
I don't see any input validation either. Just access an array with a signed int!
(EDIT there is a buffer of 320 bytes located in front of the array -- protection for out of range array access?)Last edited by raffriff42; 20th Feb 2017 at 08:32.
-
Maybe you guys will understand this better than I.
https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&ved=0ahUKEwjN5uqE8JvSA...Ab9ZvQ&cad=rja -
Alright, alright, I hadda do it
Here's the Avisynth code, (using my PixelClip), called by your routine (ported to C++)Code:#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #include <windows.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> /* https://forum.videohelp.com/threads/381298-RGB-to-YUV-to-RGB?p=2477791&viewfull=1#post2477791 */ static __inline BYTE PixelClip(int i) { return (i<=0) ? (BYTE)0 : (i>=255) ? (BYTE)255 : (BYTE)i; } /* http://avisynth2.cvs.sourceforge.net/viewvc/avisynth2/avisynth/src/internal.h?view=markup */ static __inline BYTE ScaledPixelClip(int i) { return PixelClip((i+32768) >> 16); } /* http://avisynth2.cvs.sourceforge.net/viewvc/avisynth2/avisynth/src/convert/convert.h?view=markup */ inline void YUV2RGB(int y, int u, int v, BYTE* out) { const int crv = int(1.596*65536+0.5); const int cgv = int(0.813*65536+0.5); const int cgu = int(0.391*65536+0.5); const int cbu = int(2.018*65536+0.5); int scaled_y = (y - 16) * int((255.0/219.0)*65536+0.5); out[0] = ScaledPixelClip(scaled_y + (u-128) * cbu); // blue out[1] = ScaledPixelClip(scaled_y - (u-128) * cgu - (v-128) * cgv); // green out[2] = ScaledPixelClip(scaled_y + (v-128) * crv); // red } inline int RGB2YUV(int rgb) { const int cyb = int(0.114*219/255*65536+0.5); const int cyg = int(0.587*219/255*65536+0.5); const int cyr = int(0.299*219/255*65536+0.5); // y can't overflow int y = (cyb*(rgb&255) + cyg*((rgb>>8)&255) + cyr*((rgb>>16)&255) + 0x108000) >> 16; int scaled_y = (y - 16) * int(255.0/219.0*65536+0.5); int b_y = ((rgb&255) << 16) - scaled_y; int u = ScaledPixelClip((b_y >> 10) * int(1/2.018*1024+0.5) + 0x800000); int r_y = (rgb & 0xFF0000) - scaled_y; int v = ScaledPixelClip((r_y >> 10) * int(1/1.596*1024+0.5) + 0x800000); return ((y*256+u)*256+v) | (rgb & 0xff000000); } /* https://forum.videohelp.com/threads/381298-RGB-to-YUV-to-RGB?p=2477697&viewfull=1#post2477697 */ BYTE Y,U,V, R1,G1,B1, Rd,Gd,Bd; LONG maxRedError,maxGreenerror,maxBlueError, count; void rgb_to_yuv() { int rgb = ((R1 & 0xff) << 16) + ((G1 & 0xff) << 8) + (B1 & 0xff); int yuv = RGB2YUV(rgb); Y = (yuv & 0xff0000) >> 16; U = (yuv & 0xff00) >> 8; V = (yuv & 0xff); } void yuv_to_rgb() { BYTE b[3]; YUV2RGB(Y, U, V, &b[0]); Rd = b[2]; Gd = b[1]; Bd = b[0]; } int main(int argc, char **argv) { maxRedError=0; maxGreenerror=0; maxBlueError=0; count=0; for (R1=1; R1<=254; R1++) { for (G1=1; G1<=254; G1++) { for (B1=1; B1<=254; B1++) { rgb_to_yuv(); yuv_to_rgb(); count++; int t = abs(Rd-R1); if (t > maxRedError) maxRedError = t; t = abs(Gd-G1); if (t > maxGreenerror) maxGreenerror = t; t = abs(Bd-B1); if (t > maxBlueError) maxBlueError = t; } } } printf("Colors tested: %d\n", count); printf("Maximum red error: %d\n", maxRedError); printf("Maximum green error: %d\n", maxGreenerror); printf("Maximum blue error: %d\n", maxBlueError); } // (end)
Last edited by raffriff42; 16th Mar 2017 at 15:44. Reason: (fixed image link)
-
Note that raffriff42 program (and mine earlier) is using limited range YUV (commonly used in all consumer formats like DVD, Blu-ray, broadcast TV, etc.) whereas chris319's program is using full range YUV. Hence the larger errors in riffraff42's program.
-
Raffriff's error is only higher by 1 in the green channel (2 vs 1 in my code).
Code:BYTE Y,U,V, R1,G1,B1, Rd,Gd,Bd; BYTE b[3];
Mine was never intended to be production code -- just testing.Last edited by chris319; 19th Feb 2017 at 15:26.
-
I am having no luck with the YCgCo code described here: https://www.microsoft.com/en-us/research/publication/ycocg-r-a-color-space-with-rgb-re...dynamic-range/
Do either of you spot a problem with my encoding/decoding? I am able to get a passable monochrome image from it.
Code:sub rgb_to_yuv () Rf = R1 / 255 Gf = G1 / 255 Bf = B1 / 255 Cof = Rf - Bf Cgf = Gf - (Rf + Bf) / 2 'Gf - 1 Yf = Gf/2 + (Rf + Bf) / 4 end sub sub yuv_to_rgb() dim as single temp Rf = Yf + Cof - Cgf Gf = Yf + Cgf Bf = Yf - Cof - Cgf Rd = Rf * 255 Gd = Gf * 255 Bd = Bf * 255
-
It works exactly the same. I didn't show the code but I verified the R, G, B, Y, U, and V primaries never exceeded their expected ranges. I used "int" because the original code I was working with could optionally test different bit depths. 8 bit, 9 bit, and 10 bit. Hence it needed to use a bigger type than "unsigned char".
Similar Threads
-
ffmpeg/x264 RGB to YUV
By SameSelf in forum Video ConversionReplies: 40Last Post: 14th Nov 2016, 18:40 -
YUV/ RGB problem in Avisynth
By spiritt in forum Newbie / General discussionsReplies: 9Last Post: 6th Sep 2015, 04:31 -
is this YUV or RGB?
By marcorocchini in forum Newbie / General discussionsReplies: 2Last Post: 20th Apr 2014, 10:21 -
MENCODER: how to convert from YUV to RGB?
By marcorocchini in forum Newbie / General discussionsReplies: 1Last Post: 19th Dec 2013, 13:24 -
YUV 4:2:0 to RGB
By toshyuki in forum ProgrammingReplies: 15Last Post: 8th Oct 2012, 12:12