天天看點

攝像頭圖像處理YUV轉RGB效率分析

1.文章簡述2. YUV轉RGB的代碼優化問題2.1 浮點轉換2.2 浮點轉整形2.3 浮點運算和整數運算在PC上模拟的效果3. x1000上進行對比測試3.1 使用軟浮點測試一幀圖像轉換時間3.2 開啟FPU後轉換圖像3.3 開啟FPU進行測試3.3.1 基本思路3.3.2 程式設計4. 總結

1.文章簡述

攝像輸出的圖像一般都是YUV格式的圖像,本文主要從攝像頭輸出的YUV格式圖像的角度出發,對圖像格式的轉換進行設計。同時對代碼的優化進行總結與整理。下面來詳細講述這些問題。

2. YUV轉RGB的代碼優化問題

從原理上來說,對于一個YUV轉RGB的代碼,可以從浮點和浮點轉整形這兩種方式進行轉換,而轉成整數後又可以利用MXU進行計算,應該可以加快運算速度。

在編寫代碼時,最開始的解決辦法都是從網上查找的資料,感覺可以實作基本的功能,但是對代碼沒有進行任何的優化,甚至還降低代碼的可讀性。這是我以前寫代碼時沒有認真總結的問題,經過夏總的指導,确實在這個上面需要認真的下點功夫。

2.1 浮點轉換

原始的代碼如下

y = 0.299*r + 0.587*g + 0.114*b;
u = -0.1687*r - 0.3313*g + 0.5*b + 128;
v = 0.5*r - 0.4187*g - 0.0813*b + 128;

y1 = 0.299*r1 + 0.587*g1 + 0.114*b1;
u1 = -0.1687*r1 - 0.3313*g1 + 0.5*b1 + 128;
v1 = 0.5*r1 - 0.4187*g1 - 0.0813*b1 + 128;
           

複制

這樣寫法邏輯上沒有什麼問題,但是細節上存在問題。首先是代碼的對稱性上來說,取的變量名應該是y0,u0,v0,y1,u1,v1這樣才能保證對稱性。第二可以将這些參數替代成宏,這樣的代碼更加整齊,閱讀性更好。

優化後

頭檔案定義:

/******************************************
*  YUV轉RGB公式:
*    [ 1      0        1.402 ]  [ Y ]    [ R ]
*    [ 1    -0.34414  -0.71414] * [ U ]  =  [ G ]
*    [ 1    1.1772      0  ]  [ V ]    [ B ]
******************************************/
//浮點
#define YUV2RGB_COEF00_FLOAT (1)
#define YUV2RGB_COEF01_FLOAT (0)
#define YUV2RGB_COEF02_FLOAT (1.402f)
#define YUV2RGB_COEF10_FLOAT (1)
#define YUV2RGB_COEF11_FLOAT (-0.34414f)
#define YUV2RGB_COEF12_FLOAT (-0.71414f)
#define YUV2RGB_COEF20_FLOAT (1)
#define YUV2RGB_COEF21_FLOAT (1.1772f)
#define YUV2RGB_COEF22_FLOAT (0)
           

複制

代碼

