This is an exercise in converting between 10-bit RGB and YUV using BT.2020 coefficients.
There is a tolerance of +/- 1 between the unconverted RGB value and the post-conversion RGB value to allow for rounding errors.
I have cheated by multiplying the coefficients by 20 in order to bring the error count down, so these are not Kosher YUV values.
It tests RGB values from 0 through 1023 and returns 0 errors, i.e. converted RGB +/- 1 of unconverted RGB.
Feel free to play around with it. It is written in the C language.
Code:#include "..\include\stdio.h" #include "..\include\float.h" //BT.709 CONSTANTS //#define KR 0.2126 //#define KG 0.7152 //#define KB 0.0722 //BT.2020 CONSTANTS #define KR 0.2627 #define KG 0.678 #define KB 0.0593 unsigned short nr,ng,nb,R,G,B; short Y,U,V; //16 bits /************************************************************************/ // Convert RGB to YUV /************************************************************************/ void rgb_to_yuv(void) { double fr, fg, fb, fy, fu, fv; fr = (double)R; fg = (double)G; fb = (double)B; fy = (KR*20)*fr + (KG*20)*fg + (KB*20)*fb; fu = (fb * KB*20) - fy; fv = (fr * KR*20) - fy; Y = (int)fy; U = (int)fu; V = (int)fv; //if (Y > 1023 || U > 1023 || V > 1023) {//printf("%d\n",Y);} } /************************************************************************/ // Convert YUV to RGB /************************************************************************/ void yuv_to_rgb(void) { double fy, fu, fv; //CONVERT TO FLOAT FOR ROUNDING fy = (double)Y; fu = (double)U; fv = (double)V; nr = (int)(((fy+fv)/(KR*20))+0.5); ng = (int)((-(fy + fv + fu)/(KG*20))+0.5); nb = (int)(((fy+fu)/(KB*20))+0.5); } /************************************************************************/ // Test all 1023 RGB values through a round trip // from RGB to YUV and back to RGB. /************************************************************************/ void test_round_trip(void) { int count, diff; count = 0; diff = 0; puts("\nTesting round trip...\n"); for (R=0; R<=1023; R++) { for (G=0; G<=1023; G++) { for (B=0; B<=1023; B++) { rgb_to_yuv(); yuv_to_rgb(); //if ((R != nr) || (G != ng) || (B != nb)) //if ( (R-nr>1) || (R-nr<-1) || (G-ng>1) || (G-ng<-1)||(B-nb>1) || (B-nb<-1) ) int tol = 1; if( (R-nr>tol) || (R-nr<-tol) || (G-ng>tol) || (G-ng<-tol)||(B-nb>tol) || (B-nb<-tol) ) { //printf("%d, %d, %d -> %d, %d, %d\n", R, G, B, nr, ng, nb); diff++; } count++; } } } printf("%d colors tested\n", count); printf("%d colors differed\n", diff); } /************************************************************************/ int main(void) { test_round_trip(); }
+ Reply to Thread
Results 1 to 23 of 23
-
-
-
Are you able to get it to work without that multiplication by 20?
I'm not using the matrix math that you did in your program. I'm not familiar with using matrices. -
Of course not. The problem is the same as when using 8 bit: on average 6 RGB values map to the same YUV values. So you don't get anything near lossless conversion.
If you want your YUV to be interchangeable with other software you need to stick with standards. Converting to 12 or 16 bit YUV would be a better choice. -
you don't get anything near lossless conversion
Converting to 12 or 16 bit YUV would be a better choice. -
Would multiplying by 32 give 16 bits of YUV?
Last edited by chris319; 28th Jun 2016 at 20:08.
-
By the way, your code can be lossless if you round rather than truncate at the end of rgb_to_yuv(). Ie, change:
Code:Y = (int)fy; U = (int)fu; V = (int)fv;
Code:Y = (int)(fy+0.5); if (fu>0.0) U = (int)(fu+0.5); else U = (int)(fu-0.5); if (fv>0.0) V = (int)(fv+0.5); else V = (int)(fv-0.5);
-
Maybe it doesn't matter, but your conversions are missing:
1. The scaling factors
2. The offsets
And,
You should multiply by 2^(b-8) where b = 8, 10, 12 or however many bits. -
On closer inspection, your conversion are just wrong. So I don't know what you are trying to do. I posted the correct conversions a while ago in my lossless thread.
-
-
I posted the correct conversions a while ago in my lossless thread.
So I don't know what you are trying to do.
Here's what I've got now. Y, U and V are rounded, not truncated. It is not totally error-free but the errors are +/- 1.
Code:#include <stdio.h> #include <float.h> //BT.709 CONSTANTS //#define KR 0.2126 //#define KG 0.7152 //#define KB 0.0722 //BT.2020 CONSTANTS #define KR 0.2627 #define KG 0.678 #define KB 0.0593 unsigned short nr,ng,nb,R,G,B; long Y,U,V; //16 bits /************************************************************************/ // Convert RGB to YUV /************************************************************************/ void rgb_to_yuv(void) { double fr, fg, fb, fy, fu, fv; fr = (double)R; fg = (double)G; fb = (double)B; fy = (KR*256)*fr + (KG*256)*fg + (KB*256)*fb; fu = (fb * KB*256) - fy; fv = (fr * KR*256) - fy; Y = (int)(fy+0.5); U = (int)(fu+0.5); V = (int)(fv+0.5); //if (Y > 1023 || U > 1023 || V > 1023) {//printf("%d\n",Y);} } /************************************************************************/ // Convert YUV to RGB /************************************************************************/ void yuv_to_rgb(void) { double fy, fu, fv; //CONVERT TO FLOAT FOR ROUNDING fy = (double)Y; fu = (double)U; fv = (double)V; nr = (int)(((fy+fv)/(KR*256))+0.5); ng = (int)((-(fy + fv + fu)/(KG*256))+0.5); nb = (int)(((fy+fu)/(KB*256))+0.5); } /************************************************************************/ // Test all 1023 RGB values through a round trip // from RGB to YUV and back to RGB. /************************************************************************/ void test_round_trip(void) { int count, diff; count = 0; diff = 0; puts("\nTesting round trip...\n"); for (R=0; R<=1023; R++) { for (G=0; G<=1023; G++) { for (B=0; B<=1023; B++) { rgb_to_yuv(); yuv_to_rgb(); //if ((R != nr) || (G != ng) || (B != nb)) if ( (R-nr>1) || (R-nr<-1) || (G-ng>1) || (G-ng<-1)||(B-nb>1) || (B-nb<-1) ) //int tol = 1; //if( (R-nr>tol) || (R-nr<-tol) || (G-ng>tol) || (G-ng<-tol)||(B-nb>tol) || (B-nb<-tol) ) { //printf("%d, %d, %d -> %d, %d, %d\n", R, G, B, nr, ng, nb); diff++; } count++; } } } printf("%d colors tested\n", count); printf("%d colors differed\n", diff); } /************************************************************************/ int main(void) { test_round_trip(); }
-
Multiplying by 256 gives 100% error free.
Code:#include <stdio.h> #include <float.h> //BT.709 CONSTANTS //#define KR 0.2126 //#define KG 0.7152 //#define KB 0.0722 //BT.2020 CONSTANTS #define KR 0.2627 #define KG 0.678 #define KB 0.0593 unsigned short nr,ng,nb,R,G,B; long Y,U,V; //16 bits /************************************************************************/ // Convert RGB to YUV /************************************************************************/ void rgb_to_yuv(void) { double fr, fg, fb, fy, fu, fv; fr = (double)R; fg = (double)G; fb = (double)B; fy = (KR*256)*fr + (KG*256)*fg + (KB*256)*fb; fu = (fb * KB*256) - fy; fv = (fr * KR*256) - fy; Y = (int)(fy+0.5); U = (int)(fu+0.5); V = (int)(fv+0.5); //if (Y > 1023 || U > 1023 || V > 1023) {//printf("%d\n",Y);} } /************************************************************************/ // Convert YUV to RGB /************************************************************************/ void yuv_to_rgb(void) { double fy, fu, fv; //CONVERT TO FLOAT FOR ROUNDING fy = (double)Y; fu = (double)U; fv = (double)V; nr = (int)(((fy+fv)/(KR*256))+0.5); ng = (int)((-(fy + fv + fu)/(KG*256))+0.5); nb = (int)(((fy+fu)/(KB*256))+0.5); } /************************************************************************/ // Test all 1023 RGB values through a round trip // from RGB to YUV and back to RGB. /************************************************************************/ void test_round_trip(void) { int count, diff; count = 0; diff = 0; puts("\nTesting round trip...\n"); for (R=0; R<=1023; R++) { for (G=0; G<=1023; G++) { for (B=0; B<=1023; B++) { rgb_to_yuv(); yuv_to_rgb(); if ((R != nr) || (G != ng) || (B != nb)) //if ( (R-nr>1) || (R-nr<-1) || (G-ng>1) || (G-ng<-1)||(B-nb>1) || (B-nb<-1) ) //int tol = 1; //if( (R-nr>tol) || (R-nr<-tol) || (G-ng>tol) || (G-ng<-tol)||(B-nb>tol) || (B-nb<-tol) ) { //printf("%d, %d, %d -> %d, %d, %d\n", R, G, B, nr, ng, nb); diff++; } count++; } } } printf("%d colors tested\n", count); printf("%d colors differed\n", diff); } /************************************************************************/ int main(void) { test_round_trip(); }
-
-
8 bit rec.601 and rec.709 define full black as Y=16 and full white as Y=235. So the valid range of Y is 235 - 16 = 219. In 8 bit RGB full black is 0,0,0 and full white is 255,255,255. So the range is 255 - 0 = 255. On conversion from RGB to Y the range has to be scaled from 255 units to 219 units, and 16 added to move everything up from 0 to 16.
Similarly, the valid range of U and V is 16 to 240. 240 - 16 = 224. -
8 bit rec.601 and rec.709 define full black as Y=16 and full white as Y=235. So the valid range of Y is 235 - 16 = 219.
In 8 bit RGB full black is 0,0,0 and full white is 255,255,255. So the range is 255 - 0 = 255.
I'm finding that for 100% accuracy from 0 through 1023 and multiplying the coefficients by 2^(16-8), Y, U and V must be 32-bit longs.
If you want your YUV to be interchangeable with other software you need to stick with standards. Converting to 12 or 16 bit YUV would be a better choice.Last edited by chris319; 29th Jun 2016 at 14:05.
-
I would recommend going back and doing some reading of the definitions and conventions. You are really far afield from standard practice and what NLE's actually do. We established a while back that you need 10 bits in YUV space to losslessly round trip RGB24/32. I suppose you could generalize this by saying x bits in RGB require x+2 bits in YUV. Are you disputing that? Expanding out YUV to full range should not change that. But I don't know, nor am I willing to waste time testing it. At the end of the day, it all comes down to the definitions and conventions, and if you refuse to incorporate them into your conversions, then I am unable to help because that is what my models use.
-
-
That is great. I am not trying to discourage you. Everyone has to follow their own path to enlightenment! I am sure, after you are done, that you will find the time and energy to conform your conversions to convention, and then we can all be on the same page.
-
In perusing the BT.709 spec I get the impression that the 16 - 235 clamping is performed in the RGB domain and not in the conversion to YUV. Am I incorrect on this? This explains why my YUV conversion code looks incomplete to some.
It is also my impression that as long as Y, U and V are confined to 10 bits, i.e. the range 0 to 1023, there will be color errors and the implementors figure the user just lives with it. For perfect color it seems Y, U and V would have to occupy greater than 10 bits and this brings up bandwidth issues. For example, 20 bits each of Y, U and V would fit within 64 bits and this doubles the bandwidth of 3 x 10-bit words.
Of course I could be completely wrong on all of this.
The code below quantifies the color error. N.B.: This is all experimentation on my part, just goofin' around. It is not intended to be strictly standards compliant.
Code:#include <stdio.h> #include <stdlib.h> #include <float.h> //BT.709 CONSTANTS #define KR 0.2126*4 #define KG 0.7152*4 #define KB 0.0722*4 //BT.2020 CONSTANTS //#define KR 0.2627 //#define KG 0.678 //#define KB 0.0593 unsigned short nr,ng,nb,R,G,B; short Y,U,V, maxy=-256, maxu=-256, maxv=-256; double error=0; /************************************************************************/ // Convert an 8 bit RGB value to a 10 bit YUV value. /************************************************************************/ void rgb_to_yuv(void) { double fr, fg, fb, fy, fu, fv; fr = (double)R; fg = (double)G; fb = (double)B; fy = (KR)*fr + (KG)*fg + (KB)*fb; fu = (fb * KB) - fy; fv = (fr * KR) - fy; Y = (int)(fy+0.5); U = (int)(fu+0.5); V = (int)(fv+0.5); if (Y > maxy){maxy = Y;} if (-U > maxu){maxu = -U;} if (-V > maxv){maxv = -V;} } /************************************************************************/ // Convert a 10 bit YUV value to an 8 bit RGB value. /************************************************************************/ void yuv_to_rgb(void) { double fy, fu, fv; fy = (double)Y; fu = (double)U; fv = (double)V; nr = (int)(((fy+fv)/(KR))+0.5); ng = (int)((-(fy + fv + fu)/(KG))+0.5); nb = (int)(((fy+fu)/(KB))+0.5); } /************************************************************************/ // Test all 16 million 8 bit RGB values through a round trip // from 8 bit RGB to 10 bit YUV and back to 8 bit RGB. /************************************************************************/ void test_round_trip(void) { int count, diff; count = 0; diff = 0; puts("\nTesting round trip...\n"); for (R=16; R<=235; R++) { for (G=16; G<=235; G++) { for (B=16; B<=235; B++) { rgb_to_yuv(); yuv_to_rgb(); //if ((R != nr) || (G != ng) || (B != nb)) int tol = 0;if((R-nr>tol) || (R-nr<-tol) || (G-ng>tol) || (G-ng<-tol)||(B-nb>tol) || (B-nb<-tol)) //printf("%d, %d, %d -> %d, %d, %d\n", R, G, B, nr, ng, nb); diff++; } count++; error = error + abs(nr-R)+ abs(ng-G) + abs(nb-B); } } //printf("%d colors tested\n", count); //printf("%d colors differed\n", diff); printf("max y: %d max 1024\n", maxy); printf("max u: %d max 1024\n", -maxu); printf("max v: %d max 1024\n", -maxv); printf("error: %.0f\n",error/(double)count+0.5); printf("error pct: %.2f\n",(error/(double)count)/(235.0-16.0)*100); } /************************************************************************/ int main(void) { test_round_trip(); }
-
I am not sure how you got that "impression". Clamping is something that only exists in YUV space and is a carryover from the days (long before RGB existed) of analog broadcast signals that required headroom and footroom. But then people/programs/camcorders do all kinds of weird stuff, most notably superwhites on consumer cams. Spend some time reading about about why this exists. It might help you especially if you need to preserve details in the superwhite and superblack ranges.
-
This converts 8-bit RGB to 10-bit YUV in BT.709 space. Coefficients at http://www.poynton.com/PDFs/coloureq.pdf.
Y values are in range between 64 and 940. U and V values are in range between 64 and 960.
No errors in RGB range 0 to 255.
Code:#include <stdio.h> #include <float.h> //BT.709 CONSTANTS #define KR 0.2126 #define KG 0.7152 #define KB 0.0722 short maxy = 0, maxu = 0, maxv = 0, miny = 1024, minu = 1024, minv = 1024; /************************************************************************/ // Convert an 8 bit RGB value to a 10 bit YUV value BT.709 /************************************************************************/ void rgb_to_yuv(short R, short G, short B, short *Y, short *U, short *V) { double fr, fg, fb, fy, fu, fv; fr = (double)R; fg = (double)G; fb = (double)B; // high precision matrix at http://www.poynton.com/PDFs/coloureq.pdf fy = ((KR * fr + KG * fg + KB * fb)*4); *Y = (int)(fy+0.5); if (*Y > maxy) {maxy = *Y;} if (*Y < miny) {miny = *Y;} fu = (-0.1145 * R - 0.3855 * G + 0.5 * B) + 128; *U = (int)(fu * 4.0 + 0.5); if (*U > maxu) {maxu = *U;} if (*U < minu) {minu = *U;} fv = (0.5016 * R - 0.4556 * G - 0.0459 * B) + 128; *V = (int)(fv * 4.0 + 0.5); if (*V > maxv) {maxv = *V;} if (*V < minv) {minv = *Y;} } /************************************************************************/ // Convert a 10 bit YUV value to an 8 bit RGB value BT.709 /************************************************************************/ void yuv_to_rgb(short Y, short U, short V, short *R, short *G, short *B) { double fr, fg, fb, fy, fu, fv; fy = (double)Y / 4.0; fu = (double)U / 4.0 - 128.0; fv = (double)V / 4.0 - 128.0; // high precision values at http://www.poynton.com/PDFs/coloureq.pdf fr= (int)((fy + 1.5701 * fv)+0.5); fg= (int)((fy - 0.1870 * fu - 0.4664 * fv)+0.5); fb= (int)((fy + 1.8556 * fu)+0.5); *R = (int)(fr + 0.5); *G = (int)(fg + 0.5); *B = (int)(fb + 0.5); } /************************************************************************/ // Test all 16 million 8 bit RGB values through a round trip // from 8 bit RGB to 10 bit YUV and back to 8 bit RGB. /************************************************************************/ void test_round_trip(void) { short r, g, b, y, u, v, nr, ng, nb; int count, diff; printf("\nTesting round trip...\n"); count = 0; diff = 0; for (r=0; r<=255; r++) { for (g=0; g<=255; g++) { for (b=0; b<=255; b++) { rgb_to_yuv(r, g, b, &y, &u, &v); yuv_to_rgb(y, u, v, &nr, &ng, &nb); if ((r!=nr) || (g!=ng) || (b!=nb)) { //printf("%d, %d, %d -> %d, %d, %d -> %d, %d, %d\n", r, g, b, y, u, v, nr, ng, nb); diff++; } count++; } } } printf("%d colors tested\n", count); printf("%d colors differed\n", diff); } /************************************************************************/ int main(void) { test_round_trip(); }
-
as a side note: may be some moderator could move this to the programming section?
users currently on my ignore list: deadrats, Stears555, marcorocchini
Similar Threads
-
YUV/ RGB problem in Avisynth
By spiritt in forum Newbie / General discussionsReplies: 9Last Post: 6th Sep 2015, 04:31 -
Lossless (10 Bit RGB 444) and (10 Bit YUV 422) Compression Codec's
By JasonCA in forum Video ConversionReplies: 62Last Post: 25th Dec 2014, 23:40 -
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