yuv rgb

目录:

显示器图像显示概述

电视图像显示概述

RGB介绍

YUV介绍

隔行读取BMP

RGB转YUV

YUV转RGB

结束语

显示器图像显示概述:

我们知道普通彩色CRT显示器内部有三支电子枪,电子枪去激活显示器屏幕的荧光粉,三种荧光粉发射出的光生成一个像素位置的颜色点,这就是我们人眼能看到的一个像素。每个像素对应红、绿、蓝(R、G、B)三个强度等级,每个像素占用24位,可以显示近1700 万种颜色,这就是我们所说的真彩色。

普通彩色CRT显示器是基于电视技术的光栅扫描,电子束一次扫描一行,从顶到底依次扫描,整个屏幕扫描一次(我们称它为1帧),电子束扫描完一帧后回到最初位置进行下一次扫描。

电视图像显示概述:

电视显示原理与CRT相似,不过采用的是隔行扫描,我国的广播电视采用的是625行隔行扫描方式。隔行扫描是将一帧图像分两次(场)扫描。第一场先扫出1、3、5、7…等奇数行光栅,第二场扫出2、4、6、8…等偶数行光栅。通常将扫奇数行的场叫奇数场(也称上场),扫偶数行的场叫偶数场(也称下场)。为什么电视会选择隔行扫描,这是因为会使显示运动图像更平滑。下面两图为一帧图像的上场和下场的扫描过程。

yuv rgb

(图1 上场扫描)

yuv rgb

(图2 下场扫描)

常见的电视的制式有三种:NTSC、PAL、SECAM,我国的广播电视采用PAL制式,我国电视制式的帧频只有50HZ和我们日常使用的电流频率一样,PAL帧频为25fps,在文章后面我会以一张720x576的图像转换为720x 576 PAL隔行扫描的电视场视频格式作详细描述。

RGB介绍:

在记录计算机图像时,最常见的是采用RGB(红、绿,蓝)颜色分量来保存颜色信息,例如非压缩的24位的BMP图像就采用RGB空间来保存图像。一个像素24位,每8位保存一种颜色强度(0-255),例如红色保存为 0xFF0000。

YUV介绍:

YUV是被欧洲电视系统所采用的一种颜色编码方法,我国广播电视也普遍采用这类方法。其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V”表示的则是色度(Chrominance或Chroma)。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。

隔行读取BMP:

下面我说明如何隔行读取BMP图像,为什么我以BMP图像来作演示,因为BMP可以说是最简单的一种图像格式,最容易用它说明原理,那公为什么要用BMP来演示隔行读取呢,因为要实现RGB转电视场制图像,首先就要知识如何隔行读取。

BMP图像颜色信息的保存顺序是由左到右,由下往上,您可以执行一下附带程序的 (功能菜单->读取RGB) 看到图像的读取和显示过程。代码首先依次显示奇数行像素,如(1,3,5,7,9….行),完成后再依次显示偶数行像素,代码实现如下:

