博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android Camera API/Camera2 API 相机预览及滤镜、贴纸等处理
阅读量:7010 次
发布时间:2019-06-28

本文共 6149 字,大约阅读时间需要 20 分钟。

http://www.mamicode.com/info-detail-1723122.html

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的预览怎么做呢?实现起来可能也很简单,如果预览之前,要做美颜、磨皮或者加水印等等处理呢?实现后又如何保证使用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的。。 

首先,我们使用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一个父布局

//自定义的GLSurfaceViewmGLView=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。

@Overridepublic 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 : requireprecision mediump float; varying vec2 textureCoordinate; uniform samplerExternalOES vTexture; void main() { gl_FragColor = texture2D( vTexture, textureCoordinate ); }

渲染相机数据到指定窗口上

在之前的博客中,我们是直接将相机的数据“draw”到屏幕上了。在上面的处理中,我们在绘制之前调用了EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]),这个方法是让我们后续的渲染,渲染到fTexture[0]这个纹理上。具体可以参考前面的博客。 

所以,通过上面的方式接收数据,然后利用自定义的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
mFilterQueue; private List
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; }
你可能感兴趣的文章
豆瓣阿北:用户价值大于产品体验,通过产品做运营
查看>>
单播(unicast)、组播(multicast)、广播(broadcast)的区别
查看>>
我的友情链接
查看>>
利用clonezilla克隆、还原CentOS整个系统
查看>>
解决127.0.0.1 localhost 劫持问题
查看>>
关于硬盘的一切!
查看>>
人工智能落地之路:从概念验证到产品
查看>>
winscp连接虚拟机Linux被拒绝的问题解决方案
查看>>
教程-Delphi设置功能表
查看>>
node常用库或中间件
查看>>
关系运算图模板
查看>>
Java中的多线程,线程池
查看>>
发布系统背景和saltstack的基本操作
查看>>
软件下载站
查看>>
appium - 连接设备
查看>>
C#获取一个文件的相关信息
查看>>
linux驱动系列之文件压缩解压小节(转)
查看>>
POJ 1180 斜率优化DP(单调队列)
查看>>
Zend Studio 12 生成 WSDL
查看>>
重新学struct,边界对齐,声明……与Union的区别
查看>>