principle
Correspondence between RGB and YUV space
According to the relevant knowledge of TV principles, the corresponding relationship between RGB and YUV is:
⎩⎪⎨⎪⎧Y=0.299RU=−0.1684RV=0.5R+0.587G−0.3316G−0.4187G+0.114B+0.5B−0.0813B=0.564(B−Y)=0.713(R−Y)(1)
Among them, in order to control the dynamic range of the color difference signal at [-0.5, 0.5], the normalization process before quantization is required, and the compression coefficient of the digital color difference signal (0.564 respectively) With 0.713).
Quantization level allocation
Reference: Section 7.4.2 of "Principles of Modern Television": Distribution of quantization levels of video signals
When performing 8-bit quantization, a certain margin needs to be left at the upper and lower ends as a guard band for the signal to exceed the dynamic range. specifically:
- For the luminance signal, set aside 20 levels at the upper end of the 256 levels, and leave 16 levels at the lower end as a margin, namelyThe dynamic range of Y is 16-235;
- For two color-difference signals, leave 15 levels at the upper end of the 256 levels, and leave 16 levels at the lower end as a margin, namelyThe dynamic range of U and V is 16-240。
According to the code level digital expression
the amountTransformWaitlevel=int{moldTo beElectricitylevelmostBigvalue−moldTo beElectricitylevelmostsmallvalueQmax−Qmin×CorrectshouldofnumberwordElectricitylevelpublicformula+0ElectricitylevelCorrectshouldGetthe amountTransformWaitlevel}(2)
knows
⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧Y′=int{0.5−(−0.5)235−16Y+16}U′=int{1−0240−16U+128}V′=int{1−0240−16V+128}(3)
where,
- intIndicates rounding down;
- Y′、U′、V′Is the digital quantization level,Y、U、VIs the normalized analog level (Y∈[0,1],U,V∈[−0.5,0.5]);
- Considering that the color difference signal has a negative value, the original value of 0 needs to be corresponded to 128, so 128 is added.
Since the read RGB file has been quantized with 8 bit (the range of the three RGB components is 0-255), the formula(2)Make corrections, firstYMapped to -0.5—0.5,U、VMapped to 0-1:
⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧Y′=int{255219Y+16}U′=int{255224U+128}V′=int{255224V+128}(4)
bring in(1)Formula, get:
⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧Y=25566R+129G+25B+16U=255−38R−74G+112B+128V=255112R−94G−18B+128(5)
In order to improve the calculation efficiency of the computer without causing excessive errors, use in the program>> 8
Instead of dividing by 255.
will(5)The formula is written in matrix form:
⎣⎡YUV⎦⎤=2551⎣⎡66−38112129−74−9425112−18⎦⎤⎣⎡RGB⎦⎤+⎣⎡1616128⎦⎤(6)
And rememberA=⎣⎡66−38112129−74−9425112−18⎦⎤。
Reverse solution, get:
⎣⎡RGB⎦⎤=255AT⎣⎡Y−16U−16V−128⎦⎤(7)
Organized into:
R = (298 * Y + 411 * V - 57344) >> 8;G = (298 * Y - 101 * U - 211 * V + 34739) >> 8;B = (298 * Y + 519 * U - 71117) >> 8;
main
Command line parameters of the function
main
The function actually has two formal parameters,int argc
withchar* argv[]
. Although it is the default in many cases, in operations involving files, for example, using command line parameters can provide certain convenience for programming.
The setting method is as follows: In Visual Studio, click Project→Project Properties in the menu bar in turn, and click "Debug" under the Configuration Properties menu of the project properties page. Set the working directory by browsing the folder and enter it in the command parametern
Strings (separated by spaces).
Command line parameter setting
These strings will be automatically passed toargv
, As its first1
ton
Elements (th0
Elements are"Project name.exe"
),andargc
The value isn+1
。
Source code
declarations.h
#pragma oncevoid rgb2yuv(FILE*, int, int, int, unsigned char*, unsigned char*, unsigned char*, unsigned char*);void yuv2rgb(FILE*, int, int, int, unsigned char*, unsigned char*, unsigned char*, unsigned char*);void errorData(int, unsigned char*, char* []);
rgb2yuv.cpp
#include <iostream>#include "declarations.h"int rgb66[256], rgb129[256], rgb25[256];int rgb38[256], rgb74[256], rgb112[256];int rgb94[256], rgb18[256];void rgbLookupTable(){for (int i = 0; i < 256; i++){rgb66[i] = 66 * i;rgb129[i] = 129 * i;rgb25[i] = 25 * i;rgb38[i] = 38 * i;rgb74[i] = 74 * i;rgb112[i] = 112 * i;rgb94[i] = 94 * i;rgb18[i] = 18 * i;}}void rgb2yuv(FILE* yuvFile, int rgbSize, int w, int h, unsigned char* rgbBuf, unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf){unsigned char* uBuf444 = NULL;// U component buffer before downsamplingunsigned char* vBuf444 = NULL;// V component buffer before downsamplinguBuf444 = new unsigned char[rgbSize / 3];// 4:4:4 formatvBuf444 = new unsigned char[rgbSize / 3];int pxNum = w * h;// RGB to YUV (4:4:4)for (int i = 0; i < pxNum; i++)// i is the image pixel number{unsigned char r = rgbBuf[3 * i + 2];// R component of the i-th pixel of the RGB imageunsigned char g = rgbBuf[3 * i + 1];// G component of the i-th pixel of the RGB imageunsigned char b = rgbBuf[3 * i];// The B component of the i-th pixel of the RGB image//yBuf[i] = ((66 * r + 129 * g + 25 * b) >> 8) + 16;//uBuf444[i] = ((-38 * r - 74 * g + 112 * b) >> 8) + 128;//vBuf444[i] = ((112 * r - 94 * g - 18 * b) >> 8) + 128;rgbLookupTable();// Use lookup table to improve computing efficiencyyBuf[i] = ((rgb66[r] + rgb129[g] + rgb25[b]) >> 8) + 16;uBuf444[i] = ((-rgb38[r] - rgb74[g] + rgb112[b]) >> 8) + 128;vBuf444[i] = ((rgb112[r] - rgb94[g] - rgb18[b]) >> 8) + 128;}// 4:4:4 to 4:2:0for (int i = 0; i < h; i += 2){for (int j = 0; j < w; j += 2){uBuf[i / 2 * w / 2 + j / 2] = uBuf444[i * w + j];vBuf[i / 2 * w / 2 + j / 2] = vBuf444[i * w + j];}}delete[]uBuf444;delete[]vBuf444;fwrite(yBuf, sizeof(unsigned char), rgbSize / 3, yuvFile);fwrite(uBuf, sizeof(unsigned char), rgbSize / 12, yuvFile);fwrite(vBuf, sizeof(unsigned char), rgbSize / 12, yuvFile);}
yuv2rgb.cpp
#include <iostream>#include "declarations.h"int yuv298[256], yuv411[256];int yuv101[256], yuv211[256];int yuv519[256];void yuvLookupTable(){for (int i = 0; i < 256; i++){yuv298[i] = 298 * i;yuv411[i] = 411 * i;yuv101[i] = 101 * i;yuv211[i] = 211 * i;yuv519[i] = 519 * i;}}void yuv2rgb(FILE* rgbFile, int yuvSize, int w, int h, unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf, unsigned char* rgbBuf){unsigned char* uBuf444 = new unsigned char[yuvSize * 2 / 3];// Revert to 4:4:4 U component bufferunsigned char* vBuf444 = new unsigned char[yuvSize * 2 / 3];// Revert to 4:4:4 V component bufferint pxNum = w * h;// Total number of pixels in the image// 4:2:0 to 4:4:4for (int i = 0; i < h / 2; i++)// i control line{for (int j = 0; j < w / 2; j++)// j control column{uBuf444[2 * i * w + 2 * j] = uBuf[i * w / 2 + j];uBuf444[2 * i * w + 2 * j + 1] = uBuf[i * w / 2 + j];uBuf444[2 * i * w + 2 * j + w] = uBuf[i * w / 2 + j];uBuf444[2 * i * w + 2 * j + w + 1] = uBuf[i * w / 2 + j];vBuf444[2 * i * w + 2 * j] = vBuf[i * w / 2 + j];vBuf444[2 * i * w + 2 * j + 1] = vBuf[i * w / 2 + j];vBuf444[2 * i * w + 2 * j + w] = vBuf[i * w / 2 + j];vBuf444[2 * i * w + 2 * j + w + 1] = vBuf[i * w / 2 + j];}}// YUV (4:4:4) to RGBfor (int i = 0; i < pxNum; i++){// All intermediate variables use int type to ensure sufficient accuracy and prevent overflowint y = yBuf[i];// The Y component of the i-th pixel of the YUV imageint u = uBuf444[i];// U component of the i-th pixel of YUV image (4:4:4)int v = vBuf444[i];// The V component of the i-th pixel of the YUV image (4:4:4)int r;int g;int b;yuvLookupTable();//r = (298 * y + 411 * v-57344) >> 8; // R component of the restored RGB imager = (yuv298[y] + yuv411[v] - 57344) >> 8;// R component of the restored RGB imageif (r < 0)r = 0;// fixif (r > 255)r = 255;//g = (298 * y-101 * u-211 * v + 34739) >> 8; // G component of the restored RGB imageg = (yuv298[y] - yuv101[u] - yuv211[v] + 34739) >> 8;// G component of the restored RGB imageif (g < 0)g = 0;if (g > 255)g = 255;//b = (298 * y + 519 * u-71117) >> 8; // The B component of the restored RGB imageb = (yuv298[y] + yuv519[u] - 71117) >> 8;// The B component of the restored RGB imageif (b < 0)b = 0;if (b > 255)b = 255;rgbBuf[3 * i + 2] = (unsigned char)r;// R component of the restored RGB imagergbBuf[3 * i + 1] = (unsigned char)g;// G component of the restored RGB imagergbBuf[3 * i] = (unsigned char)b;// The B component of the restored RGB image}delete[]uBuf444;delete[]vBuf444;fwrite(rgbBuf, sizeof(unsigned char), yuvSize * 2, rgbFile);}
errorData.cpp
#include <iostream>#include "declarations.h"using namespace std;void errorData(int yuvSize, unsigned char* rgbBuf, char* argv[]){FILE* rgbOriFile = NULL;// Original RGB image file pointerFILE* errorFile = NULL;// Error data file pointerconst char* rgbOriName = argv[1];// Original RGB image file nameconst char* errorName = argv[4];// Error data file name// open a fileif (fopen_s(&rgbOriFile, rgbOriName, "rb") == 0){cout << "Successfully opened " << rgbOriName << "." << endl;}else{cout << "Failed to open " << rgbOriName << "." << endl;exit(0);}if (fopen_s(&errorFile, errorName, "w") == 0){cout << "Successfully opened " << errorName << "." << endl;}else{cout << "Failed to open " << errorName << "." << endl;exit(0);}unsigned char* rgbOriBuf = new unsigned char[yuvSize * 2];fread(rgbOriBuf, sizeof(unsigned char), yuvSize * 2, rgbOriFile);// Output error data to csv filefprintf(errorFile, "Pixel,B Error,G Error,R Error\n");for (int i = 0; i < yuvSize * 2 / 3; i++){fprintf(errorFile, "%d,%d,%d,%d\n", i, (int)abs(rgbBuf[3 * i] - rgbOriBuf[3 * i]), (int)abs(rgbBuf[3 * i + 1] - rgbOriBuf[3 * i + 1]), (int)abs(rgbBuf[3 * i + 2] - rgbOriBuf[3 * i + 2]));}delete[]rgbOriBuf;fclose(rgbOriFile);fclose(errorFile);}
main.cpp
#include <iostream>#include "declarations.h"using namespace std;int main(int argc, char* argv[]){FILE* rgbOriFilePtr = NULL;// File pointer of the original RGB imageFILE* yuvFilePtr = NULL;// File pointer of YUV imageFILE* rgbRecFilePtr = NULL;// File pointer of the restored RGB fileconst char* rgbOriFileName = argv[1];// Original RGB image file nameconst char* yuvFileName = argv[2];// YUV image file nameconst char* rgbRecFileName = argv[3];// Restore RGB image file nameint width = 256;// image widthint height = 256;// image heightint rgbFileSize;// Total bytes of RGB imageint yuvFileSize;// Total bytes of YUV imageunsigned char* rgbOriBuffer = NULL;// Original RGB image bufferunsigned char* yBuffer = NULL;// Y component bufferunsigned char* uBuffer = NULL;// U component bufferunsigned char* vBuffer = NULL;// V component bufferunsigned char* rgbRecBuffer = NULL;// Restore RGB image buffer// open a fileif (fopen_s(&rgbOriFilePtr, rgbOriFileName, "rb") == 0){cout << "Successfully opened " << rgbOriFileName << "." << endl;}else{cout << "Failed to open " << rgbOriFileName << "." << endl;exit(0);}if (fopen_s(&yuvFilePtr, yuvFileName, "wb+") == 0){cout << "Successfully opened " << yuvFileName << "." << endl;}else{cout << "Failed to open " << yuvFileName << "." << endl;exit(0);}if (fopen_s(&rgbRecFilePtr, rgbRecFileName, "wb") == 0){cout << "Successfully opened " << rgbRecFileName << "." << endl;}else{cout << "Failed to open " << rgbRecFileName << "." << endl;exit(0);}// Calculate the total number of bytes of the original RGB imagefseek(rgbOriFilePtr, 0L, SEEK_END);rgbFileSize = ftell(rgbOriFilePtr);rewind(rgbOriFilePtr);cout << "The space that " << rgbOriFileName << " accounts for is " << rgbFileSize << " Bytes = " << rgbFileSize / 1024 << " kB." << endl;yuvFileSize = rgbFileSize / 2;// Create a bufferrgbOriBuffer = new unsigned char[rgbFileSize];yBuffer = new unsigned char[rgbFileSize / 3];uBuffer = new unsigned char[rgbFileSize / 12];// 4:2:0 formatvBuffer = new unsigned char[rgbFileSize / 12];rgbRecBuffer = new unsigned char[rgbFileSize];fread(rgbOriBuffer, sizeof(unsigned char), rgbFileSize, rgbOriFilePtr);// RGB image is read into the bufferrgb2yuv(yuvFilePtr, rgbFileSize, width, height, rgbOriBuffer, yBuffer, uBuffer, vBuffer);yuv2rgb(rgbRecFilePtr, yuvFileSize, width, height, yBuffer, uBuffer, vBuffer, rgbRecBuffer);errorData(yuvFileSize, rgbRecBuffer, argv);delete[]rgbOriBuffer;delete[]yBuffer;delete[]uBuffer;delete[]vBuffer;delete[]rgbRecBuffer;fclose(rgbOriFilePtr);fclose(yuvFilePtr);fclose(rgbRecFilePtr);}
Experimental results and error analysis
down.rgb
down_transformed.yuv
down_recoverd.rgb
The above three pictures are the original RGB image, the YUV image converted by RGB, and the RGB image restored by YUV. Comparing the first and third pictures, the difference is almost indistinguishable by the naked eye. In order to quantify the error, in the program, useerrorData
The function calculates the errors of the three components of each pixel of the two RGB images and outputs them to a csv file.
Because it is inconvenient to analyze and visualize data in C++, considering the large amount of data, R is used for analysis.
Make boxplot and histogram separately in R:
errorData <- read.csv("errorData.csv")b.error <- errorData[, 2]g.error <- errorData[, 3]r.error <- errorData[, 4]boxplot(r.error, g.error, b.error, horizontal = TRUE, names = c("R Error", "G Error", "B Error"), col = c("coral2", "palegreen1", "skyblue1"))hist(r.error, freq = FALSE, xlab = "Pixel", ylab = "Frequency of R Error", col = "coral2")hist(g.error, freq = FALSE, xlab = "Pixel", ylab = "Frequency of G Error", col = "palegreen1")hist(b.error, freq = FALSE, xlab = "Pixel", ylab = "Frequency of B Error", col = "skyblue1")
The Empirical CDF of each component error can be obtained:
> ecdf.r.error <- ecdf(r.error)> ecdf.g.error <- ecdf(g.error)> ecdf.b.error <- ecdf(b.error)> ecdf.r.error(5)[1] 0.9351196> ecdf.g.error(5)[1] 0.9855804> ecdf.b.error(5)[1] 0.8774567
The chart shows that the conversion of this chromaticity space cannot be 100% accurate. The sources of error may include:
- Since 3/4 of the chroma information is discarded when converting from 4:4:4 RGB image to 4:2:0 YUV image, it is impossible to restore the discarded chroma information when restoring to YUV file. of;
- When deriving the color space conversion formula, the shift operation is used instead of the division operation, and there is rounding in the calculation process;
- During the conversion from YUV to RGB, some data overflowed.
However, the pixel errors of 93.5%, 98.6%, and 87.8% of the R, G, and B components are less than or equal to 5, so the color space conversion error of this algorithm is not large; because the human eye is much more sensitive to chromaticity The sensitivity and error of brightness are also beyond the resolving power of the human eye.