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
- //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 裁剪:
[cpp] view plain copy
- //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度
[cpp] view plain copy
- 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;
[cpp] view plain copy
- 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度:
[cpp] view plain copy
- //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位置。
是以需要優化以上三類問題所用的算法将其整合。結果如下:
[cpp] view plain copy
- 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];
- }
[cpp] view plain copy
- //針對橫屏前攝像頭 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];
- }
[cpp] view plain copy
- 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];
- }
[cpp] view plain copy
- 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 鏡像:
[cpp] view plain copy
- //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,其中試用了很多彙編指令加速,效率驚人。