概述
分別在 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),也就是灰度值。而 U
和 V
則表示色度(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 執行同樣的算法在性能上有個大概的認識,實際性能和頻率、算法的優化等相關。