天天看點

針對android&ios yuv旋轉、鏡像、格式轉換、裁剪 算法實作

http://blog.csdn.net/dangxw_/article/details/50903693

移動端錄像在yuv資料上存在如下問題:

1.無論Android還是iOS都不能直接從攝像頭取出顔色空間為i420的資料,是以在編碼前需要進行格式轉換。

2.而且由于所取圖像得分辨率必須是攝像頭所提供分辨率中得一組,是以有可能需要裁剪。

3.另外由于1)想讓無論使用者哪個方向拿手機所錄的視訊内容永遠“頭朝上”,

  2)攝像頭預設傳回圖像為橫屏圖像(寬大于長)是以需要旋轉。

4.前置攝像頭需要鏡像。

YUV 顔色空間分類:https://zh.wikipedia.org/wiki/YUV

yuv 420 又分為:

I420: YYYYYYYY UU VV   =>YUV420P

YV12: YYYYYYYY VV UU   =>YUV420P

NV12: YYYYYYYY UVUV    =>YUV420SP

NV21: YYYYYYYY VUVU    =>YUV420SP

下面給出解決這四個問題所需要得算法:

1 格式轉換:

nv21 轉成i420

[cpp]  view plain  copy

  1. //nv21 to yuvi420  
  2. void NV21ToI420(uint8_t* dstyuv,uint8_t* data, int imageWidth, int imageHeight)  
  3. {  
  4.     int Ustart =imageWidth*imageHeight;  
  5.     int i,j;  
  6.     int uWidth = imageWidth/2;  
  7.     int uHeight = imageWidth/2;  
  8.     //y  
  9.     memcpy(dstyuv,data,imageWidth*imageHeight);  
  10.     int tempindex = 0 ;  
  11.     int srcindex= 0;  
  12.     //u  
  13.     for(i= 0 ;i <uHeight;i++)  
  14.     {  
  15.         for(j = 0;j <uWidth ;j++ )  
  16.         {  
  17.             dstyuv[Ustart+tempindex+j]= data[Ustart+(srcindex<<1)+1];  
  18.             srcindex++;  
  19.         }  
  20.         tempindex+= uWidth;  
  21.     }  
  22.     //v  
  23.     for (i = 0; i < uHeight;i++)  
  24.     {  
  25.         for (j = 0; j < uWidth;j++)  
  26.         {  
  27.             dstyuv[Ustart+tempindex + j] = data[Ustart + (srcindex << 1 )];  
  28.             srcindex++;  
  29.         }  
  30.         tempindex+= uWidth;  
  31.     }  
  32. }  

其實就是改變了uv的位置。

2 裁剪:

[cpp]  view plain  copy

  1. //crop yuv data  
  2. int crop_yuv (char* data, char*dst, intwidth, intheight,  
  3.         int goalwidth, int goalheight) {  
  4.     int i, j;  
  5.     int h_div = 0, w_div = 0;  
  6.     w_div= (width - goalwidth) / 2;  
  7.     if (w_div % 2)  
  8.         w_div--;  
  9.     h_div= (height - goalheight) / 2;  
  10.     if (h_div % 2)  
  11.         h_div--;  
  12.     //u_div = (height-goalheight)/4;  
  13.     int src_y_length = width *height;  
  14.     int dst_y_length =goalwidth * goalheight;  
  15.     for (i = 0; i <goalheight; i++)  
  16.         for (j = 0; j <goalwidth; j++) {  
  17.             dst[i* goalwidth + j] = data[(i + h_div) * width + j + w_div];  
  18.         }  
  19.     int index = dst_y_length;  
  20.     int src_begin =src_y_length + h_div * width / 4;  
  21.     int src_u_length =src_y_length / 4;  
  22.     int dst_u_length =dst_y_length / 4;  
  23.     for (i = 0; i <goalheight / 2; i++)  
  24.         for (j = 0; j <goalwidth / 2; j++) {  
  25.             int p = src_begin + i *(width >> 1) + (w_div >> 1) + j;  
  26.             dst[index]= data[p];  
  27.             dst[dst_u_length+ index++] = data[p + src_u_length];  
  28.         }  
  29.     return 0;  
  30. }  

3 旋轉:

分為四個方向

旋轉:

以順時針旋轉270度為例作圖:

Y1 Y2 Y3 Y4
Y5 Y6 Y7 Y8
Y9 Y10 Y11 Y12
Y13 Y14 Y15 Y16
U1 U2 U3 U4
V1 V2 V3 V4

原圖

Y4 Y8 Y12 Y16
Y3 Y7 Y11 Y15
Y2 Y6 Y10 Y14
Y1 Y5 Y9 Y13
U2 U4 U1 U3
V2 V4 V1 V3

旋轉後

u值的第i 行j列 對應原 資料的下标為:  ustart+uw*j-i;

去除index的乘除法運算後:

//i420 順時針 270度

[cpp]  view plain  copy

  1. int rotateYUV420Degree270(uint8_t* dstyuv,uint8_t* srcdata, int imageWidth, int imageHeight) {  
  2.     int i = 0, j = 0;  
  3.     int index = 0;  
  4.     int tempindex = 0;  
  5.     int div = 0;  
  6.     for (i = 0; i <imageHeight; i++) {  
  7.         div= i +1;  
  8.         tempindex= 0;  
  9.         for (j = 0; j <imageWidth; j++) {  
  10.             tempindex+= imageWidth;  
  11.             dstyuv[index++]= srcdata[tempindex-div];  
  12.         }  
  13.     }  
  14.     int start =imageWidth*imageHeight;  
  15.     int udiv = imageWidth *imageHeight / 4;  
  16.     int uWidth = imageWidth /2;  
  17.     int uHeight = imageHeight /2;  
  18.     index= start;  
  19.     for (i = 0; i < uHeight;i++) {  
  20.         div= i +1;  
  21.         tempindex= start;  
  22.         for (j = 0; j < uWidth;j++) {  
  23.             tempindex += uWidth;  
  24.             dstyuv[index]= srcdata[tempindex-div];  
  25.             dstyuv[index+udiv]= srcdata[tempindex-div+udiv];  
  26.             index++;  
  27.         }  
  28.     }  
  29.     return 0;  
  30. }  

//i420 順時針旋轉 180;

[cpp]  view plain  copy

  1. int rotateYUV420Degree180(uint8_t* dstyuv,uint8_t* srcdata, int imageWidth, int imageHeight)  
  2. {  
  3.     int i = 0, j = 0;  
  4.     int index = 0;  
  5.     int tempindex = 0;  
  6.     int ustart = imageWidth *imageHeight;  
  7.     tempindex= ustart;  
  8.     for (i = 0; i <imageHeight; i++) {  
  9.         tempindex-= imageWidth;  
  10.         for (j = 0; j <imageWidth; j++) {  
  11.             dstyuv[index++] = srcdata[tempindex + j];  
  12.         }  
  13.     }  
  14.     int udiv = imageWidth *imageHeight / 4;  
  15.     int uWidth = imageWidth /2;  
  16.     int uHeight = imageHeight /2;  
  17.     index= ustart;  
  18.     tempindex= ustart+udiv;  
  19.     for (i = 0; i < uHeight;i++) {  
  20.         tempindex-= uWidth;  
  21.         for (j = 0; j < uWidth;j++) {  
  22.             dstyuv[index]= srcdata[tempindex + j];  
  23.             dstyuv[index+ udiv] = srcdata[tempindex + j + udiv];  
  24.             index++;  
  25.         }  
  26.     }  
  27.     return 0;  
  28. }  

順時針 90度:

[cpp]  view plain  copy

  1. //i420順時針旋轉90 ;  
  2. int rotateYUV420Degree90(uint8_t* dstyuv,uint8_t* srcdata, int imageWidth, int imageHeight) {  
  3.     int i = 0, j = 0;  
  4.     int index = 0;  
  5.     int tempindex = 0;  
  6.     int div = 0;  
  7.     int ustart = imageWidth *imageHeight;  
  8.     for (i = 0; i <imageHeight; i++) {  
  9.         div= i;  
  10.         tempindex= ustart;  
  11.         for (j = 0; j <imageWidth; j++) {  
  12.             tempindex-= imageWidth;  
  13.             dstyuv[index++]= srcdata[tempindex + div];  
  14.         }  
  15.     }  
  16.     int udiv = imageWidth *imageHeight / 4;  
  17.     int uWidth = imageWidth /2;  
  18.     int uHeight = imageHeight /2;  
  19.     index= ustart;  
  20.     for (i = 0; i < uHeight;i++) {  
  21.         div= i ;  
  22.         tempindex= ustart+udiv;  
  23.         for (j = 0; j < uWidth;j++) {  
  24.             tempindex-= uWidth;  
  25.             dstyuv[index]= srcdata[tempindex + div];  
  26.             dstyuv[index+ udiv] = srcdata[tempindex + div + udiv];  
  27.             index++;  
  28.         }  
  29.     }  
  30.     return 0;  
  31. }  

如果從攝像頭取出資料,這樣一步步的曆遍,在低配手機上是滿足不了需求的。其實這三個步驟中有很多中間步驟是可以省去的,比如:将a放到b 位置,再将b位置上的資料取出放到c位置,那麼可以直接将a放到c位置。

是以需要優化以上三類問題所用的算法将其整合。結果如下:

[cpp]  view plain  copy

  1. void detailPic0(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {  
  2.     int deleteW = (nw - w) / 2;  
  3.     int deleteH = (nh - h) / 2;  
  4.     //處理y 旋轉加裁剪  
  5.     int i, j;  
  6.     int index = 0;  
  7.     for (j = deleteH; j < nh- deleteH; j++) {  
  8.         for (i = deleteW; i < nw- deleteW; i++)  
  9.             yuv_temp[index++]= d[j * nw + i];  
  10.     }  
  11.     //處理u  
  12.     index= w * h;  
  13.     for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)  
  14.         for (j = deleteW + 1; j< nw - deleteW; j += 2)  
  15.             yuv_temp[index++]= d[i * nw + j];  
  16.     //處理v 旋轉裁剪加格式轉換  
  17.     for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)  
  18.         for (j = deleteW; j < nw- deleteW; j += 2)  
  19.             yuv_temp[index++]= d[i * nw + j];  
  20. }  

[cpp]  view plain  copy

  1. //針對橫屏前攝像頭 nv21 to 420sp  裁剪,旋轉  
  2. void detailPic180(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {  
  3.     int deleteW = (nw - w) / 2;  
  4.     int deleteH = (nh - h) / 2;  
  5.     //處理y 旋轉加裁剪  
  6.     int i, j;  
  7.     int index = w * h;  
  8.     for (j = deleteH; j < nh- deleteH; j++) {  
  9.         for (i = deleteW; i < nw- deleteW; i++)  
  10.             yuv_temp[--index]= d[j * nw + i];  
  11.     }  
  12.     //處理u  
  13.     index= w * h * 5 / 4;  
  14.     for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)  
  15.         for (j = deleteW + 1; j< nw - deleteW; j += 2)  
  16.             yuv_temp[--index]= d[i * nw + j];  
  17.     //處理v 旋轉裁剪加格式轉換  
  18.     index= w * h * 3 / 2;  
  19.     for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)  
  20.         for (j = deleteW; j < nw- deleteW; j += 2)  
  21.             yuv_temp[--index]= d[i * nw + j];  
  22. }  

[cpp]  view plain  copy

  1. void detailPic90(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {  
  2.     int deleteW = (nw - h) / 2;  
  3.     int deleteH = (nh - w) / 2;  
  4.     int i, j;  
  5.      }*/  
  6.     for (i = 0; i < h; i++){  
  7.         for (j = 0; j < w; j++){  
  8.             yuv_temp[(h- i) * w - 1 - j] = d[nw * (deleteH + j) + nw - deleteW  
  9.                     -i];  
  10.         }  
  11.     }  
  12.     int index = w * h;  
  13.     for (i = deleteW + 1; i< nw - deleteW; i += 2)  
  14.         for (j = nh / 2 * 3 -deleteH / 2; j > nh + deleteH / 2; j--)  
  15.             yuv_temp[index++]= d[(j - 1) * nw + i];  
  16.     for (i = deleteW; i < nw- deleteW; i += 2)  
  17.         for (j = nh / 2 * 3 -deleteH / 2; j > nh + deleteH / 2; j--)  
  18.             yuv_temp[index++]= d[(j - 1) * nw + i];  
  19. }  

