针对移动端摄像头yuv旋转、裁剪、镜像、格式转换算法的实现

存在问题

移动端录像在yuv数据上存在如下问题:

  1. 无论android还是ios都不能直接从摄像头取出颜色空间为i420的数据,所以在编码前需要进行格式转换。
  2. 而且由于所取图像得分辨率必须是摄像头所提供分辨率中得一组,所以有可能需要裁剪。
  3. 另外由于
    (1)想让无论用户哪个方向拿手机所录的视频内容永远“头朝上”
    ( 2)摄像头默认返回图像为横屏图像(宽大于长)
    所以需要旋转。
  4. 前置摄像头需要镜像。

算法实现

1.格式转换
nv21转成i420。可以通过摄像头设置将所采集数据设置为YUVNV21格式。
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的位置。
I420: YYYYYYYY UU VV
NV21: YYYYYYYY VUVU

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度为例作图。
旋转前:


旋转后:

u值的第i 行j列 对应原 数据的下标为:ustart+uw*j-i;
去除index的乘除法运算后算法:
(1) 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; }
(2)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; }
(3)顺时针 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 <imageHeight; 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; }
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--; } } }

算法优化

如果从摄像头取出数据,这样一步步的历遍,在低配手机上是满足不了需求的。其实这三个步骤中有很多中间步骤是可以省去的,比如:将a放到b 位置,再将b位置上的数据取出放到c位置,那么可以直接将a放到c位置。
所以将旋转、裁剪、格式转换三个问题所用的算法整合(未整合进去镜像)。结果如下:
**(1)处理不用旋转的图像,格式转换加裁剪
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]; }

**(2)格式转换、裁剪加旋转90度
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]; }

(3)格式转换、裁剪加旋转180度
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]; }

(4)格式转换、裁剪加旋转270度
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的乘法后效果肯定会更好。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容