No.5 YUV420 格式圖像旋轉

概述

分別在 CPU 上使用 C 語言和在 GPU 上使用 OpenCL 對一張 YUV420p 的圖像順時針旋轉 90 度,比較兩者之間的性能差異。GPU 使用了 Mali T-880 和 AMD RX 460 作對比。完整代碼參見 No.1_OpenCLRotate,Ubuntu 系統環境下完整代碼參見No.2_OpenCLRotate

簡介

YUV 和我們熟知的 RGB 類似,是一種顏色編碼格式。它主要用於電視系統和模擬視頻鄰域(如 Camera 系統)。YUV 包含三個分量,其中 Y 表示明亮度(Luminance 或 Luma),也就是灰度值。而 UV 則表示色度(Chrominance 或 Chroma),作用是描述圖像色彩及飽和度,用於指定像素的顏色。沒有 UV 分量信息,一樣可以顯示完整的圖像,只不過是黑白的灰度圖像。YUV 格式的好處是很好地解決了彩色電視機與黑白電視機的兼容問題。而且 YUV 不像 RGB 那樣要求三個獨立的視頻信號同時傳輸,所以用 YUV 方式傳送佔用極少的頻寬。

YUV 格式包含兩大類,分別爲 planar 和 packed:

  • 對於 planar 格式,先連續存儲所有像素點的 Y 分量,緊接着存儲所有像素點的 U 分量,最後是所有像素點的 V 分量;
  • 對於 packed 格式,和 planar 格式一樣,也是先連續存儲所有像素點的 Y 分量,緊接着存儲所有像素點的 UV 分量。不同的是,UV 分量是交替存放的。

在 YUV420 中,一個像素點對應一個 Y 分量,一個 2x2 的小方塊對應一個 U 和 V,上面兩種格式的 Y 值在排列是完全相同的(只有 Y 的圖像就是灰度圖像)。YUV420p 與 YUV420sp 的數據格式在 UV 的排列上不同,420p 它是先存放完 U 分量,再存放 V 放量,也就是說他們的 UV 分量是連續的。而 420sp 它的 UV 分量是按照 UV、UV…… 這樣交替存放的。

假設原始圖像的寬度和高度分別使用 w 和 h 表示,旋轉前的圖像尺寸是 w x h,旋轉後的圖像尺寸是 h x w,旋轉前後圖像大小保持不變。對於 YUV420p 數據格式,其數據在內存中安排如下:

  • 圖像佔用的內存空間大小: w * h * 3 / 2;
  • Y 分量佔用的內存大小爲 w * h,從內存起始位置開始存放;
  • Y 分量中每個 2x2 的小方塊對應一個 U 和 V;
  • U 分量大小爲 (w/2)*(h/2),存放的位置從圖像的 w * h 偏移位置開始;
  • V 分量大小爲 (w/2)*(h/2),存放的位置從圖像的 w * h + (w * h)/4 偏移位置開始。

下面是 YUV420sp 和 YUV420p 圖像格式在內存中的存儲情況,這些數據在內存中是線性存儲的。當使用圖片瀏覽器顯示的是時候需要指定它們的尺寸(寬度和高度),否則不能正確顯示。例如,40x30 和 20x60 這兩張圖像的像素點的個數相同,但如果把 40x30 的圖像按照 20x60 的圖像來顯示,就會出現顯示異常。

YUV420sp 格式

YUV420p 格式

把 YUV420p 的圖像順時針旋轉 90 度後,Y 分量在內存中存放如下圖所示,隨後是連續的 U 分量 和 V 分量。

YUV420p 旋轉 90 度

