音視頻學習:RGB

RGB

概念別問,問就是抄的Wikipedia,代碼來自雷霄驊先生的博客

基本概念

三原色光模式RGB color model),又稱RGB顏色模型紅綠藍顏色模型,是一種加色模型,將紅(Red)、綠(Green)、藍(Blue)三原色的色光以不同的比例相加,以合成產生各種色彩光。

一個顏色顯示的描述是由三個數值控制的,他分別爲RGB。當三個數值位爲最大時,顯示爲白色,當三個數值最小時,顯示爲黑色。

RGB可以和YUV互相轉換:
R=Y+1.13983(V128) R = Y + 1.13983 * (V - 128)

G=Y0.39465(U128)0.58060(V128) G = Y - 0.39465 * (U - 128) - 0.58060 * (V - 128)

B=Y+2.03211(U128) B = Y + 2.03211 * (U - 128)

RGB

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個字節,處理器可直接對其運算而不需額外的轉換)。同樣在一些特殊情況下,如DirectXOpenGL等環境,餘下的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圖片大小爲widthheight3Bytewidth * height * 3 Byte,這種存儲方式成爲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標準圖:

cie1931_500x500.rgb

效果圖如下:

output_rgb_r.y
output_rgb_g.y
output_rgb_b.y

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效果如下:

output_bmp.bmp

10. 將RGB24格式像素數據轉換爲YUV420P格式像素數據

調用下公式就行,回顧下公式:
Y=0.299R+0.587G+0.114B Y = 0.299 * R + 0.587 * G + 0.114 * B

U=0.169R0.331G+0.5B+128 U = -0.169 * R - 0.331 * G + 0.5 * B + 128

V=0.5R0.419G0.081B+128 V = 0.5 * R - 0.419 * G - 0.081 * B + 128

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;
}

結果如下:

output_yuv.yuv

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;
}

生成效果圖如下:

output_colobar.rgb
發佈了90 篇原創文章 · 獲贊 97 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章