Android Camera API/Camera2 API 相機預覽及濾鏡、貼紙等處理


Android Lollipop 添加了Camera2 API,並將原來的Camera API標記為廢棄了。相對原來的Camera API來說。Camera2是又一次定義的相機 API,也重構了相機 API 的架構。初看之下,可能會感覺Camera2使用起來比Camera要復雜,然而使用過后,你或許就會喜歡上使用Camera2了。不管是Camera還是Camera2,當相機遇到OpenGL就比較好玩了。

問題及思路

Camera的預覽比較常見的是使用SurfaceHolder來預覽。Camera2比較常見的是使用TextureView來預覽。

假設利用SurfaceHolder作為Camera2的預覽,利用TextureView作為Camera的預覽怎么做呢?實現起來可能也非常easy,假設預覽之前,要做美顏、磨皮或者加水印等等處理呢?實現后又怎樣保證使用Camera還是Camera2 API,使用SurfaceHolder還是TextureView預覽,或者是直接編碼不預覽都能夠迅速的更改呢?
本篇博客演示樣例將數據、處理過程、預覽界面分離開,使得不管使用Camera還是Camera2。僅僅需關注相機自身。不管終於期望顯示在哪里,都僅僅需提供一個顯示的載體。詳細點說來就是:

  1. 以一個SurfaceTexture作為接收相機預覽數據的載體,這個SurfaceTexture就是處理器的輸入。
  2. SurfaceView、TextureView或者是Surface,提供SurfaceTexture或者Surface給處理器作為輸出,來接收處理結果。
  3. 重點就是處理器了。處理器利用GLSurfaceView提供的GL環境。以相機數據作為輸入。進行處理,處理的結果渲染到視圖提供的輸出點上,而不是GLSurfaceView內部的Surface上。當然,也能夠不用GLSurfaceView,自己利用EGL來實現GL環境,問題也不大。詳細實現就參照GLSurfaceView的源代碼來了。

處理效果

既然是用OpenGL來處理,索性利用OpenGL在圖像上加兩個其它圖片,相似水印、貼紙的效果。隨便兩幅圖貼上去。也不管好看不好看了,重點是功能。

依次為先貼紙然后灰色濾鏡。先灰色濾鏡然后貼紙,僅僅有貼紙。
這里寫圖片描寫敘述這里寫圖片描寫敘述這里寫圖片描寫敘述

詳細實現

依據上述思路來。主要涉及到以下問題:

  1. 使用GLSurfaceView創建GL環境。可是要讓這個環境為離屏渲染服務,而不是直接渲染到GLSurfaceView的Surface上。在這當中還涉及到其它的一些問題,詳細的問題。在以下再說。
  2. OpenGL 的使用。相關文章
  3. 務必使相機、處理過程、顯示視圖分離。

    以便能夠自由的替換數據源、顯示視圖,僅僅須要關注處理過程。

GLSurfaceView的利用

通常我們在Android中使用OpenGL環境,僅僅須要在GLSurfaceView的Renderer接口中,調用GL函數就好了。這是由於GLSurfaceView在內部幫我們創建了GL環境,假設我們要拋開GLSurfaceView的話,僅僅須要依據GLSurfaceView創建GL環境的過程在,做同樣實現就可了,也就是EGL的使用。

也就是說,OpenGL是離不開EGL的。

EGL的使用步驟參考


首先。我們使用GLSurfaceView,是希望利用它的GL環境,而不是它的視圖,所以,我們須要改變它的渲染位置為我們期望的位置:

//這句是必要的,避免GLSurfaceView自帶的Surface影響渲染
getHolder().addCallback(null);  
//指定外部傳入的surface為渲染的window surface
setEGLWindowSurfaceFactory(new GLSurfaceView.EGLWindowSurfaceFactory() {
    @Override
    public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig
        config, Object window) {
        //這里的surface由外部傳入,能夠為Surface、SurfaceTexture或者SurfaceHolder
        return egl.eglCreateWindowSurface(display,config,surface,null);
    }

    @Override
    public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {
        egl.eglDestroySurface(display, surface);
    }
});

