1. 程式人生 > >Android OpenGLES濾鏡開發之大眼效果

Android OpenGLES濾鏡開發之大眼效果

前言

在很多美顏相機啊,抖音啊,都會有一些放大眼睛的效果,今天就來實現如何放大眼睛。

思路

1、首先使用OpenCV定位到人臉
2、根據定位到的人臉去檢測人臉關鍵點,進而獲取到人眼睛的位置。
3、根據眼睛位置,對眼睛進行放大。

實現

定位人臉

人臉的定位追蹤,在之前文章中已經寫過OpenCv實現人臉追蹤 當時是在xCode上寫的,把裡面的程式碼移植到Android中就可以了,這個是C寫的,所以需要移植到JNI中。這裡的人臉模型,我採用的是OpenCV中提供的,當然也可以自己去訓練模型。

檢測人臉關鍵點

人臉關鍵點的檢測,有很多三方的sdk,比如說face++等等,都是要收費的,face++是檢測了68個關鍵點,這68個關鍵點都不是隨意分佈的,都是有規律的。如下圖:
人臉關鍵點


但是像face++這種,都是需要收費的,我從GitHub上找了一個免費的叫SeetaFaceEngine它這個裡面有三個模組:人臉檢測模組(SeetaFace Detection)、面部特徵點定位模組(SeetaFace Alignment)以及人臉特徵提取與比對模組(SeetaFace Identification)。這裡我用到的是SeetaFace Alignment,用來檢測人臉的關鍵點。

它這裡面並不是定位了68個關鍵點,而是定位了5個關鍵點,即左眼(0)、右眼(1)、鼻子(2)、嘴巴左邊(3)、嘴巴右邊(4),這個分佈也是有規律的,並不是隨便的點。程式碼如下:將定位到的人臉,送去進行關鍵點的檢測

  if (faces.size()){
        Rect face = faces[0];
        rects.push_back(Rect2f(face.x,face.y,face.width,face.height));

        //關鍵點定位
        //儲存5個關鍵點
        //0:左眼 1:右眼 2:鼻頭 3:嘴巴左邊,4 :嘴巴右邊
        seeta::FacialLandmark points[5];
        //影象資料
        seeta::ImageData image_data(src.cols,src.rows)
; image_data.data = src.data; //指定人臉部位 去定位眼睛 seeta::FaceInfo faceInfo; seeta::Rect bbox; //是一個矩形邊框,用來微調定位到的人臉的視窗,有時候可能會定位的不準確 bbox.x = face.x; bbox.y = face.y; bbox.width = face.width; bbox.height = face.height; faceInfo.bbox = bbox; // 定位人臉關鍵點方法,第一個引數是你需要檢測的灰度圖, // 第二個引數是The face bounding box,臉部邊框 // 第三個引數是檢測到的關鍵點集合 faceAlignment->PointDetectLandmarks(image_data,faceInfo,points); for (int i = 0; i < 5; ++i) { //把點放入集合中 ,點是沒有寬和高的 rects.push_back(Rect2f(points[i].x,points[i].y,0,0)); } }
放大眼睛

這裡是根據網上論文中的一個公式實現的,http://www.gson.org/thesis/warping-thesis.pdf, 大概在45頁左右有個這樣的描述,以及公式,就是根據這個來實現的。

4.4.2. Local scaling warps
The mapping used for a local scaling warp maps each point within the area
of influence onto a point in the source image whose distance vector from
the center of the area has the same orientation as that in the destination
image but whose length is a function fs® of the length r of the vector
The function chosen for fs®
where the parameter a is controlled by the current horizontal position of
the mouse ranging from when the mouse is at the left edge of the
area of interest to  when the mouse is at the right edge of the area of
interest Note that the function reduces to the identity function fs® = r
when a =0.In Figure the function f(s)r for rmax  has been plotted for a number of values of a in the range  

這個公式是什麼意思呢?這個公式求出的值是採集的改變後的點距離眼睛中心點的位置,rmax表示的是最大放大的區域,r表示原來的點距離眼睛中心點的位置,a表示的是放大係數,所以當a=0的時候,公式的結果就是r,也就是沒有變化,不放大。
原理是什麼呢,就是在片元著色器,把眼睛周邊的需要放大的點取值成對應的眼睛內部的點的值,然後把這個值的座標賦值給gl_FragColor,這樣就完成了眼睛的放大;
片元著色器:

// r:原來的點距離眼睛中心點的位置
//rmax: 放大區域
float fs(float r,float rmax){
    //放大係數
    float a = 0.4;
    return (1.0 - pow((r/rmax -1.0),2.0) *a);
}

//根據需要採集的點 aCoord 計算新的點(可能是需要改變為眼睛內部的點,完成放大的效果)
vec2 newCoordDistance(vec2 coord,vec2 eye,float rmax){
    vec2 newCoord = coord;
    //獲得當前需要採集的點與眼睛的距離
    //distance是glsl中的內建函式
    float r = distance(coord,eye);
    //在範圍內 才放大
    if(r < rmax){
        //想要方法需要採集的點 與 眼睛中心點的距離
        float fsr = fs(r,rmax);
        // 新點-眼睛 / 老點-眼睛 = 新距離/老距離
        //(newCoord  - eye) / (coord-eye) = fsr/r;
        //(newCoord  - eye) = fsr/r * (coord-eye)
        newCoord = fsr * (coord - eye) +eye;
    }
    return newCoord;
}
void main(){
    //最大作用半徑 rmax
    //計算兩個點的距離
    float rmax = distance(left_eye,right_eye)/2.0;
    // 如果屬於 左眼 放大區域的點 得到的就是 左眼裡面的某一個點(完成放大效果)
    // 如果屬於 右眼放大區域的點 或者都不屬於 ,那麼 newCoord還是 aCoord
    vec2 newCoord= newCoordDistance(aCoord,left_eye,rmax);

    newCoord = newCoordDistance(newCoord,right_eye,rmax);
   //  採集到 RGBA 值
    gl_FragColor = texture2D(vTexture,newCoord);
}

效果圖

用最愛的明星做個效果圖,可以看出來眼睛部位是有明顯的放大的
在這裡插入圖片描述

在這裡插入圖片描述