在整个视频行业中,定义了很多 YUV 格式,我以UYVY格式标准来说明,4:2:2 格式UYVY每像素占16 位,UYVY字节顺序如下图:
(图3 UYVY字节顺序)
其中第一个字节为U0,每二个字节为Y0,依次排列如下:
[U0,Y0,V0,Y1] [U1,Y2,V1,Y3] [U2,Y4,V2,Y5] ……
经过仔细分析,我们要实现RGB转YUV格式的话,一个像素的RGB占用三个节,而UYVY每像素占用两个字节,在演示中直接把UYVY字节信息保存到*.pal格式中(这是我自己写来测试用的^_^),*.pal格式字节顺序是先保存上场像素,接着保存下场像素,如果是720x576的一张图像转换为YUV格式并保存的话,文件大小应该是829,440字节(720*576*2)。您可以执行本文附带的程序 (功能菜单->转换并写入YUV两场) 查看转换过程。
关于YUV转换为RGB公式,我直接使用一篇文章提供的公式,经过思考,我发觉要想实现准确无误的把YUV还原为原有的RGB图像很难实现,因为我从UYVY的字节顺序来分析没有找到反变换的方法(您找到了记得告诉我哟: [email protected] ),例如我做了一个简单的分析,假设有六个像素的UYVY格式,要把这12个字节的UYVY要转换回18个字节的RGB,分析如下:
12个字节的UYVY排列方式:
[U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5]
完全转换为18个字节的RGB所需的UYVY字节排列如下:
[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3] [Y4 U4 V4] [Y5 U5 V5]
我们可以看到,12个字节的UYVY无法实现,缺少U3 V3 U4 V4。于是我抛开准确无误地把UYVY转换回RGB的想法,直接使用最近的UV来执行转换,结果发觉转换回来的RGB图像用肉眼根本分辩不出原有RGB图像与反变换回来的RGB图像差别,您可以执行本文附带的程序 (功能菜单->读取YUV并显示) 查看效果,下面是反变换公式和代码的实现:
// 反变换公式
R= 1.0Y + 0 +1.402(V-128)
G= 1.0Y - 0.34413 (U-128)-0.71414(V-128)
B= 1.0Y + 1.772 (U-128)+0
代码实现:
void CRGB2YUVView::YUV2RGB(byte *pRGB, byte *pYUV)
{
byte y, u, v;
y = *pYUV; pYUV++;
u = *pYUV; pYUV++;
v = *pYUV;
*pRGB = static_cast<byte>(1.0*y + 8 + 1.402*(v-128)); pRGB++; // r
*pRGB = static_cast<byte>(1.0*y - 0.34413*(u-128) - 0.71414*(v-128)); pRGB++; // g
*pRGB = static_cast<byte>(1.0*y + 1.772*(u-128) + 0); // b
}
// 读取PAL文件转换为RGB并显示
void CRGB2YUVView::OnReadPAL()
{
// TODO: Add your command handler code here
CDC *pDC = GetDC();
CRect rect;
CBrush brush(RGB(128,128,128));
GetClientRect(&rect);
pDC->FillRect(&rect, &brush);
// PAL 720x576 : 中国的电视标准为PAL制
int CurrentXRes = 720;
int CurrentYRes = 576;
int size = CurrentXRes * CurrentYRes;
// 分配内存
byte *Video_Field0 = (byte*)malloc(CurrentXRes*CurrentYRes);
byte *Video_Field1 = (byte*)malloc(CurrentXRes*CurrentYRes);
// 保存内存指针
byte *Video_Field0_ = Video_Field0;
byte *Video_Field1_ = Video_Field1;
// 初始化内存
ZeroMemory(Video_Field0, CurrentXRes*CurrentYRes);
ZeroMemory(Video_Field1, CurrentXRes*CurrentYRes);
byte yuv_y0, yuv_u0, yuv_v0; // yuv_v1; // {y0, u0, v0, v1};
byte r, g, b;
byte bufRGB[3]; // 临时保存{R,G,B}
byte bufYUV[3]; // 临时保存{Y,U,V}
// 初始化数组空间
memset(bufRGB,0, sizeof(byte)*3);
memset(bufYUV,0, sizeof(byte)*3);
char strFileName[MAX_PATH]="720bmp.pal";
// 分配图片像素内存
RGBTRIPLE *rgb;
rgb = new RGBTRIPLE[CurrentXRes*CurrentYRes];
memset(rgb,0, sizeof(RGBTRIPLE)*CurrentXRes*CurrentYRes); // 初始化内存空间
CFile* f;
f = new CFile();
f->Open(strFileName, CFile::modeRead);
f->SeekToBegin();
f->Read(Video_Field0, CurrentXRes*CurrentYRes);
f->Read(Video_Field1, CurrentXRes*CurrentYRes);
// 上场 (1,3,5,7...行)
for ( int i = CurrentYRes-1; i>=0; i--) {
for ( int j = 0; j<CurrentXRes; j++) {
if(!(i%2)==0)
{
// UYVY标准 [U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5] 每像素点两个字节,[内]为四个字节
if ((j%2)==0)
{
yuv_u0 = *Video_Field0;
Video_Field0++;
}
else
{
yuv_v0 = *Video_Field0;
Video_Field0++;
}
yuv_y0 = *Video_Field0;
Video_Field0++;
bufYUV[0] = yuv_y0; // Y
bufYUV[1] = yuv_u0; // U
bufYUV[2] = yuv_v0; // V
// RGB转换为YUV
YUV2RGB(bufRGB,bufYUV);
r = bufRGB[0]; // y
g = bufRGB[1]; // u
b = bufRGB[2]; // v
if (r>255) r=255; if (r<0) r=0;
if (g>255) g=255; if (g<0) g=0;
if (b>255) b=255; if (b<0) b=0;
for (int k=0; k<1000; k++) ; //延时
// 视图中显示
pDC->SetPixel(j, CurrentYRes-1-i, RGB(r, g, b));
}// end if i%2
}
}
// 下场 (2,4,6,8...行)
for ( int i_ = CurrentYRes-1; i_>=0; i_--) {
for ( int j_ = 0; j_<CurrentXRes; j_++) {
if((i_%2)==0)
{
// UYVY标准 [U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5] 每像素点两个字节,[内]为四个字节
if ((j_%2)==0)
{
yuv_u0 = *Video_Field1;
Video_Field1++;
}
else
{
yuv_v0 = *Video_Field1;
Video_Field1++;
}
yuv_y0 = *Video_Field1;
Video_Field1++;
bufYUV[0] = yuv_y0; // Y
bufYUV[1] = yuv_u0; // U
bufYUV[2] = yuv_v0; // V
// RGB转换为YUV
YUV2RGB(bufRGB,bufYUV);
r = bufRGB[0]; // y
g = bufRGB[1]; // u
b = bufRGB[2]; // v
if (r>255) r=255; if (r<0) r=0;
if (g>255) g=255; if (g<0) g=0;
if (b>255) b=255; if (b<0) b=0;
for (int k=0; k<1000; k++) ; //延时
// 视图中显示
pDC->SetPixel(j_, CurrentYRes-1-i_, RGB(r, g, b));
}
}
}
// 提示完成
char buffer[80];
sprintf(buffer,"完成读取PAL文件:%s ", strFileName);
MessageBox(buffer, "提示信息", MB_OK | MB_ICONINFORMATION);
// 关闭PAL电视场文件
f->Close();
// 释放内存
free( Video_Field0_ );
free( Video_Field1_ );
delete f;
delete rgb;
}