另外,GLSurfaceView的GL環境是受View的狀態影響的,比方View的可見與否,創建和銷毀。等等。我們須要盡可能的讓GL環境變得可控。因此。GLSurfaceView有兩個方法一頂要暴露出來:

public void attachedToWindow(){
    super.onAttachedToWindow();
}

public void detachedFromWindow(){
    super.onDetachedFromWindow();
}

這里就又有問題了,由於GLSurfaceView的onAttachedToWindow和onDetachedFromWindow是須要保證它有parent的。所以。在這里必須給GLSurfaceView一個父布局

//自己定義的GLSurfaceView
mGLView=new GLView(mContext);

//避免GLView的attachToWindow和detachFromWindow崩潰
new ViewGroup(mContext) {
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }
}.addView(mGLView);

另外。GLSurfaceView的其它設置:

setEGLContextClientVersion(2);          
setRenderer(TextureController.this);    
setRenderMode(RENDERMODE_WHEN_DIRTY);
setPreserveEGLContextOnPause(true);

這樣,我們就能夠愉快的使用GLSurfaceView來提供GL環境。給指定的Surface或者SurfaceTexture渲染圖像了。

數據的接收

我們針對的是相機的處理,相機的圖像數據和視頻的圖像數據,在Android中都能夠直接利用SurfaceTexture來接收,所以我們能夠提供一個SurfaceTexture給相機,然后將SurfaceTexture的數據拿出來,調整好方向。作為原始數據。

詳細處理和相機普通的預覽相似,不同的是,我們是不希望直接顯示到屏幕上的,並且在興許我們還會對這個圖像做其它處理。所以我們時將相機的當前幀數據渲染到一個2d的texture上,作為興許處理過程的輸入。所以在渲染時,須要綁定FrameBuffer。

@Override
public void draw() {
    boolean a=GLES20.glIsEnabled(GLES20.GL_DEPTH_TEST);
    if(a){
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
    }
    if(mSurfaceTexture!=null){
        mSurfaceTexture.updateTexImage();
        mSurfaceTexture.getTransformMatrix(mCoordOM);
        mFilter.setCoordMatrix(mCoordOM);
    }
    EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]);
    GLES20.glViewport(0,0,width,height);
    mFilter.setTextureId(mCameraTexture[0]);
    mFilter.draw();
    Log.e("wuwang","textureFilter draw");
    EasyGlUtils.unBindFrameBuffer();

    if(a){
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
    }
}

上面所使用的mFilter就是用來渲染相機數據的Filter,該Filter所起的作用就是將相機數據的方向調整正確。然后通過綁定FrameBuffer並制定接受渲染的Texture,就能夠將相機數據以一個正確的方向渲染到這個指定的Texture上了。
mFilter的頂點着色器為:

attribute vec4 vPosition;
attribute vec2 vCoord;
uniform mat4 vMatrix;
uniform mat4 vCoordMatrix;
varying vec2 textureCoordinate;

void main(){
    gl_Position = vMatrix*vPosition;
    textureCoordinate = (vCoordMatrix*vec4(vCoord,0,1)).xy;
}

其片元着色器為:

#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 textureCoordinate;
uniform samplerExternalOES vTexture;
void main() {
    gl_FragColor = texture2D( vTexture, textureCoordinate );
}

渲染相機數據到指定窗體上

在之前利用OpenGLES預覽Camera的博客中,我們是直接將相機的數據“draw”到屏幕上了。在上面的處理中,我們在繪制之前調用了EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]),這種方法是讓我們興許的渲染,渲染到fTexture[0]這個紋理上。詳細能夠參考前面的博客FBO離屏渲染


