音視頻學習:RGB
RGB
概念別問,問就是抄的Wikipedia,代碼來自雷霄驊先生的博客。
基本概念
三原色光模式(RGB color model),又稱RGB顏色模型或紅綠藍顏色模型,是一種加色模型,將紅(Red)、綠(Green)、藍(Blue)三原色的色光以不同的比例相加,以合成產生各種色彩光。
一個顏色顯示的描述是由三個數值控制的,他分別爲R、G、B。當三個數值位爲最大時,顯示爲白色,當三個數值最小時,顯示爲黑色。
RGB可以和YUV互相轉換:
RGB幾種常見的表示形式
16比特模式
16比特模式分配給每種原色各爲5比特,其中綠色爲6比特,因爲人眼對綠色分辨的色調更精確。但某些情況下每種原色各佔5比特,餘下的1比特不使用。
24比特模式
每像素24位(比特s per pixel,bpp)編碼的RGB值:使用三個8位無符號整數(0到255)表示紅色、綠色和藍色的強度。這是當前主流的標準表示方法,用於真彩色和JPEG或者TIFF等圖像文件格式裏的通用顏色交換。它可以產生一千六百萬種顏色組合,對人類的眼睛來說,其中有許多顏色已經是無法確切的分辨。
說白了就是先存R(8bit),再存G(8bit),再存B(8bit),一共24bit,每個顏色256個梯度,交錯地以RGBRGBRGB...
這樣的形式存在文件裏面。
32比特模式
實際就是24比特模式,餘下的8比特不分配到像素中,這種模式是爲了提高數據輸送的速度(32比特爲一個DWORD,DWORD全稱爲Double Word,一般而言一個Word爲16比特或2個字節,處理器可直接對其運算而不需額外的轉換)。同樣在一些特殊情況下,如DirectX、OpenGL等環境,餘下的8比特用來表示象素的透明度(Alpha)。
RGB_PARSER
1. 分離RGB24像素數據中的R、G、B分量
bool RgbParser::rgb24_split(const std::string input_url, int width, int height, int frame_num)
{
FILE *input_file = fopen(input_url.c_str(), "rb+");
FILE *output_r = fopen("output_rgb_r.y", "wb+");
FILE *output_g = fopen("output_rgb_g.y", "wb+");
FILE *output_b = fopen("output_rgb_b.y", "wb+");
unsigned char *picture = new unsigned char[width * height * 3];
for (int i = 0; i < frame_num; i++) {
fread(picture, 1, width * height * 3, input_file);
for (int cur_pixel = 0; cur_pixel < width * height * 3; cur_pixel += 3) {
// Read R
fwrite(picture + cur_pixel, 1, 1, output_r);
// Read G
fwrite(picture + cur_pixel + 1, 1, 1, output_g);
// Read B
fwrite(picture + cur_pixel + 2, 1, 1, output_b);
}
}
delete[] picture;
fclose(input_file);
fclose(output_r);
fclose(output_g);
fclose(output_b);
return true;
}
分離後查看圖片用yuvplayer查看,選擇500*500,Y分量查看。
RGB24的每個像素三個分量都是連續存儲的,先存儲第一個像素的R、G、B,再存第二個R、G、B,所以寬(width)和高(height)的RGB圖片大小爲,這種存儲方式成爲Packed方式。
R、G、B三張圖分辨率如下(此處有疑問。。沒搞懂)
- output_rgb_r.y:R數據,分辨率256 *256
- output_rgb_g.y:G數據,分辨率256 *256
- output_rgb_b.y:B數據,分辨率256 *256
原圖爲雷神提供的CIE 1931標準圖:
效果圖如下:
2. 將RGB格式像素數據封裝爲BMP圖像
將RGB轉換爲BMP圖像,就可以使用Windows自帶的圖片查看器查看,對於BMP來說,如果是未壓縮的格式,那麼除了頭部信息外,剩下的數據和RGB類似,只不過是以B、G、R的順序存儲,所以只需要加上頭並且略加修改即可。
首先了解下BMP的格式:
BMP_FILE_HEADER
BMP文件有文件頭,在Windows中定義如下:
typedef struct tagBITMAPFILEHEADER
{
UINT16 bfType;
DWORD bfSize;
UINT16 bfReserved1;
UINT16 bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
變量名 | 地址偏移 | 大小 | 作用 |
---|---|---|---|
bfType | 0x0000H | 2 Byte | 一般爲’BM’,在小端的機器時要注意字節序問題 |
bfSize | 0x0002H | 4 Byte | 位圖文件的大小,字節爲單位 |
bfReserverd1 | 0x0006H | 2 Byte | 保留,必須設置爲0 |
bfReserverd2 | 0x0008H | 2 Byte | 保留,必須設置爲0 |
bfbfOffBits | 0x000AH | 4 Byte | 說明從文件頭開始到實際數據之間的偏移量 |
BMP_INFO_HEADER
typedef struct tagBITMAPINFOHEADER
{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
變量名 | 地址偏移 | 大小 | 作用 |
---|---|---|---|
biSize | 0x000EH | 4 Byte | BITMAPINFOHEADER結構需要的字數 |
biWidth | 0x0012H | 4 Byte | 說明圖像的寬度,以像素爲單位 |
biHeight | 0x0016H | 4 Byte | 說明圖像的高度,以像素爲單位。 若爲正值,則圖像爲倒向; 若爲負值,則圖像爲正向 |
biPlanes | 0x001AH | 2 Byte | 爲目標設備說明顏色平面數,一般爲1 |
biBitCount | 0x001CH | 2 Byte | 一像素需要的比特數,可以爲1、4、8、16、24 |
biCompression | 0x001EH | 4 Byte | 說明圖像數據壓縮類型,最常用爲0,不壓縮 |
biSizeImage | 0x0022H | 4 Byte | 說明圖像字節大小 |
biXPelsPerMeter | 0x0026H | 4 Byte | 說明水平分辨率,可以設置爲0 |
biYPelsPerMeter | 0x002AH | 4 Byte | 說明垂直分辨率,可以設置爲0 |
biClrUsed | 0x002EH | 4 Byte | 說明位圖使用的顏色索引數,爲0表示所有調色板 |
biClrImportant | 0x0032H | 4 Byte | 對圖像顯示有影響的顏色索引數目,0表示都重要 |
color palette
代碼中沒有用到調色板,不過可以有。
調色板其實是一張映射表,標識顏色索引號與其代表的顏色的對應關係。它在文件中的佈局就像一個二維數組palette[N][4]
,其中N表示總的顏色索引數,每行的四個元素分別表示該索引對應的B、G、R和Alpha的值,每個分量佔一個字節。如不設透明通道時,Alpha爲0。
代碼
代碼中有一個地方需要注意,那就是BITMAP_FILE_HEADER::bfType
這個值,由於我的機器是小端的(應該所有Windows都是小端的吧),在賦值的時候我一開始寫的是('B') << 8) + 'M'
,結果出問題了,windows圖片查看器識別不出來文件格式,其實是圖片查看器讀到了MB導致的,所以正確的寫法應該是('M') << 8) + 'B'
,這樣在小端機器上是能跑成的。
bool RgbParser::rgb24_to_bmp(const std::string input_url, int width, int height)
{
FILE *input_file = fopen(input_url.c_str(), "rb+");
FILE *output_bmp = fopen("output_bmp.bmp", "wb+");
unsigned char *picture = new unsigned char[width * height * 3];
BMP::BIT_MAP_FILE_HEADER bmp_file_header;
BMP::BIT_MAP_INFO_HEADER bmp_info_header;
// Fill bit map file header
// Note: the bfType is only useful in little endian machine.
bmp_file_header.bfType = ((unsigned short int)('M') << 8) + 'B';
bmp_file_header.bfSize = sizeof(bmp_file_header) + sizeof(bmp_info_header) + width * height * 3 ;
bmp_file_header.bfReserverd1 = 0;
bmp_file_header.bfReserverd2 = 0;
bmp_file_header.bfbfOffBits = sizeof(bmp_file_header) + sizeof(bmp_info_header);
// Fill bit map info header
bmp_info_header.biSize = sizeof(bmp_info_header);
bmp_info_header.biWidth = width;
bmp_info_header.biHeight = -height;
bmp_info_header.biPlanes = 1; // Must be 1
bmp_info_header.biBitcount = 24; // RGB24 need 24 bit to description one pixel
bmp_info_header.biCompression = 0; // 0 means without compression
bmp_info_header.biSizeImage = width * height * 3; // RGB24 can set it to 0
bmp_info_header.biXPelsPermeter = 0; // Most time it set to 0
bmp_info_header.biYPelsPermeter = 0; // Most time it set to 0
bmp_info_header.biClrUsed = 0; // 0 means use all palette
bmp_info_header.biClrImportant = 0; // 0 means all color is important
unsigned char temp_val = 0;
fread(picture, 1, width * height * 3, input_file);
for (int cur_pixel = 0; cur_pixel < width * height * 3; cur_pixel += 3) {
temp_val = picture[cur_pixel];
picture[cur_pixel] = picture[cur_pixel + 2];
picture[cur_pixel + 2] = temp_val;
}
fwrite(&bmp_file_header, 1, sizeof(bmp_file_header), output_bmp);
fwrite(&bmp_info_header, 1, sizeof(bmp_info_header), output_bmp);
fwrite(picture, 1, width * height * 3, output_bmp);
delete[] picture;
fclose(input_file);
fclose(output_bmp);
return true;
}
使用lena512的rgb24圖片生成bmp效果如下:
10. 將RGB24格式像素數據轉換爲YUV420P格式像素數據
調用下公式就行,回顧下公式:
U取第1行第一個點,V取第一行第三個點,然後U再取第一行第5個點,然後V取第一行第七個點,以此類推,寫出如下代碼,雷神用的是整數去算的方式,其實也差不多,結果是一樣的。
bool RgbParser::rgb24_to_yuv420p(const std::string input_url, int width, int height)
{
FILE *input_file = fopen(input_url.c_str(), "rb+");
FILE *output_yuv = fopen("output_rgb_to_yuv.yuv", "wb+");
unsigned char *rgb_picture = new unsigned char[width * height * 3];
unsigned char *yuv_picture = new unsigned char[width * height * 3 / 2];
int cur_y = 0, cur_u = width * height, cur_v = width * height * 5 / 4;
int cur_width = 0, cur_height = 0;
unsigned char r = 0, g = 0, b = 0;
fread(rgb_picture, 1, width * height * 3, input_file);
for (int cur_pixel = 0; cur_pixel < width * height * 3; cur_pixel += 3) {
r = rgb_picture[cur_pixel];
g = rgb_picture[cur_pixel + 1];
b = rgb_picture[cur_pixel + 2];
yuv_picture[cur_y++] = (unsigned char)(0.299 * (double)r + 0.587 * (double)g + 0.114 * (double)b);
cur_width = (cur_pixel / 3) % width;
cur_height = (cur_pixel / 3) / width;
// Every four
if (cur_width % 2 == 0 && cur_height % 2 == 0) {
yuv_picture[cur_u++] = (unsigned char)(-0.169 * (double)r - 0.331 * (double)g + 0.5 * (double)b + 128);
}
else {
if (cur_width % 2 == 0) {
yuv_picture[cur_v++] = (unsigned char)(0.5 * (double)r - 0.419 * (double)g - 0.081 * (double)b + 128);
}
}
}
fwrite(yuv_picture, 1, width * height * 3 / 2, output_yuv);
delete[] rgb_picture;
delete[] yuv_picture;
fclose(input_file);
fclose(output_yuv);
return true;
}
結果如下:
4. 生成RGB24格式的彩條測試圖
彩條順序”白黃青綠品紅藍黑“,RGB值如下:
顏色 | R | G | B |
---|---|---|---|
白 | 255 | 255 | 255 |
黃 | 255 | 255 | 0 |
青 | 0 | 255 | 255 |
綠 | 0 | 255 | 0 |
品 | 255 | 0 | 255 |
紅 | 255 | 0 | 0 |
藍 | 0 | 0 | 255 |
黑 | 0 | 0 | 0 |
bool RgbParser::rgb24_colorbar(int width, int height)
{
FILE *output_rgb = fopen("output_colorbar.rgb", "wb+");
unsigned char *picture = new unsigned char[width * height * 3];
int width_range = width / 8;
int cur_width_max = width_range;
char color[8][3] = {
{ 255, 255, 255 }, { 255, 255, 0 }, { 0, 255, 255 },
{ 0, 255, 0 }, { 255, 0, 255 }, { 255, 0, 0 },
{ 0, 0, 255 }, { 0, 0, 0 }
};
for (int cur_width = 0; cur_width < width; cur_width++) {
// NOTE: All the extra pixels in the end are in black.
int cur_color_pos = cur_width / width_range == 8 ? 7 : cur_width / width_range;
for (int cur_height = 0; cur_height < height; cur_height++) {
int cur_pixel = width * cur_height * 3 + cur_width * 3;
picture[cur_pixel] = color[cur_color_pos][0];
picture[cur_pixel + 1] = color[cur_color_pos][1];
picture[cur_pixel + 2] = color[cur_color_pos][2];
}
}
fwrite(picture, 1, width * height * 3, output_rgb);
delete[] picture;
fclose(output_rgb);
return true;
}
生成效果圖如下: