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

时间:2021-06-07 20:31:33

移动端录像在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,其中试用了很多汇编指令加速,效率惊人。