所以。通過上面的方式接收數據。然后利用自己定義的GLSurfaceView指定渲染窗體並運行渲染后,我們依然無法看到相機的預覽效果。為了將相機的數據渲染到屏幕上,我們須要將fTexture[0]的內容再渲染到制定的窗體上。這個渲染比之前的接收相機數據,渲染到fTexture[0]上更為簡單:

AFilter filter=new NoFilter(getResource());

...

void onDrawFrame(GL10 gl){
    GLES20.glViewPort(0,0,width,height)
    filter.setMatrix(matrix);
    filter.setTexture(fTexture[0]);
    filter.draw();
}

...

NoFilter的頂點着色器為:

attribute vec4 vPosition;
attribute vec2 vCoord;
uniform mat4 vMatrix;

varying vec2 textureCoordinate;

void main(){
    gl_Position = vMatrix*vPosition;
    textureCoordinate = vCoord;
}

片元着色器為:

precision mediump float;
varying vec2 textureCoordinate;
uniform sampler2D vTexture;
void main() {
    gl_FragColor = texture2D( vTexture, textureCoordinate );
}

沒錯,就是顯示超簡單的渲染一張圖片的着色器。看過前面的博客的應該見過這段着色器代碼。

添加濾鏡、貼紙效果

假設僅僅是預覽相機。我們這種做法簡直就是多次一句,直接指定渲染窗體,渲染出來就萬事大吉了。可是僅僅是這種話,就太沒意思了。而如今要做的就是相機有意思的起點了。非常多有意思的相機應用,都能夠通過這種方式去實現,比方我們常見美妝、美顏、色彩處理(濾鏡)。甚至瘦臉、大眼,或者其它的讓臉變胖的,以及一些給相機中的人帶眼鏡、帽子、發箍(這些一般須要做人臉識別特征點定位)等等等等。


通過上面的接收數據。和渲染相機數據到指定的窗體上,我們是已經能夠看到渲染的結果了的。


然后我們要在渲染到指定窗體前,添加其它的Filter,為了保證易用性,我們添加一個GroupFilter,讓其它的Filter,直接添加到GroupFilter中來完畢處理。

public class GroupFilter extends AFilter{

   private Queue<AFilter> mFilterQueue;
   private List<AFilter> mFilters;
   private int width=0, height=0;
   private int size=0;

   public GroupFilter(Resources res) {
       super(res);
       mFilters=new ArrayList<>();
       mFilterQueue=new ConcurrentLinkedQueue<>();
   }

   @Override
   protected void initBuffer() {

   }

   public void addFilter(final AFilter filter){
       //繪制到frameBuffer上和繪制到屏幕上的紋理坐標是不一樣的
       //Android屏幕相對GL世界的紋理Y軸翻轉
       MatrixUtils.flip(filter.getMatrix(),false,true);
       mFilterQueue.add(filter);
   }

   public boolean removeFilter(AFilter filter){
       boolean b=mFilters.remove(filter);
       if(b){
           size--;
       }
       return b;
   }

   public AFilter removeFilter(int index){
       AFilter f=mFilters.remove(index);
       if(f!=null){
           size--;
       }
       return f;
   }

   public void clearAll(){
       mFilterQueue.clear();
       mFilters.clear();
       size=0;
   }