/*********************************************************************************
*  YUV2轉RGB格式(浮點計算),兩個rgb像素轉換一個yuv2像素
*  r = y1 + 1.4075*(v - 128);
*  g = y - 0.3455*(u - 128) - 0.7169*(v - 128);
*  b = y + 1.779*(u - 128);
**********************************************************************************/
int yuv2rgb_native(unsigned char *rgb, unsigned char *yuv, unsigned int width, unsigned int height)
{
    if (width < 1 || height < 1 || rgb == NULL || yuv == NULL)
    {
        return 0;
    }
    unsigned char y0, u, v, y1;
    int r0, g0, b0, r1, g1, b1;
    unsigned int i;
    int loop = (width*height) >> 1;
    for (i = 0; i < loop; i++)
    {
        y0 = yuv[0];
        u = yuv[1];
        y1 = yuv[2];
        v = yuv[3];
        yuv += 4;
        r0 = YUV2RGB_COEF00_FLOAT*y1 + YUV2RGB_COEF01_FLOAT*u + YUV2RGB_COEF02_FLOAT*(v - 128);
        g0 = YUV2RGB_COEF10_FLOAT*y0 + YUV2RGB_COEF11_FLOAT*(u - 128) + YUV2RGB_COEF12_FLOAT*(v - 128);
        b0 = YUV2RGB_COEF20_FLOAT*y0 + YUV2RGB_COEF21_FLOAT*(u - 128) + YUV2RGB_COEF22_FLOAT*v;
        r1 = YUV2RGB_COEF00_FLOAT*y1 + YUV2RGB_COEF01_FLOAT*u + YUV2RGB_COEF02_FLOAT*(v - 128);
        g1 = YUV2RGB_COEF10_FLOAT*y1 + YUV2RGB_COEF11_FLOAT*(u - 128) + YUV2RGB_COEF12_FLOAT*(v - 128);
        b1 = YUV2RGB_COEF20_FLOAT*y1 + YUV2RGB_COEF21_FLOAT*(u - 128) + YUV2RGB_COEF22_FLOAT*v;

        /*
        rgb[0] = (((TUNE(g0) & 0x1C) << 3) | (TUNE(b0) >> 3));
        rgb[1] = ((TUNE(r0) & 0xF8) | (TUNE(g0) >> 5));
        rgb[2] = (((TUNE(g1) & 0x1C) << 3) | (TUNE(b1) >> 3));
        rgb[3] = ((TUNE(r1) & 0xF8) | (TUNE(g1) >> 5));
        rgb += 4;
      */
        rgb[0] = TUNE(r0);
        rgb[1] = TUNE(g0);
        rgb[2] = TUNE(b0);
        rgb[3] = TUNE(r1);
        rgb[4] = TUNE(g1);
        rgb[5] = TUNE(b1);
        rgb += 6;

    }
    return 1;
}
           

複制

從整體的代碼風格上來說,一定要確定代碼盡可能的簡潔優美,代碼中用的比較多的常數可以用宏來進行表示。往往這些細節問題,容易忽視的問題,一定要重視。

2.2 浮點轉整形

在前面的文檔中,已經較長的描述了浮點轉整形的原理,現在隻是做一些細節上的優化和叙述。

定義轉換精度:

//定義轉換精度
#define YUV2RGB_COEF_SHIFT (8)
           

複制

定義系數宏:

#define YUV2RGB_COEF00_INT (1)
#define YUV2RGB_COEF01_INT (1)
#define YUV2RGB_COEF02_INT ((int)((YUV2RGB_FLOAT_COEF02 - 1) * (1<<YUV2RGB_COEF_SHIFT)))
#define YUV2RGB_COEF10_INT (1)
#define YUV2RGB_COEF11_INT ((int)((YUV2RGB_FLOAT_COEF11) * (1<<YUV2RGB_COEF_SHIFT)))
#define YUV2RGB_COEF12_INT ((int)((YUV2RGB_FLOAT_COEF12) * (1<<YUV2RGB_COEF_SHIFT)))
#define YUV2RGB_COEF20_INT (1)
#define YUV2RGB_COEF21_INT (1)
#define YUV2RGB_COEF22_INT ((int)((YUV2RGB_FLOAT_COEF21 - 1) * (1<<YUV2RGB_COcEF_SHIFT)))
           

複制

轉換函數

/*********************************************************************************
*  YUV2轉RGB格式(浮點轉整形計算),兩個rgb像素轉換一個yuv2像素
*  r = y1 + 1.4075*(v - 128);
*  g = y - 0.3455*(u - 128) - 0.7169*(v - 128);
*  b = y + 1.779*(u - 128);
*
*  YUV轉RGB公式:
*    [ 1      0        1.402 ]  [ Y ]    [ R ]
*    [ 1    -0.34414  -0.71414] * [ U ]  =  [ G ]
*    [ 1    1.1772      0  ]  [ V ]    [ B ]
*
*  r = y1 + v + ((104*v)>>8) -180
*  g = y - ((89*u)>>8) -((183*v)>>8) + 135
*  b = y + u + ((199*u)<<8) -227
**********************************************************************************/
int yuv2rgb(unsigned char *rgb, unsigned char *yuv, unsigned int width, unsigned int height)
{
    unsigned char y0, u, v, y1;
    int r0, g0, b0, r1, g1, b1;
    unsigned int i;
    int loop = (width*height) >> 1;//yuv圖像大小是
    for (i = 0; i < loop; i++)
    {
        y0 = yuv[0];
        u = yuv[1];
        y1 = yuv[2];
        v = yuv[3];
        yuv += 4;
        //簡化
        r0 = y1 + v + ((YUV2RGB_INT_COEF02*v) >> YUV2RGB_COEF_SHIFT) - 179;
        g0 = y0 + ((YUV2RGB_INT_COEF11* u) >> YUV2RGB_COEF_SHIFT) + ((YUV2RGB_INT_COEF12*v) >> YUV2RGB_COEF_SHIFT) + 135;
        b0 = y0 + u + ((YUV2RGB_INT_COEF22*u) >> YUV2RGB_COEF_SHIFT) - 150;
        r1 = y1 + v + ((YUV2RGB_INT_COEF02*v) >> YUV2RGB_COEF_SHIFT) - 179;
        g1 = y1 + ((YUV2RGB_INT_COEF11* u) >> YUV2RGB_COEF_SHIFT) + ((YUV2RGB_INT_COEF12*v) >> YUV2RGB_COEF_SHIFT) + 135;
        b1 = y1 + u + ((YUV2RGB_INT_COEF22*u) >> YUV2RGB_COEF_SHIFT) - 150;

        rgb[0] = TUNE(r0);
        rgb[1] = TUNE(g0);
        rgb[2] = TUNE(b0);
        rgb[3] = TUNE(r1);
        rgb[4] = TUNE(g1);
        rgb[5] = TUNE(b1);
        rgb += 6;

        /*
        rgb[0] = (((TUNE(g0) & 0x1C) << 3) | (TUNE(b0) >> 3));
        rgb[1] = ((TUNE(r0) & 0xF8) | (TUNE(g0) >> 5));
        rgb[2] = (((TUNE(g1) & 0x1C) << 3) | (TUNE(b1) >> 3));
        rgb[3] = ((TUNE(r1) & 0xF8) | (TUNE(g1) >> 5));
        rgb += 4;
        */
    }
    return 1;
}
           

複制

在這個代碼中主要注意将函數展開,這樣就是讓複雜的運算變成乘法或者加法運算,因為MXU有相關的乘法加法和移位運算。

2.3 浮點運算和整數運算在PC上模拟的效果

在PC機上模拟時間測試

攝像頭圖像處理YUV轉RGB效率分析

可以看到YUV2RGB_Native函數運作時間11158us,也就是浮點轉換的時間為11158us。

而轉換成整形後需要7444us。明顯轉換成整數後效率要高。

圖像品質比較:

原圖:

攝像頭圖像處理YUV轉RGB效率分析

浮點轉換:

攝像頭圖像處理YUV轉RGB效率分析

經過整形轉換後的圖

攝像頭圖像處理YUV轉RGB效率分析

從上面的效果上可以看出,基本上圖形效果比較好。

3. x1000上進行對比測試

在開發闆上進行測試主要從以下幾個方面進行:

  • 不開啟FPU的情況下測試浮點和整形一幀圖像轉換時間
  • 開啟FPU的情況下測試浮點和整形一幀圖像轉換時間
  • 在利用MXU進行優化後的一幀圖像轉換時間

3.1 使用軟浮點測試一幀圖像轉換時間

開啟軟浮點需要在編譯選項中添加

-msoft-float
           

複制

然後找到

ingenic-linux-kernel3.10.14-x1000-v5.0-20161213\prebuilts\toolchains\mips-gcc472-glibc216\lib\gcc\mips-linux-gnu\4.7.2\soft-float\libgcc.a
           

複制

檔案添加到到application目錄下。

然後編輯application目錄中的SConsript

from building import *

cwd = GetCurrentDir()
src = Glob('*.c') + Glob('*.cpp')+ Glob('*.a')

CPPPATH = [cwd, str(Dir('#'))]

group = DefineGroup('Applications', src, depend = [''],CPPPATH = CPPPATH)

Return('group')
           

複制

浮點運算時間

攝像頭圖像處理YUV轉RGB效率分析

轉換一幀圖像需要的時間是137ms

浮點轉整形運算時間

攝像頭圖像處理YUV轉RGB效率分析

經過轉換隻需要9ms。也就是說,将浮點轉換成整形後,效率提高了15倍。

3.2 開啟FPU後轉換圖像

在linux系統下編譯,并利用君正提供的gcc。預設情況下是支援FPU的,是以首先需要将編譯選項中的-msoft-float去掉。

浮點運算時間

攝像頭圖像處理YUV轉RGB效率分析

可見轉換一幀圖像後運算時間為12ms。

浮點轉整形運算時間

攝像頭圖像處理YUV轉RGB效率分析

浮點轉整形後速度還是要快一些。

3.3 開啟FPU進行測試

3.3.1 基本思路

總體的代碼如下

r0 = y1 + v + a1 - 179;
g0 = y0 + b1 + c1 + 135;
b0 = y0 + u + d1 - 150;
r1 = y1 + v + a1 - 179;
g1 = y1 + b1 + c1 + 135;
b1 = y1 + u + d1 - 150;
           

複制

上面簡化其實就是

a1:((YUV2RGB_INT_COEF02*v) >> YUV2RGB_COEF_SHIFT)

b1:((YUV2RGB_INT_COEF11* u) >> YUV2RGB_COEF_SHIFT)

c1:((YUV2RGB_INT_COEF12*v) >> YUV2RGB_COEF_SHIFT)

d1:((YUV2RGB_INT_COEF22*u) >> YUV2RGB_COEF_SHIFT)

實際上這就是一個乘法,加法,移位運算。考慮到乘法和移位比較消耗時間,可以在代碼中隻做加減操作,乘法和移位用MXU來進行。

主要用到的指令

而在MXU中有一個8位的乘法指令

攝像頭圖像處理YUV轉RGB效率分析

也就是說可以将四個char類型的數填充到32位的寄存器中,得到的資料是4個16位的short型資料。

是以得到xra,xrd後然後将這兩個寄存器的值移位

攝像頭圖像處理YUV轉RGB效率分析

是以這四個乘法和移位計算由兩條MXU指令即可完成

3.3.2 程式設計

将四個char類型系數放在src1中,将四個char類型的u,v分量放在src2中

int yuv2rgb(unsigned char *rgb, unsigned char *yuv, unsigned int width, unsigned int height)
{
    // if (width < 1 || height < 1 || rgb == NULL || yuv == NULL)
    // {
    //    return 0;
    // }
    unsigned char src1[4];
    unsigned char src2[4];
    unsigned short dst1[2];
    unsigned short dst2[2];
    unsigned char y0, u, v, y1;
    int r0, g0, b0, r1, g1, b1;
    src1[0] = YUV2RGB_INT_COEF02 ;
    src1[1] = -YUV2RGB_INT_COEF11;//變成正數
    src1[2] = -YUV2RGB_INT_COEF12;//變成正數
    src1[3] = YUV2RGB_INT_COEF22 ;
    unsigned int i;
    int loop = (width*height) >> 1;

    for (i = 0; i < loop; i++)
    {
        y0 = yuv[0];
        u = yuv[1];
        y1 = yuv[2];
        v = yuv[3];
        yuv += 4;

        src2[0] = v;
        src2[1] = u;
        src2[2] = v;
        src2[3] = u;

        S32LDDR(xr1, src1, 0);//将src1的資料放在xr1中
        S32LDDR(xr2, src2, 0);//将src2的資料放在xr2中
        Q8MUL(xr3, xr1, xr2, xr4);//将xr1與xr2每八位相乘,得到高位放xr3,低位放xr4
        Q16SLR(xr5, xr3, xr4, xr6, YUV2RGB_COEF_SHIFT);//将xr3與xr4進行移位
        S32STD(xr5, dst1, 0);// dst1[1] Short3 dst1[0] Short2
        S32STD(xr6, dst2, 0);// dst2[1] Short1 dst2[0] Short0

        r0 = y1 + v + dst1[1] - 179;
        g0 = y0 - dst1[0] - dst2[1]  + 135;
        b0 = y0 + u + dst2[0] - 150;
        r1 = y1 + v + dst1[1]- 179;
        g1 = y1 - dst1[0] - dst2[1] + 135;
        b1 = y1 + u + dst2[0] - 150;

        rgb[0] = (((TUNE(g0) & 0x1C) << 3) | (TUNE(b0) >> 3));
        rgb[1] = ((TUNE(r0) & 0xF8) | (TUNE(g0) >> 5));
        rgb[2] = (((TUNE(g1) & 0x1C) << 3) | (TUNE(b1) >> 3));
        rgb[3] = ((TUNE(r1) & 0xF8) | (TUNE(g1) >> 5));
        rgb += 4;
    }
    return 1;
}
           

複制

經過測試,效果如下

攝像頭圖像處理YUV轉RGB效率分析

發現效果并沒有預想中的那麼明顯。依然和整形轉換一幀圖像時間差不多。和之前的猜想不相符,如果将幾條乘法指令并行執行,可能會效果好很多,但實際測試發現優化好不了多少。後面再将加減法進行一下MXU的優化,看一下能不能有更好的優化方案。

4. 總結

本文主要測試YUV轉RGB的幾種方法的效率問題,得到的結論是定點化處理更加的高效。如果用浮點運算,會消耗大量的硬體資源。