针对android&ios yuv旋转、镜像、格式转换、裁剪 算法实现

移动端录像在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

 

//nv21 to yuvi420
void NV21ToI420(uint8_t* dstyuv,uint8_t* data, int imageWidth, int imageHeight)
{
    int Ustart =imageWidth*imageHeight;
    int i,j;
    int uWidth = imageWidth/2;
    int uHeight = imageWidth/2;
    //y
    memcpy(dstyuv,data,imageWidth*imageHeight);
    int tempindex = 0 ;
    int srcindex= 0;
    //u
    for(i= 0 ;i <uHeight;i++)
    {
 
 
        for(j = 0;j <uWidth ;j++ )
        {
            dstyuv[Ustart+tempindex+j]= data[Ustart+(srcindex<<1)+1];
            srcindex++;
        }
        tempindex+= uWidth;
    }
 
 
    //v
    for (i = 0; i < uHeight;i++)
    {
 
        for (j = 0; j < uWidth;j++)
        {
            dstyuv[Ustart+tempindex + j] = data[Ustart + (srcindex << 1 )];
            srcindex++;
        }
        tempindex+= uWidth;
    }
}


 

其实就是改变了uv的位置。

 

2 裁剪:

//crop yuv data
int crop_yuv (char* data, char*dst, intwidth, intheight,
        int goalwidth, int goalheight) {
 
    int i, j;
    int h_div = 0, w_div = 0;
    w_div= (width - goalwidth) / 2;
    if (w_div % 2)
        w_div--;
    h_div= (height - goalheight) / 2;
    if (h_div % 2)
        h_div--;
    //u_div = (height-goalheight)/4;
    int src_y_length = width *height;
    int dst_y_length =goalwidth * goalheight;
    for (i = 0; i <goalheight; i++)
        for (j = 0; j <goalwidth; j++) {
            dst[i* goalwidth + j] = data[(i + h_div) * width + j + w_div];
        }
    int index = dst_y_length;
    int src_begin =src_y_length + h_div * width / 4;
    int src_u_length =src_y_length / 4;
    int dst_u_length =dst_y_length / 4;
    for (i = 0; i <goalheight / 2; i++)
        for (j = 0; j <goalwidth / 2; j++) {
            int p = src_begin + i *(width >> 1) + (w_div >> 1) + j;
            dst[index]= data[p];
            dst[dst_u_length+ index++] = data[p + src_u_length];
        }
 
    return 0;
}


 

 

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

int rotateYUV420Degree270(uint8_t* dstyuv,uint8_t* srcdata, int imageWidth, int imageHeight) {
 
    int i = 0, j = 0;
 
    int index = 0;
    int tempindex = 0;
    int div = 0;
    for (i = 0; i <imageHeight; i++) {
        div= i +1;
        tempindex= 0;
        for (j = 0; j <imageWidth; j++) {
 
            tempindex+= imageWidth;
            dstyuv[index++]= srcdata[tempindex-div];
        }
    }
 
    int start =imageWidth*imageHeight;
    int udiv = imageWidth *imageHeight / 4;
 
    int uWidth = imageWidth /2;
    int uHeight = imageHeight /2;
    index= start;
    for (i = 0; i < uHeight;i++) {
        div= i +1;
        tempindex= start;
        for (j = 0; j < uWidth;j++) {
            tempindex += uWidth;
            dstyuv[index]= srcdata[tempindex-div];
            dstyuv[index+udiv]= srcdata[tempindex-div+udiv];
            index++;
        }
    }
 
    return 0;
 
}


//i420 顺时针旋转 180

int rotateYUV420Degree180(uint8_t* dstyuv,uint8_t* srcdata, int imageWidth, int imageHeight)
{
 
    int i = 0, j = 0;
 
    int index = 0;
    int tempindex = 0;
 
    int ustart = imageWidth *imageHeight;
    tempindex= ustart;
    for (i = 0; i <imageHeight; i++) {
 
        tempindex-= imageWidth;
        for (j = 0; j <imageWidth; j++) {
 
            dstyuv[index++] = srcdata[tempindex + j];
        }
    }
 
    int udiv = imageWidth *imageHeight / 4;
 
    int uWidth = imageWidth /2;
    int uHeight = imageHeight /2;
    index= ustart;
    tempindex= ustart+udiv;
    for (i = 0; i < uHeight;i++) {
 
        tempindex-= uWidth;
        for (j = 0; j < uWidth;j++) {
 
            dstyuv[index]= srcdata[tempindex + j];
            dstyuv[index+ udiv] = srcdata[tempindex + j + udiv];
            index++;
        }
    }
    return 0;
}


 

 

顺时针 90度:

 

//i420顺时针旋转90 ;
int rotateYUV420Degree90(uint8_t* dstyuv,uint8_t* srcdata, int imageWidth, int imageHeight) {
 
    int i = 0, j = 0;
 
    int index = 0;
    int tempindex = 0;
    int div = 0;
    int ustart = imageWidth *imageHeight;
    for (i = 0; i <imageHeight; i++) {
        div= i;
        tempindex= ustart;
        for (j = 0; j <imageWidth; j++) {
 
            tempindex-= imageWidth;
 
            dstyuv[index++]= srcdata[tempindex + div];
        }
    }
 
 
    int udiv = imageWidth *imageHeight / 4;
 
    int uWidth = imageWidth /2;
    int uHeight = imageHeight /2;
    index= ustart;
    for (i = 0; i < uHeight;i++) {
        div= i ;
        tempindex= ustart+udiv;
        for (j = 0; j < uWidth;j++) {
            tempindex-= uWidth;
            dstyuv[index]= srcdata[tempindex + div];
            dstyuv[index+ udiv] = srcdata[tempindex + div + udiv];
            index++;
        }
    }
    return 0;
 
}

 

如果从摄像头取出数据,这样一步步的历遍,在低配手机上是满足不了需求的。其实这三个步骤中有很多中间步骤是可以省去的,比如:将a放到b 位置,再将b位置上的数据取出放到c位置,那么可以直接将a放到c位置。

所以需要优化以上三类问题所用的算法将其整合。结果如下:

 

void detailPic0(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {
    int deleteW = (nw - w) / 2;
    int deleteH = (nh - h) / 2;
    //处理y 旋转加裁剪
    int i, j;
    int index = 0;
    for (j = deleteH; j < nh- deleteH; j++) {
        for (i = deleteW; i < nw- deleteW; i++)
            yuv_temp[index++]= d[j * nw + i];
    }
 
    //处理u
    index= w * h;
 
    for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)
        for (j = deleteW + 1; j< nw - deleteW; j += 2)
            yuv_temp[index++]= d[i * nw + j];
 
    //处理v 旋转裁剪加格式转换
    for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)
        for (j = deleteW; j < nw- deleteW; j += 2)
            yuv_temp[index++]= d[i * nw + j];
 
}

//针对横屏前摄像头 nv21 to 420sp  裁剪,旋转
void detailPic180(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {
    int deleteW = (nw - w) / 2;
    int deleteH = (nh - h) / 2;
    //处理y 旋转加裁剪
    int i, j;
    int index = w * h;
    for (j = deleteH; j < nh- deleteH; j++) {
        for (i = deleteW; i < nw- deleteW; i++)
            yuv_temp[--index]= d[j * nw + i];
    }
 
    //处理u
    index= w * h * 5 / 4;
 
    for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)
        for (j = deleteW + 1; j< nw - deleteW; j += 2)
            yuv_temp[--index]= d[i * nw + j];
 
    //处理v 旋转裁剪加格式转换
    index= w * h * 3 / 2;
    for (i = nh + deleteH / 2;i < nh / 2 * 3 - deleteH / 2; i++)
        for (j = deleteW; j < nw- deleteW; j += 2)
            yuv_temp[--index]= d[i * nw + j];
 
}



void detailPic90(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {
 
 
 
 
 
    int deleteW = (nw - h) / 2;
    int deleteH = (nh - w) / 2;
 
    int i, j;
     }*/
    for (i = 0; i < h; i++){
        for (j = 0; j < w; j++){
            yuv_temp[(h- i) * w - 1 - j] = d[nw * (deleteH + j) + nw - deleteW
                    -i];
        }
    }
 
    int index = w * h;
    for (i = deleteW + 1; i< nw - deleteW; i += 2)
        for (j = nh / 2 * 3 -deleteH / 2; j > nh + deleteH / 2; j--)
            yuv_temp[index++]= d[(j - 1) * nw + i];
 
    for (i = deleteW; i < nw- deleteW; i += 2)
        for (j = nh / 2 * 3 -deleteH / 2; j > nh + deleteH / 2; j--)
            yuv_temp[index++]= d[(j - 1) * nw + i];
 
}
 


void detailPic270(uint8_t* d, uint8_t* yuv_temp, int nw, int nh, int w, int h) {
    int deleteW = (nw - h) / 2;
    int deleteH = (nh - w) / 2;
    int i, j;
    //处理y 旋转加裁剪
    for (i = 0; i < h; i++){
        for (j = 0; j < w; j++){
            yuv_temp[i* w + j] = d[nw * (deleteH + j) + nw - deleteW - i];
        }
    }
 
    //处理u 旋转裁剪加格式转换
    int index = w * h;
    for (i = nw - deleteW - 1;i > deleteW; i -= 2)
        for (j = nh + deleteH / 2;j < nh / 2 * 3 - deleteH / 2; j++)
            yuv_temp[index++]= d[(j) * nw + i];
 
    //处理v 旋转裁剪加格式转换
 
    for (i = nw - deleteW - 2;i >= deleteW; i -= 2)
        for (j = nh + deleteH / 2;j < nh / 2 * 3 - deleteH / 2; j++)
            yuv_temp[index++]= d[(j) * nw + i];
 
}
 


 

注:没有优化,消除index的乘法后效果肯定会更好。

 

4 镜像:

 

//mirro 原址的
void Mirror(uint8_t* yuv_temp, int nw, int nh, int w,
        int h) {
    int deleteW = (nw - h) / 2;
    int deleteH = (nh - w) / 2;
    int i, j;
   
    int a, b;
    uint8_ttemp;
    //mirror y
    for (i = 0; i < h; i++){
        a= i * w;
        b= (i + 1) * w - 1;
        while (a < b) {
            temp= yuv_temp[a];
            yuv_temp[a]= yuv_temp[b];
            yuv_temp[b]= temp;
            a++;
            b--;
        }
    }
    //mirror u
    int uindex = w * h;
    for (i = 0; i < h / 2;i++) {
        a = i * w / 2;
        b= (i + 1) * w / 2 - 1;
        while (a < b) {
            temp= yuv_temp[a + uindex];
            yuv_temp[a+ uindex] = yuv_temp[b + uindex];
            yuv_temp[b+ uindex] = temp;
            a++;
            b--;
        }
    }
    //mirror v
    uindex= w * h / 4 * 5;
    for (i = 0; i < h / 2;i++) {
        a= i * w / 2;
        b= (i + 1) * w / 2 - 1;
        while (a < b) {
            temp= yuv_temp[a + uindex];
            yuv_temp[a+ uindex] = yuv_temp[b + uindex];
            yuv_temp[b+ uindex] = temp;
            a++;
            b--;
        }
    }
 
}


 

 

由于当初忽略了镜像,所以并没有把镜像也和其他三个算法和并到一起。不过测试还是通过的。

 

如果集成ffmpeg或者opencv,可以使用ffmpeg的sws,filter 或者opencv的cvtcolor都可以轻松实现部分功能,不过跟踪过ffmpeg sws的源码,发现效率较低,代码实现较差。如果追求处理效率,并且以上优化的算法仍不能满足,建议使用libyuv,其中试用了很多汇编指令加速,效率惊人。

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
要将YUV422格式转换为RGB格式,您可以使用FFmpeg库中的函数进行转换。以下是一个使用FFmpeg进行YUV422到RGB转换的示例代码: ```c #include <stdio.h> #include <libavutil/imgutils.h> #include <libswscale/swscale.h> int main() { int width = 640; // 输入图像的宽度 int height = 480; // 输入图像的高度 // 分配输入图像的内存 uint8_t* yuv422Buffer = (uint8_t*)malloc(width * height * 2); // 在这里填充YUV422数据yuv422Buffer // 分配输出图像的内存 uint8_t* rgbBuffer = (uint8_t*)malloc(width * height * 3); // 创建输入图像的AVFrame AVFrame* yuv422Frame = av_frame_alloc(); yuv422Frame->format = AV_PIX_FMT_YUV422P; yuv422Frame->width = width; yuv422Frame->height = height; av_image_fill_arrays(yuv422Frame->data, yuv422Frame->linesize, yuv422Buffer, AV_PIX_FMT_YUV422P, width, height, 1); // 创建输出图像的AVFrame AVFrame* rgbFrame = av_frame_alloc(); rgbFrame->format = AV_PIX_FMT_RGB24; rgbFrame->width = width; rgbFrame->height = height; av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, rgbBuffer, AV_PIX_FMT_RGB24, width, height, 1); // 创建转换上下文 struct SwsContext* swsContext = sws_getContext(width, height, AV_PIX_FMT_YUV422P, width, height, AV_PIX_FMT_RGB24, 0, NULL, NULL, NULL); // 进行转换 sws_scale(swsContext, yuv422Frame->data, yuv422Frame->linesize, 0, height, rgbFrame->data, rgbFrame->linesize); // 在这里可以使用rgbBuffer中的RGB数据 // 释放资源 free(yuv422Buffer); free(rgbBuffer); av_frame_free(&yuv422Frame); av_frame_free(&rgbFrame); sws_freeContext(swsContext); return 0; } ``` 请注意,此示例假设您已经填充了正确的YUV422数据yuv422Buffer。您需要根据实际情况修改输入图像的宽度和高度,并在转换后使用rgbBuffer中的RGB数据进行进一步处理。还要确保您已经链接了FFmpeg库并包含所需的头文件。 请参考FFmpeg文档以获得更多关于使用FFmpeg进行图像格式转换的详细信息和选项。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值