   public void draw(){
       updateFilter();
       textureIndex=0;
       if(size>0){
           for (AFilter filter:mFilters){
               GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fFrame[0]);
               GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                       GLES20.GL_TEXTURE_2D, fTexture[textureIndex%2], 0);
               GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
                       GLES20.GL_RENDERBUFFER, fRender[0]);
               GLES20.glViewport(0,0,width,height);
               if(textureIndex==0){
                   filter.setTextureId(getTextureId());
               }else{
                   filter.setTextureId(fTexture[(textureIndex-1)%2]);
               }
               filter.draw();
               unBindFrame();
               textureIndex++;
           }
       }

   }

   private void updateFilter(){
       AFilter f;
       while ((f=mFilterQueue.poll())!=null){
           f.create();
           f.setSize(width,height);
           mFilters.add(f);
           size++;
       }
   }

   @Override
   public int getOutputTexture(){
       return size==0?getTextureId():fTexture[(textureIndex-1)%2];
   }

   @Override
   protected void onCreate() {

   }

   @Override
   protected void onSizeChanged(int width, int height) {
       this.width=width;
       this.height=height;
       updateFilter();
       createFrameBuffer();
   }

   //創建離屏buffer
   private int fTextureSize = 2;
   private int[] fFrame = new int[1];
   private int[] fRender = new int[1];
   private int[] fTexture = new int[fTextureSize];
   private int textureIndex=0;

   //創建FrameBuffer
   private boolean createFrameBuffer() {
       GLES20.glGenFramebuffers(1, fFrame, 0);
       GLES20.glGenRenderbuffers(1, fRender, 0);

       genTextures();
       GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fFrame[0]);
       GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, fRender[0]);
       GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width,
           height);
       GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
           GLES20.GL_TEXTURE_2D, fTexture[0], 0);
       GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
           GLES20.GL_RENDERBUFFER, fRender[0]);
// int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
// if(status==GLES20.GL_FRAMEBUFFER_COMPLETE){
// return true;
// }
       unBindFrame();
       return false;
   }

   //生成Textures
   private void genTextures() {
       GLES20.glGenTextures(fTextureSize, fTexture, 0);
       for (int i = 0; i < fTextureSize; i++) {
           GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fTexture[i]);
           GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height,
               0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
           GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
           GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
           GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
           GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
       }
   }

   //取消綁定Texture
   private void unBindFrame() {
       GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0);
       GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
   }


   private void deleteFrameBuffer() {
       GLES20.glDeleteRenderbuffers(1, fRender, 0);
       GLES20.glDeleteFramebuffers(1, fFrame, 0);
       GLES20.glDeleteTextures(1, fTexture, 0);
   }

}

將這個FilterGroup添加到已有的流程中。僅僅須要將保持的相機數據的Texture作為FilterGroup的輸入,然后將FilterGroup的輸出作為渲染到指定窗體的Filter的輸入就可以:

 @Override
 public void onDrawFrame(GL10 gl) {
     if(isParamSet.get()){
         mCameraFilter.draw();
         mGroupFilter.setTextureId(mCameraFilter.getOutputTexture());
         mGroupFilter.draw();

         GLES20.glViewport(0,0,mWindowSize.x,mWindowSize.y);
         mShowFilter.setMatrix(SM);
         mShowFilter.setTextureId(mGroupFilter.getOutputTexture());
         mShowFilter.draw();
         if(mRenderer!=null){
             mRenderer.onDrawFrame(gl);
         }
         callbackIfNeeded();
     }
 }

然后將須要添加的其它的Filter。依次添加到GroupFilter中就可以:

WaterMarkFilter filter=new WaterMarkFilter(getResources());
            filter.setWaterMark(BitmapFactory.decodeResource(getResources(),R.mipmap.logo));
filter.setPosition(300,50,300,150);
mController.addFilter(filter);
//mController.addFilter(new GrayFilter(getResources()));
mController.setFrameCallback(720, 1280, Camera2Activity.this);

其它

假設之前沒有接觸過OpenGL,這篇博客看下來可能也是雲里霧里。主要是由於篇幅有限,加上之前的博客分享的也是從零開始學習OpenGLES的內容,所以在這篇博客中沒有贅述,如有問題,可在評論區留言,或給我發郵件,共同探討。另外。分享一下我們公司的項目——AiyaEffectSDK,能夠高速實現各種好玩的美顏、貼紙效果,歡迎Star和Fork。

源代碼

所有的代碼所有在一個項目中。托管在Github上——Android OpenGLES 2.0系列博客的Demo


歡迎轉載。轉載請保留文章出處。

湖廣午王的博客[http://blog.csdn.net/junzia/article/details/61207844]



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM