zoukankan      html  css  js  c++  java
  • 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]


  • 相关阅读:
    How to install VXDIAG Honda, Toyota and JLR SDD software
    16% off MPPS V16 ECU tuning tool for EDC15 EDC16 EDC17
    Cummins INSITE locked and ask for verification code
    How to use BMW Multi Tool 7.3 to replace lost key for BMW X1
    Bleed Brake Master Cylinder with Intelligent Tester IT2
    Porsche Piwis Tester II “No VCI has been detected”,how to do?
    Creader VIII VS. Creader VII+
    How to solve GM MDI cannot complete the installation
    汽车OBD2诊断程序开发 (原文转载,思路很清晰!)
    汽车节温器单片机开发思路
  • 原文地址:https://www.cnblogs.com/yangykaifa/p/7305659.html
Copyright © 2011-2022 走看看