旋轉步驟如下,把圖像看作是一個類似 image[w][h] 的二維數組。

  • 先處理 Y 分量,原始圖像的第一個像素位置爲 (h-1)*w,旋轉後的目標位置爲 (0, 0);第二個像素位置爲 (h-2)*w,旋轉後的目標位置爲 (0, 1),以此類推;
  • 接下來處理 U 分量,U 和 V 分量的大小都爲 (w/2)*(h/2)。原圖的第一個像素位置爲 (w*h)+(((h/2)-1)*(w/2)),旋轉後的目標位置爲(w*h)+(0, 0);第二個像素位置爲 (w*h)+(((h/2)-2)*(w/2)),旋轉後的目標位置爲 (w*h)+(0, 1),以此類推;
  • 最後處理 V 分量。原圖的第一個像素位置爲 (w*h)+(w*h/4)+(((h/2)-1)*(w/2)),旋轉後的目標位置爲 (w*h)+(w*h/4)+(0, 0);第二個像素位置爲 (w*h)+(w*h/4)+(((h/2)-2)*(w/2)),旋轉後的目標位置爲 (w*h)+(w*h/4)+(0, 1),以此類推。

實現

函數 yuv420p_rotate_normal 是 C 語言實現。該函數接收圖像的寬度和高度,將 YUV420p 圖像順時針旋轉 90 度。在處理器執行算數運算時,除法往往需要多個 CPU 週期才能完成,而加法和移位等操作能在單個 CPU 週期內完成,爲了提升除發運算的執行效率,在 yuv420p_rotate_shift 函數中使用移位操作來替代 yuv420p_rotate_normal 中的除法運算。

1.算法實現

採用 C 語言的算法實現如下:

/**
 * 使用 C 語言在 CPU 上執行旋轉操作。
 */
void yuv420p_rotate_normal(uint8_t *src, uint8_t *des, int w, int h)
{
    int i, j;
    int wh = w * h;

    // 旋轉 Y 分量,按照列對原始圖像取值
    int k = 0;
    for(i = 0; i < w; i++) {
        for(j = h-1; j >= 0; j--) {
            des[k] = src[w*j + i];
            k++;
        }
    }

    // 旋轉 U 分量
    for (i = 0; i < w/2; i++) {
        for (j = 1; j <= h/2; j++) {
            des[k] = src[wh + ((h/2 - j) * (w/2) + i)];
            k++;
        }
    }

    // 旋轉 V 分量
    for (i = 0; i < w/2; i++) {
        for (j = 1; j <= h/2; j++) {
            des[k] = src[wh+wh/4 + ((h/2 - j) * (w/2) + i)];
            k++;
        }
    }
}

2.顯示

圖像 ghost_yuv420p_1280x720.yuv 旋轉前、後的顯示效果如下圖所示,原始圖像是從 ghost_yuv420p_3264x2448.yuv 左下角截取的矩形區域,相關算法參見示例 No.1_YUV420pScissor,它可以從一幅較大 YUV420p 圖像的左上角或左下角截取一塊矩形區域。

原始圖像

旋轉後圖像

3.性能

使用 GPU 和 CPU 實現旋轉算法,對 ghost_yuv420p_3264x2448.yuv 圖像旋轉,分別在華爲榮耀8的 Mali T-880 平臺和 Ubuntu 的 AMD RX 460 平臺上執行 GPU 運算,ubuntu 對應的 CPU 爲 i5-4590,時間統計如下:

ARM T-880

shell@HWFRD:/data/local/tmp $ ./opencl_rotate
Test: yuv420p_rotate_opencl, 62.497917 ms
Test: yuv420p_rotate_normal, 100.525000 ms

從上面統計的時間來看,在 T-880 GPU 上運行的時間和 ARM CPU 比較,性能提升了大約 60%;

AMD RX 460

xbdong@xbdong-opencl:~/Project/github/OpenCL/No.5_2_OpenCLRotate$ ./OpenCLRotate
Test: yuv420p_rotate_opencl, 13.518628 ms
Test: yuv420p_rotate_normal, 38.027586 ms

在 AMD RX 460 上使用 GPU 和 CPU 相比,性能提升了 180%,這進一步體現了 GPU 在通用計算的優勢。

只有在需要處理大量運算的情況下才能體現 GPU 的優勢。如果對 1280x720 的圖像旋轉來處理少量的數據,這樣 OpenCL 函數調用,設備間通信等操作會把 GPU 帶來的好處給抵消。

通過上面的實驗,對 CPU 和 GPU 執行同樣的算法在性能上有個大概的認識,實際性能和頻率、算法的優化等相關。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章