彩色空間轉換
實驗原理
YUV到RGB空間的轉換公式
由電視原理知識可知,數字視頻系統中對色差信號的壓縮公式爲:
Cr=0.713(R−Y)
Cb=0.564(B−Y)
此時色差信號經過歸一化處理,動態範圍爲-0.5-0.5,讓色差零電平對應碼電平128(8比特量化時),則有:
V=0.713(R−Y)+128
U=0.564(B−Y)+128
易推得YUV到RGB空間的轉換公式爲:
R=Y+1.4075(V−128)
G=Y−0.3455(U−128)−0.7169(V−128)
B=Y+1.779(U−128) 取樣格式
YUV空間的取樣格式爲4:2:0,U、V兩個色差信號的亮度信號取樣頻率的四分之一,在水平方向和垂直方向上的取樣點數均爲Y的一半。而RGB空間的取樣格式爲4:4:4,則需要對U、V分量做上採樣,讓水平方向和垂直方向上取樣點數與Y一致。- 存儲方式與佔用空間
YUV格式的視頻將整幀圖像的Y打包存儲,依次再存整幀的U、V,然後再是下一幀的數據;YUV格式按4:2:0取樣的視頻所佔空間大小爲Width*Height*Frame*1.5(Width –寬、Height–高、Frame–幀數)。RGB格式的視頻按每個像素B、G、R的順序儲存數據;所佔空間大小爲Width*Height*Frame*3。 - YUV轉換爲RGB後顏色溢出的處理
經過上式計算,原來爲整數的Y、U、V轉換爲浮點數R、G、B,由於RGB文件的顏色是8bit量化,則數值不在0-255範圍內的顏色會出現溢出,顯示錯誤。本實驗採用了定義float型的中間變量存放計算RGB的直接結果,而後通過簡單的判斷防止顏色溢出。 - 帶參數的主函數的參數輸入
打開項目屬性窗口,通過設置工作目錄、命令參數完成主函數的參數輸入。
YUV2RGB文件轉換流程分析
- 程序初始化(打開兩個文件、定義變量和緩衝區 等)
- 讀取YUV文件,抽取YUV數據寫入緩衝區
- 調用YUV2RGB的函數實現YUV到RGB數據的轉換
- 寫RGB文件
- 程序收尾工作(關閉文件,釋放緩衝區)
關鍵代碼及其分析
分析部分寫在代碼註釋中
YUVtoRGB.cpp
#include "stdlib.h"
#include "YUVtoRGB.h"
static float YUVRGB14075[256];
static float YUVRGB03455[256];
static float YUVRGB07169[256];
static float YUVRGB1779[256];
int YUV2RGB (int Width, int Height, void *rgb_out, void *y_in, void *u_in, void *v_in)
{
static int init_done = 0;
long i, j, size;
unsigned char *r, *g, *b;
float rf, gf, bf;//中間變量,用來防止色彩溢出
unsigned char *y, *u, *v;
unsigned char *y_buffer, *u_buffer, *v_buffer,*rgb_buffer;
unsigned char *sub_u_buf, *sub_v_buf;
if (init_done == 0)
{
InitLookupTable();
init_done = 1;
}
// check to see if width and height are divisible by 2
if ((Width % 2) || (Height % 2)) return 1;
size = Width * Height;
y_buffer = (unsigned char *)y_in;
u_buffer = (unsigned char *)u_in;
v_buffer = (unsigned char *)v_in;
rgb_buffer = (unsigned char *)rgb_out;
sub_u_buf = (unsigned char *)malloc(Width*Height);
sub_v_buf = (unsigned char *)malloc(Width*Height);
//sub_u_buf、sub_v_buf用於存放寬高都上採樣以後的U/V,便於計算RGB
/************************上採樣***********************/
for (i = 0; i < Height; i++)
{
for (j = 0; j < Width; j++)
{
*(sub_u_buf + i*Width + j) = *(u_buffer + (i / 2)*Width/2 + j / 2);
*(sub_v_buf + i*Width + j) = *(v_buffer + (i / 2)*Width /2+ j / 2);
}
}
/****************************************************/
b = rgb_buffer;//通過r/g/b三個指針改變rgb_buffer的內容值
y = y_buffer;
u = sub_u_buf;
v = sub_v_buf;
for (i = 0; i < Height; i++)
{
for (j = 0; j < Width; j++)
{
g = b + 1;//RGB格式文件儲存按照BGR的順序
r = b + 2;
rf= (*y + YUVRGB14075[*v]);
gf = (*y - YUVRGB03455[*u] - YUVRGB07169[*v]);
bf = (*y + YUVRGB1779[*u]);
*r =(rf>0?(rf>255? 255:(unsigned char)rf):0);
*g = (gf>0 ? (gf>255 ? 255 : (unsigned char)gf) : 0);
*b = (bf>0 ? (bf>255 ? 255 : (unsigned char)bf) : 0);
/*由YUV到RGB的轉換公式可知,經過計算得到的RGB值都不是整數,由於計算
係數的精度,RGB值有可能出現超出0-255的範圍。由於unsigned char型
數據佔1字節,超出0-255的數值會溢出,發生彩色顯示錯誤。例如256會變爲
0。
可以採用一個float型中間變量(rf/bf/gf)來暫存由彩色轉換公式計算得
來的RGB值,經過判斷以後,再給unsigned char型的r/g/b進行賦值,大
於255則賦值爲255,小於0則賦值爲0,在0-255範圍內的數值則進行強制類
型轉換。*/
b += 3;
y++;
u++;
v++;
}
}
if (sub_u_buf != NULL) free(sub_u_buf);
if (sub_v_buf != NULL) free(sub_v_buf);
return 0;
}
void InitLookupTable()
{
int i;
for (i = 0; i < 256; i++) YUVRGB14075[i] = (float)1.4075 * (i-128);
for (i = 0; i < 256; i++) YUVRGB03455[i] = (float)0.3455 * (i-128);
for (i = 0; i < 256; i++) YUVRGB07169[i] = (float)0.7169 * (i-128);
for (i = 0; i < 256; i++) YUVRGB1779[i] = (float)1.779 * (i-128);
}
/*提前計算彩色轉換公式中的分量值。需要特別注意的是UV值需要提前
減128,也就是說,計算分量值時保證UV的範圍在-128~127。*/
main.cpp
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include"YUVtoRGB.h"
#define u_int8_t unsigned __int8
#define u_int unsigned __int32
#define u_int32_t unsigned __int32
//爲了防止不同編程軟件數據類型int的字節數不一致,利用define進行規定
int main(int argc,char ** argv)
{
u_int frameWidth = 352;
u_int frameHeight = 240;
int i;
char *rgbFileName= NULL;
char *yuvFileName= NULL;
FILE *rgbFile=NULL;
FILE *yuvFile=NULL;
u_int8_t* rgbBUFFER = NULL;
u_int8_t* yBUFFER = NULL;
u_int8_t* uBUFFER= NULL;
u_int8_t* vBUFFER = NULL;
u_int32_t videoFramesWritten = 0;
yuvFileName = argv[1];
rgbFileName = argv[2];
frameWidth = atoi(argv[3]);
frameHeight = atoi(argv[4]);
//argv[0]是缺省的,軟件自動會裝填Project當中的exe文件名
yuvFile = fopen(yuvFileName, "rb");
if (yuvFile == NULL)
{
printf("cannot find yuv file\n");
exit(1);
}
else
{
printf("The output yuv file is %s\n", yuvFileName);
}
rgbFile = fopen(rgbFileName, "wb");
if (rgbFile == NULL)
{
printf("cannot find rgb file\n");
exit(1);
}
else
{
printf("The input rgb file is %s\n", rgbFileName);
}
/* get an input buffer for a frame */
rgbBUFFER= (u_int8_t *)malloc(frameWidth * frameHeight * 3);
/* get the output buffers for a frame */
yBUFFER= (u_int8_t *)malloc(frameWidth * frameHeight);
uBUFFER= (u_int8_t *)malloc(frameWidth * frameHeight/4);
vBUFFER= (u_int8_t *)malloc(frameWidth * frameHeight/4);
if (rgbBUFFER == NULL || yBUFFER == NULL || uBUFFER == NULL || vBUFFER == NULL)
{
printf("no enought memory\n");
exit(1);
}
while(fread(yBUFFER,1,frameWidth*frameHeight,yuvFile)
&&fread(uBUFFER,1,frameWidth*frameHeight/4,yuvFile)
&&fread(vBUFFER,1,frameWidth*frameHeight/4,yuvFile))
/*當讀入成功時fread函數返回實際讀取的數據項個數,當整幀的YUV都能讀
入時,進入while循環。由於文件指針自動向後移動到下一個未讀字節,故該
程序也適用於視頻文件,整段視頻被讀完fread函數返回0,則結束循環。*/
{
if(YUV2RGB(frameWidth, frameHeight, rgbBUFFER, yBUFFER, uBUFFER, vBUFFER))
{
printf("error");
return 0;
}
fwrite(rgbBUFFER, 1, frameWidth * frameHeight*3, rgbFile);
printf("\r...%d", ++videoFramesWritten);
}
printf("\n%u %ux%u video frames written\n",
videoFramesWritten, frameWidth, frameHeight);
if(rgbFile!=NULL) fclose(rgbFile);
if(yuvFile!=NULL) fclose(yuvFile);
if(rgbBUFFER!=NULL) free(rgbBUFFER);
if(yBUFFER!=NULL) free(yBUFFER);
if(uBUFFER!=NULL) free(uBUFFER);
if(vBUFFER!=NULL) free(vBUFFER);
return(0);
}
實驗結果及其分析
將轉換得的RGB文件輸入已有的RGB2YUV程序,得到新的YUV文件,用YUVplayer打開新的YUV文件查看結果。
down.yuv
由於計算係數精度、強制類型轉換等帶來的誤差,經過彩色空間轉換的YUV值會存在些許差異,但從畫面、數據上看,這個差異都不算太明顯。
沒有采用中間變量,彩色溢出的情況
下面給出另外三個YUV視頻文件進行彩色空間轉換的結果,佐證該程序是正確的。
akiyo.yuv
src01.yuv
src04.yuv