[cpp]  view plain  copy

  1. void detailPic270(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {  
  2.     int deleteW = (nw - h) / 2;  
  3.     int deleteH = (nh - w) / 2;  
  4.     int i, j;  
  5.     //處理y 旋轉加裁剪  
  6.     for (i = 0; i < h; i++){  
  7.         for (j = 0; j < w; j++){  
  8.             yuv_temp[i* w + j] = d[nw * (deleteH + j) + nw - deleteW - i];  
  9.         }  
  10.     }  
  11.     //處理u 旋轉裁剪加格式轉換  
  12.     int index = w * h;  
  13.     for (i = nw - deleteW - 1;i > deleteW; i -= 2)  
  14.         for (j = nh + deleteH / 2;j < nh / 2 * 3 - deleteH / 2; j++)  
  15.             yuv_temp[index++]= d[(j) * nw + i];  
  16.     //處理v 旋轉裁剪加格式轉換  
  17.     for (i = nw - deleteW - 2;i >= deleteW; i -= 2)  
  18.         for (j = nh + deleteH / 2;j < nh / 2 * 3 - deleteH / 2; j++)  
  19.             yuv_temp[index++]= d[(j) * nw + i];  
  20. }  

注:沒有優化,消除index的乘法後效果肯定會更好。

4 鏡像:

[cpp]  view plain  copy

  1. //mirro 原址的  
  2. void Mirror(uint8_t* yuv_temp, int nw, int nh, int w,  
  3.         int h) {  
  4.     int deleteW = (nw - h) / 2;  
  5.     int deleteH = (nh - w) / 2;  
  6.     int i, j;  
  7.     int a, b;  
  8.     uint8_ttemp;  
  9.     //mirror y  
  10.     for (i = 0; i < h; i++){  
  11.         a= i * w;  
  12.         b= (i + 1) * w - 1;  
  13.         while (a < b) {  
  14.             temp= yuv_temp[a];  
  15.             yuv_temp[a]= yuv_temp[b];  
  16.             yuv_temp[b]= temp;  
  17.             a++;  
  18.             b--;  
  19.         }  
  20.     }  
  21.     //mirror u  
  22.     int uindex = w * h;  
  23.     for (i = 0; i < h / 2;i++) {  
  24.         a = i * w / 2;  
  25.         b= (i + 1) * w / 2 - 1;  
  26.         while (a < b) {  
  27.             temp= yuv_temp[a + uindex];  
  28.             yuv_temp[a+ uindex] = yuv_temp[b + uindex];  
  29.             yuv_temp[b+ uindex] = temp;  
  30.             a++;  
  31.             b--;  
  32.         }  
  33.     }  
  34.     //mirror v  
  35.     uindex= w * h / 4 * 5;  
  36.     for (i = 0; i < h / 2;i++) {  
  37.         a= i * w / 2;  
  38.         b= (i + 1) * w / 2 - 1;  
  39.         while (a < b) {  
  40.             temp= yuv_temp[a + uindex];  
  41.             yuv_temp[a+ uindex] = yuv_temp[b + uindex];  
  42.             yuv_temp[b+ uindex] = temp;  
  43.             a++;  
  44.             b--;  
  45.         }  
  46.     }  
  47. }  

由于當初忽略了鏡像,是以并沒有把鏡像也和其他三個算法和并到一起。不過測試還是通過的。

如果內建ffmpeg或者OpenCV,可以使用ffmpeg的sws,filter 或者opencv的cvtcolor都可以輕松實作部分功能,不過跟蹤過ffmpeg sws的源碼,發現效率較低,代碼實作較差。如果追求處理效率,并且以上優化的算法仍不能滿足,建議使用libyuv,其中試用了很多彙編指令加速,效率驚人。