01. // 隔行显示BMP
02. void CRGB2YUVView::OnReadBmp()
03. {
04. // TODO: Add your command handler code here
05. CDC *pDC = GetDC();
06.  
07. CRect rect;
08. CBrush brush(RGB(128,128,128));
09. GetClientRect(&rect);
10. pDC->FillRect(&rect, &brush);
11.  
12. BITMAPFILEHEADER bmfh;
13. BITMAPINFOHEADER bmih;
14.  
15. char strFileName[MAX_PATH]="720bmp.bmp";
16. CFile* f;
17. f = new CFile();
18. f->Open(strFileName, CFile::modeRead);
19. f->SeekToBegin();
20. f->Read(&bmfh, sizeof(bmfh));
21. f->Read(&bmih, sizeof(bmih));
22.  
23. // 分配图片像素内存
24. RGBTRIPLE *rgb;
25. rgb = new RGBTRIPLE[bmih.biWidth*bmih.biHeight];
26.  
27. f->SeekToBegin();
28. f->Seek(54,CFile::begin); // BMP 54个字节之后的是像素数据
29. f->Read(rgb, bmih.biWidth * bmih.biHeight * 3);   // 这里只读24位RGB(r,g,b)图像
30.  
31. // 显示上场 (奇数行组成的奇数场)
32. for (int i = 0; i < bmih.biHeight; i++) {
33. for (int j = 0; j < bmih.biWidth; j++) {
34. if(!(i%2))
35. pDC->SetPixel(j, bmih.biHeight-i,
36. RGB(rgb[i*bmih.biWidth+j].rgbtRed,
37. rgb[i*bmih.biWidth+j].rgbtGreen,rgb[i*bmih.biWidth+j].rgbtBlue));
38. for (int k=0; k< 1000; k++) ;  //延时
39. }
40. }
41.  
42. Sleep(500);
43.  
44. // 显示下场 (偶数行组成的偶数场)
45. for (int i_ = 0; i_< bmih.biHeight; i_++) {
46. for (int j_ = 0; j_< ;bmih.biWidth; j_++) {
47. if(i_%2)
48. pDC->SetPixel(j_, bmih.biHeight-i_,
49. RGB(rgb[i_*bmih.biWidth+j_].rgbtRed,
50. rgb[i_*bmih.biWidth+j_].rgbtGreen,
51. rgb[i_*bmih.biWidth+j_].rgbtBlue));
52. for (int k=0; k< 1000; k++) ;  //延时
53. }
54. }
55.  
56. // 显示24位BMP信息
57. LONG dwWidth = bmih.biWidth;
58. LONG dwHeight = bmih.biHeight;
59. WORD wBitCount = bmih.biBitCount;
60. char buffer[80];
61. sprintf(buffer,"图像宽为:%ld 高为:%ld 像数位数:%d", dwWidth, dwHeight, wBitCount);
62. MessageBox(buffer, "每个像素的位数", MB_OK | MB_ICONINFORMATION);
63.  
64. f->Close();
65. delete f;
66. delete rgb;
67. }

RGB转YUV

在整个视频行业中,定义了很多 YUV 格式,我以UYVY格式标准来说明,4:2:2 格式UYVY每像素占16 位,UYVY字节顺序如下图:

(图3 UYVY字节顺序)

其中第一个字节为U0,每二个字节为Y0,依次排列如下:

[U0,Y0,U1,Y1] [U1,Y2,V1,Y3] [U2,Y4,V2,Y5] ……

经过仔细分析,我们要实现RGB转YUV格式的话,一个像素的RGB占用三个节,而UYVY每像素占用两个字节,我演示直接把UYVY字节信息保存到*.pal格式中(这是我自己写来测试用的^_^),*.pal格式中,先保存上场像素,接着保存下场像素,如果是720x576的一张图像转换为YUV格式并保存的话,文件大小应该是829,440字节(720*576*2)。您可以执行本文附带的程序 (功能菜单->转换并写入YUV两场) 查看转换过程。

RGB转UYVY公式如下:

公式:(RGB => YCbCr)

Y = 0.257R′ + 0.504G′ + 0.098B′ + 16

Cb = -0.148R′ - 0.291G′ + 0.439B′ + 128

Cr = 0.439R′ - 0.368G′ - 0.071B′ + 128

代码实现:

01. // RGB转换为YUV
02. void CRGB2YUVView::RGB2YUV(byte *pRGB, byte *pYUV)
03. {
04. byte r,g,b;
05. r = *pRGB; pRGB++;
06. g = *pRGB; pRGB++;
07. b = *pRGB;
08.  
09. *pYUV = static_cast< byte >(0.257*r + 0.504*g + 0.098*b + 16);    pYUV++;   // y
10. *pYUV = static_cast< byte >(-0.148*r - 0.291*g + 0.439*b + 128);  pYUV++;   // u
11. *pYUV = static_cast< byte >(0.439*r - 0.368*g - 0.071*b + 128);             // v
12. }

像素转换实现:

001. // 转换RGB
002. void CRGB2YUVView::OnConvertPAL()
003. {
004. CDC *pDC = GetDC();
005. CRect rect;
006. CBrush brush(RGB(128,128,128));
007. GetClientRect(&rect);
008. pDC->FillRect(&rect, &brush);
009.  
010. // PAL 720x576 : 中国的电视标准为PAL制  
011. int CurrentXRes = 720;
012. int CurrentYRes = 576;
013. int size        = CurrentXRes * CurrentYRes;
014.  
015. // 分配内存
016. byte *Video_Field0 = (byte*)malloc(CurrentXRes*CurrentYRes);
017. byte *Video_Field1 = (byte*)malloc(CurrentXRes*CurrentYRes);
018.  
019. // 保存内存指针
020. byte *Video_Field0_ = Video_Field0;
021. byte *Video_Field1_ = Video_Field1;
022.  
023. byte yuv_y0, yuv_u0, yuv_v0, yuv_v1;  // {y0, u0, v0, v1};
024. byte bufRGB[3];  // 临时保存{R,G,B}
025. byte bufYUV[3];  // 临时保存{Y,U,V}
026.  
027. // 初始化数组空间
028. ZeroMemory(bufRGB, sizeof(byte)*3);
029. ZeroMemory(bufYUV, sizeof(byte)*3);
030.  
031. // 初始化内存
032. ZeroMemory(Video_Field0, CurrentXRes*CurrentYRes);
033. ZeroMemory(Video_Field1, CurrentXRes*CurrentYRes);
034.  
035. // BMP 位图操作
036. BITMAPFILEHEADER bmfh;
037. BITMAPINFOHEADER bmih;
038.  
039. char strFileName[MAX_PATH]="720bmp.bmp";
040. CFile* f;
041. f = new CFile();
042. f->Open(strFileName, CFile::modeRead);
043. f->SeekToBegin();
044. f->Read(&bmfh, sizeof(bmfh));
045. f->Read(&bmih, sizeof(bmih));
046.  
047. // 分配图片像素内存
048. RGBTRIPLE *rgb;
049. rgb = new RGBTRIPLE[bmih.biWidth*bmih.biHeight];
050.  
051. f->SeekToBegin();
052. f->Seek(54,CFile::begin);  // BMP 54个字节之后的是位像素数据
053. f->Read(rgb, bmih.biWidth * bmih.biHeight * 3);   // 这里只读24位RGB(r,g,b)图像
054.  
055. // 上场  (1,3,5,7...行)
056. for (int i = bmih.biHeight-1; i>=0; i--) {
057. for (int j = 0; j< bmih.biWidth; j++) {
058. if(!(i%2)==0)
059. {
060. bufRGB[0] = rgb[i*bmih.biWidth+j].rgbtRed;   // R
061. bufRGB[1] = rgb[i*bmih.biWidth+j].rgbtGreen; // G
062. bufRGB[2] = rgb[i*bmih.biWidth+j].rgbtBlue;  // B
063.  
064. // RGB转换为YUV
065. RGB2YUV(bufRGB,bufYUV);
066. yuv_y0 = bufYUV[0];   // y
067. yuv_u0 = bufYUV[1];   // u
068. yuv_v0 = bufYUV[2];   // v
069.  
070. for (int k=0; k< 1000; k++) ;  //延时
071. // 视图中显示
072. pDC->SetPixel(j, (bmih.biHeight-1)-i, RGB(bufRGB[0], bufRGB[1], bufRGB[2]));
073.  
074. // UYVY标准  [U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5]
075. // 每像素点两个字节,[内]为四个字节
076. if ((j%2)==0)
077. {
078. *Video_Field0 = yuv_u0; 
079. Video_Field0++;
080. yuv_v1 = yuv_v0;   // v保存起来供下一字节使用
081. }
082. else
083. {
084. *Video_Field0 = yuv_v1; 
085. Video_Field0++;
086. }
087. *Video_Field0 = yuv_y0;     
088. Video_Field0++;
089. }// end if i%2
090. }
091. }
092.  
093. // 下场 (2,4,6,8...行)
094. for (int i_ = bmih.biHeight-1; i_>=0; i_--) {
095. for (int j_ = 0; j_< bmih.biWidth; j_++) {
096. if((i_%2)==0)
097. {
098. bufRGB[0] = rgb[i_*bmih.biWidth+j_].rgbtRed;   //   R
099. bufRGB[1] = rgb[i_*bmih.biWidth+j_].rgbtGreen; // G
100. bufRGB[2] = rgb[i_*bmih.biWidth+j_].rgbtBlue;  // B
101.  
102. // RGB转换为YUV
103. RGB2YUV(bufRGB,bufYUV);
104. yuv_y0 = bufYUV[0];   // y
105. yuv_u0 = bufYUV[1];   // u
106. yuv_v0 = bufYUV[2];   // v
107.  
108. for (int k=0; k< 1000; k++) ;  //延时
109. // 视图中显示
110. pDC->SetPixel(j_, (bmih.biHeight-1)-i_, RGB(bufRGB[0], bufRGB[1], bufRGB[2]));
111.  
112. // UYVY标准  [U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5]
113. // 每像素点两个字节,[内]为四个字节
114. if ((j_%2)==0)
115. {
116. *Video_Field1 = yuv_u0; 
117. Video_Field1++;
118. yuv_v1 = yuv_v0;   // v保存起来供下一字节使用
119. }
120. else
121. {
122. *Video_Field1 = yuv_v1; 
123. Video_Field1++;
124. }
125. *Video_Field1 = yuv_y0;     
126. Video_Field1++;
127. }
128. }
129. }
130.  
131. // 关闭BMP位图文件
132. f->Close();
133. WriteYUV(Video_Field0_, Video_Field1_, size);
134.  
135. // 释放内存
136. free( Video_Field0_ );
137. free( Video_Field1_ );
138. delete f;
139. delete rgb;
140. }

YUV转RGB

关于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并显示) 查看效果,下面是反变换公式和代码的实现:

1. //  反变换公式
2. R= 1.0Y + 0 +1.402(V-128) 
3. G= 1.0Y - 0.34413 (U-128)-0.71414(V-128) 
4. B= 1.0Y + 1.772 (U-128)+0

代码实现:

001. void CRGB2YUVView::YUV2RGB(byte *pRGB, byte *pYUV)
002. {
003. byte y, u, v;
004. y = *pYUV; pYUV++;
005. u = *pYUV; pYUV++;
006. v = *pYUV;
007.  
008. *pRGB = static_cast< byte >(1.0*y + 8 + 1.402*(v-128));    pRGB++;                 // r
009. *pRGB = static_cast< byte >(1.0*y - 0.34413*(u-128) - 0.71414*(v-128));  pRGB++;   // g
010. *pRGB = static_cast< byte >(1.0*y + 1.772*(u-128) + 0);                            // b
011. }
012.  
013. // 读取PAL文件转换为RGB并显示
014. void CRGB2YUVView::OnReadPAL()
015. {
016. // TODO: Add your command handler code here
017. CDC *pDC = GetDC();
018. CRect rect;
019. CBrush brush(RGB(128,128,128));
020. GetClientRect(&rect);
021. pDC->FillRect(&rect, &brush);
022.  
023. // PAL 720x576 : 中国的电视标准为PAL制  
024. int CurrentXRes = 720;
025. int CurrentYRes = 576;
026. int size        = CurrentXRes * CurrentYRes;
027.  
028. // 分配内存
029. byte *Video_Field0 = (byte*)malloc(CurrentXRes*CurrentYRes); 
030. byte *Video_Field1 = (byte*)malloc(CurrentXRes*CurrentYRes);
031.  
032. // 保存内存指针
033. byte *Video_Field0_ = Video_Field0;
034. byte *Video_Field1_ = Video_Field1;
035.  
036. // 初始化内存
037. ZeroMemory(Video_Field0, CurrentXRes*CurrentYRes);
038. ZeroMemory(Video_Field1, CurrentXRes*CurrentYRes);
039.  
040. byte yuv_y0, yuv_u0, yuv_v0; // yuv_v1;  // {y0, u0, v0, v1};
041. byte r, g, b;
042. byte bufRGB[3];  // 临时保存{R,G,B}
043. byte bufYUV[3];  // 临时保存{Y,U,V}
044.  
045. // 初始化数组空间
046. memset(bufRGB,0, sizeof(byte)*3);
047. memset(bufYUV,0, sizeof(byte)*3);
048.  
049. char strFileName[MAX_PATH]="720bmp.pal";
050.  
051. // 分配图片像素内存
052. RGBTRIPLE *rgb;
053. rgb = new RGBTRIPLE[CurrentXRes*CurrentYRes];
054.  
055. memset(rgb,0, sizeof(RGBTRIPLE)*CurrentXRes*CurrentYRes); // 初始化内存空间
056.  
057. CFile* f;
058. f = new CFile();
059. f->Open(strFileName, CFile::modeRead);
060. f->SeekToBegin();
061. f->Read(Video_Field0, CurrentXRes*CurrentYRes);
062. f->Read(Video_Field1, CurrentXRes*CurrentYRes);
063.  
064. // 上场  (1,3,5,7...行)
065. for ( int i = CurrentYRes-1; i>=0; i--) {
066. for ( int j = 0; j< CurrentXRes; j++) {
067. if(!(i%2)==0)
068. {
069. // UYVY标准  [U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5]
070. // 每像素点两个字节,[内]为四个字节
071. if ((j%2)==0)
072. {
073. yuv_u0 = *Video_Field0; 
074. Video_Field0++;
075. }
076. else
077. {
078. yuv_v0 = *Video_Field0; 
079. Video_Field0++;
080. }
081. yuv_y0 = *Video_Field0;     
082. Video_Field0++;
083.  
084. bufYUV[0] = yuv_y0;  // Y
085. bufYUV[1] = yuv_u0;  // U
086. bufYUV[2] = yuv_v0;  // V
087.  
088. // RGB转换为YUV
089. YUV2RGB(bufRGB,bufYUV);
090. r = bufRGB[0];   // y
091. g = bufRGB[1];   // u
092. b = bufRGB[2];   // v
093. if (r>255) r=255; if (r< 0) r=0;
094. if (g>255) g=255; if (g< 0) g=0;
095. if (b>255) b=255; if (b< 0) b=0;
096.  
097. for (int k=0; k< 1000; k++) ;  //延时
098. // 视图中显示
099. pDC->SetPixel(j, CurrentYRes-1-i, RGB(r, g, b));
100.  
101. }// end if i%2
102. }
103. }
104.  
105. // 下场 (2,4,6,8...行)
106. for ( int i_ = CurrentYRes-1; i_>=0; i_--) {
107. for ( int j_ = 0; j_< CurrentXRes; j_++) {
108. if((i_%2)==0)
109. {
110. // UYVY标准  [U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5]
111. // 每像素点两个字节,[内]为四个字节
112. if ((j_%2)==0)
113. {
114. yuv_u0 = *Video_Field1; 
115. Video_Field1++;
116. }
117. else
118. {
119. yuv_v0 = *Video_Field1; 
120. Video_Field1++;
121. }
122. yuv_y0 = *Video_Field1;     
123. Video_Field1++;
124.  
125. bufYUV[0] = yuv_y0;  // Y
126. bufYUV[1] = yuv_u0;  // U
127. bufYUV[2] = yuv_v0;  // V
128.  
129. // RGB转换为YUV
130. YUV2RGB(bufRGB,bufYUV);
131. r = bufRGB[0];   // y
132. g = bufRGB[1];   // u
133. b = bufRGB[2];   // v
134. if (r>255) r=255; if (r< 0) r=0;
135. if (g>255) g=255; if (g< 0) g=0;
136. if (b>255) b=255; if (b< 0) b=0;
137.  
138. for (int k=0; k< 1000; k++) ;  //延时
139. // 视图中显示
140. pDC->SetPixel(j_, CurrentYRes-1-i_, RGB(r, g, b));
141. }
142. }
143. }
144.  
145. // 提示完成
146. char buffer[80];
147. sprintf(buffer,"完成读取PAL文件:%s ", strFileName);
148. MessageBox(buffer, "提示信息", MB_OK | MB_ICONINFORMATION);
149.  
150. // 关闭PAL电视场文件
151. f->Close();
152.  
153. // 释放内存
154. free( Video_Field0_ );
155. free( Video_Field1_ );
156. delete f;
157. delete rgb;
158. }

结束语:

通过阅读本文,希望能让您理解一些RGB及YUV转换的细节,其实在一些开发包里已经提供了一些函数实现转换,本文只在于说明转换原理,没有对代码做优化,也没有对读取和写入格式做一些异常处理,希望您能体凉。YUV的格式非常多且复杂,本文只以UYVY为例,希望能起到抛砖引玉的作用。写本文之前笔者阅读了不少相关的文章不能一一列出,在此对他们无私的把自己的知识拿出来共享表示感谢。本文所带源码您可以直接到我的个人网站下载http://www.cgsir.com 。另外本人专门写了一个AVI转换为YUV视频格式的工具,如果您有需要,可以直接到我个人网站下载或直接与我联系。

参考文章:

Video Rendering with 8-Bit YUV Formats

关于YUV和RGB图像格式的问题

….

编程环境:Visual C++6.0 & MFC

关于作者:李英江目前就职于 湖南三辰卡通集团,是一名普通的程序员,可以通过以下方式与我取得联系。

QQ:15385155 MAIL: [email protected]

个人网站:http://www.cgsir.com

你可能感兴趣的:(yuv rgb)