天天看点

针对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,其中试用了很多汇编指令加速,效率惊人。