RGB与YUV色彩空间的相互转换 - 代码先锋网 (2024)

原理

RGB与YUV空间的对应关系

根据电视原理的相关知识可知,RGB与的YUV对应关系为:
{Y=0.299R+0.587G+0.114BU=0.1684R0.3316G+0.5B=0.564(BY)V=0.5R0.4187G0.0813B=0.713(RY)(1)\begin{cases}Y= 0.299\ R &+ 0.587\ G &+ 0.114\ B \\U= -0.1684\ R &- 0.3316\ G &+ 0.5\ B &= 0.564\ (B-Y) \\V= 0.5\ R &- 0.4187\ G &- 0.0813\ B &= 0.713\ (R-Y) \\\end{cases}\tag{1}Y=0.299RU=0.1684RV=0.5R+0.587G0.3316G0.4187G+0.114B+0.5B0.0813B=0.564(BY)=0.713(RY)(1)
其中,为了使色差信号的动态范围控制在[-0.5, 0.5],需要进行量化前的归一化处理,需要引入数字色差信号的压缩系数(分别为0.564与0.713)。

量化电平的分配

参考:《现代电视原理》7.4.2节:视频信号量化电平的分配

在进行8 bit量化时,需要在上下两端留出一定的余量,作为信号超越动态范围的保护带。具体地:

  • 对于亮度信号,在256级的上端留出20级,下端留出16级作为余量,即Y的动态范围为16—235
  • 对于两个色差信号,在256级的上端留出15级,下端留出16级作为余量,即U、V的动态范围为16—240

根据码电平数字表达式
=int{QmaxQmin×+0}(2)量化等级={\rm{int}}\left\{ \dfrac{Q_{\max}-Q_{\min}}{模拟电平最大值-模拟电平最小值}\times 对应的数字电平公式+0电平对应得量化等级 \right\} \tag{2}=int{QmaxQmin×+0}(2)
可知
{Y=int{235160.5(0.5)Y+16}U=int{2401610U+128}V=int{2401610V+128}(3)\begin{cases}Y' = {\rm int}\left\{\dfrac {235-16}{0.5-(-0.5)}Y+16 \right\}\\U' = {\rm int}\left\{\dfrac {240-16}{1-0}U+128 \right\}\\V' = {\rm int}\left\{\dfrac {240-16}{1-0}V+128 \right\}\end{cases}\tag{3}Y=int{0.5(0.5)23516Y+16}U=int{1024016U+128}V=int{1024016V+128}(3)
其中,

  • int{\rm int}int表示向下取整;
  • YY'YUU'UVV'V为数字量化电平,YYYUUUVVV为归一化的模拟电平(Y[0,1]Y\in [0,1]Y[0,1]U,V[0.5,0.5]U,V\in [-0.5,0.5]U,V[0.5,0.5]);
  • 考虑到色差信号有负值,需要将原来的0值对应到128,故加上128。

由于读取的RGB文件已经进行了8 bit的量化(RGB三个分量范围均为0—255),所以要对公式(2)(2)(2)进行修正,先将YYY映射到-0.5—0.5,UUUVVV映射到0—1:
{Y=int{219255Y+16}U=int{224255U+128}V=int{224255V+128}(4)\begin{cases}Y' = {\rm int}\left\{ \dfrac {219}{255}Y+16 \right\}\\U' = {\rm int}\left\{ \dfrac {224}{255}U+128 \right\}\\V' = {\rm int}\left\{ \dfrac {224}{255}V+128 \right\}\end{cases}\tag{4}Y=int{255219Y+16}U=int{255224U+128}V=int{255224V+128}(4)
带入(1)(1)(1)式,得:
{Y=66R+129G+25B255+16U=38R74G+112B255+128V=112R94G18B255+128(5)\begin{cases}Y= \dfrac {66R + 129G + 25B}{255} + 16 \\U= \dfrac{-38R - 74G + 112B}{255} +128 \\V= \dfrac{112R - 94G - 18B}{255} + 128\end{cases}\tag{5}Y=25566R+129G+25B+16U=25538R74G+112B+128V=255112R94G18B+128(5)

为了提高计算机的计算效率且不会造成过大的误差,在程序中使用>> 8来代替除以255的计算。

(5)(5)(5)式写为矩阵形式:
[YUV]=1255[661292538741121129418][RGB]+[1616128](6)\begin{bmatrix}Y \\ U \\ V\end{bmatrix}= \dfrac {1}{255}\begin{bmatrix}66 & 129 & 25 \\-38 & -74 & 112 \\112 & -94 & -18\end{bmatrix}\begin{bmatrix}R \\ G \\ B\end{bmatrix}+\begin{bmatrix}16 \\ 16 \\ 128\end{bmatrix}\tag{6}YUV=2551663811212974942511218RGB+1616128(6)

并记A=[661292538741121129418]\boldsymbol A= \begin{bmatrix}66 & 129 & 25 \\-38 & -74 & 112 \\112 & -94 & -18\end{bmatrix}A=663811212974942511218

反解,得:
[RGB]=255AT[Y16U16V128](7)\begin{bmatrix}R \\ G \\ B\end{bmatrix}= 255\boldsymbol A^{\rm T}\begin{bmatrix}Y-16 \\ U-16 \\ V-128\end{bmatrix}\tag{7}RGB=255ATY16U16V128(7)
整理得:

R = (298 * Y + 411 * V - 57344) >> 8;G = (298 * Y - 101 * U - 211 * V + 34739) >> 8;B = (298 * Y + 519 * U - 71117) >> 8;

main函数的命令行参数

main函数实际上具有两个形参,int argcchar* argv[]。虽然很多情况下是缺省的,但在例如涉及文件的操作中,使用命令行参数可以为编程提供一定的便利。

设置方法如下:在Visual Studio中,依次点击菜单栏中的项目→项目属性,在项目属性页的配置属性菜单下,点击“调试”。通过浏览文件夹的方式设置工作目录,并在命令参数中输入n个字符串(以空格分隔)。

RGB与YUV色彩空间的相互转换 - 代码先锋网 (1)

命令行参数的设置

这些字符串将会自动传递给argv,作为其第1n个元素(第0个元素为"项目名.exe"),而argc的值为n+1

源代码

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分量缓冲区unsigned char* vBuf444 = NULL;// 下采样前的V分量缓冲区uBuf444 = new unsigned char[rgbSize / 3];// 4:4:4格式vBuf444 = new unsigned char[rgbSize / 3];int pxNum = w * h;// RGB to YUV (4:4:4)for (int i = 0; i < pxNum; i++)// i为图像像素序号{unsigned char r = rgbBuf[3 * i + 2];// RGB图像第i个像素的R分量unsigned char g = rgbBuf[3 * i + 1];// RGB图像第i个像素的G分量unsigned char b = rgbBuf[3 * i];// RGB图像第i个像素的B分量//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();// 使用查找表,提高运算效率yBuf[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];// 还原成4:4:4的U分量缓冲区unsigned char* vBuf444 = new unsigned char[yuvSize * 2 / 3];// 还原成4:4:4的V分量缓冲区int pxNum = w * h;// 图像中的总像素数// 4:2:0 to 4:4:4for (int i = 0; i < h / 2; i++)// i控制行{for (int j = 0; j < w / 2; j++)// j控制列{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++){// 中间变量均使用int型,以保证足够的精度,防止溢出int y = yBuf[i];// YUV图像第i个像素的Y分量int u = uBuf444[i];// YUV图像第i个像素的U分量(4:4:4)int v = vBuf444[i];// YUV图像第i个像素的V分量(4:4:4)int r;int g;int b;yuvLookupTable();//r = (298 * y + 411 * v - 57344) >> 8;// 还原的RGB图像的R分量r = (yuv298[y] + yuv411[v] - 57344) >> 8;// 还原的RGB图像的R分量if (r < 0)r = 0;// 修正if (r > 255)r = 255;//g = (298 * y - 101 * u - 211 * v + 34739) >> 8;// 还原的RGB图像的G分量g = (yuv298[y] - yuv101[u] - yuv211[v] + 34739) >> 8;// 还原的RGB图像的G分量if (g < 0)g = 0;if (g > 255)g = 255;//b = (298 * y + 519 * u - 71117) >> 8;// 还原的RGB图像的B分量b = (yuv298[y] + yuv519[u] - 71117) >> 8;// 还原的RGB图像的B分量if (b < 0)b = 0;if (b > 255)b = 255;rgbBuf[3 * i + 2] = (unsigned char)r;// 还原的RGB图像的R分量rgbBuf[3 * i + 1] = (unsigned char)g;// 还原的RGB图像的G分量rgbBuf[3 * i] = (unsigned char)b;// 还原的RGB图像的B分量}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;// 原始RGB图像文件指针FILE* errorFile = NULL;// 误差数据文件指针const char* rgbOriName = argv[1];// 原始RGB图像文件名const char* errorName = argv[4];// 误差数据文件名// 打开文件if (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);// 将误差数据输出到csv文件fprintf(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;// 原RGB图像的文件指针FILE* yuvFilePtr = NULL;// YUV图像的文件指针FILE* rgbRecFilePtr = NULL;// 复原的RGB文件的文件指针const char* rgbOriFileName = argv[1];// 原RGB图像文件名const char* yuvFileName = argv[2];// YUV图像文件名const char* rgbRecFileName = argv[3];// 复原RGB图像文件名int width = 256;// 图像宽int height = 256;// 图像高int rgbFileSize;// RGB图像总字节数int yuvFileSize;// YUV图像总字节数unsigned char* rgbOriBuffer = NULL;// 原RGB图像缓冲区unsigned char* yBuffer = NULL;// Y分量缓冲区unsigned char* uBuffer = NULL;// U分量缓冲区unsigned char* vBuffer = NULL;// V分量缓冲区unsigned char* rgbRecBuffer = NULL;// 复原RGB图像缓冲区// 打开文件if (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);}// 计算原RGB图像总字节数fseek(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;// 建立缓冲区rgbOriBuffer = new unsigned char[rgbFileSize];yBuffer = new unsigned char[rgbFileSize / 3];uBuffer = new unsigned char[rgbFileSize / 12];// 4:2:0格式vBuffer = new unsigned char[rgbFileSize / 12];rgbRecBuffer = new unsigned char[rgbFileSize];fread(rgbOriBuffer, sizeof(unsigned char), rgbFileSize, rgbOriFilePtr);// RGB图像读入缓冲区rgb2yuv(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);}

实验结果与误差分析

RGB与YUV色彩空间的相互转换 - 代码先锋网 (2)

down.rgb

RGB与YUV色彩空间的相互转换 - 代码先锋网 (3)

down_transformed.yuv

RGB与YUV色彩空间的相互转换 - 代码先锋网 (4)

down_recoverd.rgb

以上三张图分别是原RGB图像、通过RGB转换的YUV图像和通过YUV复原的RGB图像。对比第1、3张图,几乎通过肉眼分辨不出差别。为了量化误差,在程序中,利用errorData函数计算了两张RGB图像各像素的三个分量的误差,并输出到了csv文件中。

由于在C++中进行数据分析与可视化并不方便,考虑到数据量较大,因而采用R进行分析。

在R中分别作出boxplot和直方图:

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")
RGB与YUV色彩空间的相互转换 - 代码先锋网 (5)
RGB与YUV色彩空间的相互转换 - 代码先锋网 (6)
RGB与YUV色彩空间的相互转换 - 代码先锋网 (7)
RGB与YUV色彩空间的相互转换 - 代码先锋网 (8)

可以再求出各分量误差的Empirical CDF:

> 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

图表显示,该色度空间的转换不能做到100%的准确。误差来源可能有:

  • 由于从4:4:4的RGB图像转换为4:2:0的YUV图像时,舍弃掉了3/4的色度信息,因而在还原为YUV文件时是无法还原出舍弃部分的色度信息的;
  • 在进行色彩空间转换的公式推导时,使用了移位运算代替了除法运算,并且在计算过程中存在四舍五入;
  • 在YUV向RGB的转换时,存在部分数据溢出。

但R、G、B分量分别有93.5%、98.6%、87.8%的像素误差小于等于5,因而该算法的色彩空间转换的误差并不大;由于人眼对色度的敏感度远高于对亮度的敏感度,误差也在人眼的分辨能力之外。

RGB与YUV色彩空间的相互转换 - 代码先锋网 (2024)

References

Top Articles
Your New Boyfriend Roblox ID Code (2024) Wilbur Soot Song
Hedonism | Internet Encyclopedia of Philosophy
Obituary for Mark E. Rimer at Hudson-Rimer Funeral Chapel
Craigslist Centre Alabama
Nycers Pay Schedule
Jocelyne Mirando
Iapd Lookup
Fatshark Forums
Craigslist Farm And Garden Yakima Wa
Ter Reviews Boston
Does Cvs Sell Ulta Gift Cards
Demystifying the C-Suite: A Close Look at the Top Executive Roles - 33rd Square
Weldmotor Vehicle.com
Craigslist Westchester Cars For Sale By Owner
Hsclink.health.unm.edu
Ksat Doppler Radar
Samanthaschwartz Fapello
Craigslist Quad Cities
EVOLVE: Predicting User Evolution and Network Dynamics in Social Media Using Fine-Tuned GPT-like Model
Secret Stars Sessions Julia
Weird Al.setlist
Software For Organizing A Pledge Drive Crossword Clue
Fishweather
Envy Nail Bar Memphis
Ups Drop Off Newton Ks
Ayala Rv Storage
The Star Beacon Obituaries
Yonkers Garbage Schedule 2023
Eddie Murphy Cast Of Elemental
The Origins Behind Kurt Angle's "Perc Angle" Nickname In TNA
Saint Lukes Epulse
More on this Day - March, 7
Monroe County Incidents
Preventice Learnworlds
Wells Fargo Hiring Hundreds to Develop New Tech Hub in the Columbus Region
Oakly Rae Leaks
Glassbox Eyecare
فیلم 365 روز 1 نیکی مووی
Strange World Showtimes Near Amc Hoffman Center 22
Envision Okta Sign In
Chalupp's Pizza Taos Menu
Accuradio Unblocked
Priscilla 2023 Showtimes Near Regal Escondido
Hexanaut.io – Jouez en ligne sur Coolmath Games
Hr Central Luxottica Benefits
Toxic Mold Attorney Near Me How To File A Toxic Mold Lawsuit Sample Complaint In Apartment Mold Case
Magnifeye Alcon
Mycarolinas Login
Ceton Village Diggy
Sicilys Pizza Promo Code 40 Off
How to Screenshot on Cash App: A Complete Guide
Local artist makes award-winning reflection of his home, Duluth
Latest Posts
Article information

Author: Arielle Torp

Last Updated:

Views: 6302

Rating: 4 / 5 (41 voted)

Reviews: 80% of readers found this page helpful

Author information

Name: Arielle Torp

Birthday: 1997-09-20

Address: 87313 Erdman Vista, North Dustinborough, WA 37563

Phone: +97216742823598

Job: Central Technology Officer

Hobby: Taekwondo, Macrame, Foreign language learning, Kite flying, Cooking, Skiing, Computer programming

Introduction: My name is Arielle Torp, I am a comfortable, kind, zealous, lovely, jolly, colorful, adventurous person who loves writing and wants to share my knowledge and understanding with you.