基于安卓平台的滤镜功能相机
Posted nullZgy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于安卓平台的滤镜功能相机相关的知识,希望对你有一定的参考价值。
1.1需求背景
爱美之心,人皆有之,我们拍照是为了留住一个美好的瞬间。android自带的相机拍照效果满足不了人们的爱美心理,而且比较单一;因此为了解决这个问题我们研发滤镜功能相机。
滤镜功能相机主要基于Android手机自带的摄像头,在实现拍照等基本功能的基础上实现实时滤镜的功能,并加以改良优化滤镜的渲染速度照过程以及舒适的拍照体验,可以轻而易举的拍出令人满意的照片。
1.2项目背景
目前针对Android系统的拍照APP主要集中在两点上,一个是拍照时的预览效果处理,另一个是对于手机相册中的照片进行处理。两者相结合起来并且做的非常好的很少。对于Android这个开源系统来说,开源是其优点也是容易造成APP性能缺失的一大因素,尤其是对于拍照APP来说,摄像头作为Android手机一大系统资源,处理不当极易导致应用性能下降、用户体验不佳。
1.3建设目标
从用户的角度出发,为了满足用户对于应用的需求,相机APP主要提供两大方面的功能。一个是拍照功能,拍照功能包括了启动相机、预览拍照画面、相机参数的个性化设置,滤镜的实时预览、拍摄以及照片的保存。另一个是相册功能,相册功能包括用户对相册图片的查看,而作为支持滤镜的相机还需要有对相册图片进行滤镜处理的功能,并对处理之后处理之前的图片进行备份保存。
1.4软硬件环境
软件环境:本系统可以运行于具有Android操作系统的手机.
硬件环境:本软件对硬件没有特别的需求,普通用户的个人手机上都可以运行本软件。
2.设计方案
2.1系统设计思路
结合相机应用的功能分析和 APP 的流程分析,可以将应用主要分成 3 个 功能模块,整个应用的功能结构如图 2-1 所示。
图2-1 模块关系图
2.3系统技术需求
拍照功能主要包括两个过程,打开相机预览画面和拍照保存。而相机APP的重点以及性能优化点主要集中在打开相机预览这一过程。在预览过程中,用户需要做拍照的准备工作,包括切换前后置摄像头、拍照参数的设置((比如光圈、亮度、对比度等等)、保存图片方面的设置(预览画面比例,保存图片大小,是否添加地理位置信息)、拍照角度以及本文要实现的滤镜的选择。为了实现相机APP本身的定制开发,需要获取Android系统的摄像头资源,并利用开放的外部接口操作摄像头,获取相应的摄像头信息,在此基础上提供良好的拍照体验。拍照功能模块的需求用例如图2-2所示。
2.4业务流程分析
3. 方案实施
3.1平台与技术路线
结合功能需求以及开发原则,将拍照模块分为两层进行分别开发,分别是相机控制层和用户交互层,主要的概要设计如图 3-6 所示。相机的控制层主要向上层提供操作相机的接口,是针对 Android 系统中 Camera 原生架构的一种封装,加入自定义的回调函数以及必要的操作函数,开发者可以通过相机控制层对相机的各个阶段的操作进行必要的控制,如获取摄像头资源、获取摄像头参数、打开相机、获得预览原始数据回调、进行拍照操作、获得拍照成功的回调和数据等等。这一层添加的意义在于开发的低耦合性 和复用性,并且能将相机处理各个步骤更直观的展现,将层次分开有助于避免开发中牵一发而动全身的危险。用户交互层主要是作为用户与应用之间的交互界面,向用户提供可视化的软件操作,例如设置界面、预览界面、相机属性(前后摄像头、闪光灯打开与否,录像照相与否)控制界面以及本文所要讨论的滤镜选择界面,用户点击界面后结合相机控制层进行相应的操作。
3.2功能模块架构
滤镜功能模块主要实现原始图像数据的格式转换、对于预览数据的实时滤镜处理、处理后数据的界面绘制以及最后带有滤镜效果的图像保存。同样是在 Android 的 Native 层实现滤镜,在 Native 层使用 C/C++结合 CPU 处理数据效率低下,而在 Native 层采用 OpenGL 结合 GLSL 的技术能够充分利用 GPU 的高并发性以及其强大的计算性能,并采 用 JNI 技术将 Java 层和 Native 层通过接口连接。可以得出本文的方案通过分析相机应 用中的滤镜处理模块内部调用的层次时序图如图 3-2 所示。
3.3 拍照模块概要设计
1)相机控制层
相机控制层主要是通过对 Android 系统中 Camera 架构进行封装来向上层的应用调用提供相应的接口,是系统底层和应用操作层面的连接层,为最终的用户操作 APP 提供接口。具体的封装通过第二章的分析是对 Camera2 版本进行封装,Camera2 的特点是异步和回调,将一个完整的拍照流程看做一次会话,会话中不同阶段有相应的回调接口。应用整体由一个主要的 CameraActivity 执行,CameraController 是整个相机控制层的核心类,相机的打开、预览、拍照,数据的获取、处理、传递都由它进行操作。CameraDeviceInfo 代表目前手机上摄像头的信息处理,针对相应的信息才能绘制此摄像头可以调整的参数范围,CameraListener 是一个重要接口,包括 onCameraAvailable、onCameraOpen、onPreviewFrame,onCapture,onCaptureFinished 接口方法,分别代表摄像头成功获得、相机打开、预览帧获得、拍照触发、拍照完成。最关键的是 onPreviewFrame和 onCaptureFinished 方法,从 onPreviewFrame 方法中可以获得预览的原始数据,在此方法中对数据进行重新排列处理转换为 GPU 能够处理的数据形式;onCaptureFinished是完成拍照的回调。
2)用户交互层
用户交互层功能是提供用户与应用之间的交互界面,主要包括应用主界面
onConfigurationChanged()方法, CameraAppUI、相机设置界面 SettingView(包括一级设置 SettingMainLayer 和二级设置SettingDetailLayer 界面)、主预览界面 MySurfaceView、滤镜实现并展示界面 MagicFilterView 以及与 Native 层进行通信传递数据的 NativeMagicFilter。这些组件是通过 CameraAppUI 进行统一控制,包括各个界面的渲染以及渲染后的回调。通过使用的是 Android 提供SurfaceView,针对一般的 View 来说,SurfaceView 提供了 一种子线程工作的属性,能使渲染操作脱离于 UI 主线程工作,防止主线程被渲染工作 所阻塞从而导致应用崩溃退出。但正如前面讨论所说,使用 CPU 进行渲染操作会消耗大量资源,提高 CPU 的占有率,只能勉强实现预览数据的渲染,但性能完全跟不上如 今的应用要求。故本方案使用GLSurfaceView来代替SurfaceView,如图3-1的MySurfaceView便是对GLSurfaceView 的继承类,方便对 GLSurfaceView 中的方法进行重写和覆盖以此达到个性化的需求。GLSurfaceView 是 OpenGL ES 在移动端专门提供的渲染 View,使得开发者能在 Android 的 Java 层进行 OpenGL ES 的渲染操作,但本文的 解决方案是结合 GLSL 脚本语言使其渲染在 GPU 中进行,提高渲染速度和性能。具体做法是利用 Native 层的 NativeMagicFiler 接口获得 Java 层传入的GLSurfaceView,获得上层的引用之后,利用 GLSL 在 GPU 中执行数据处理操作,最后通过OpenGL 将处理后的数据直接绘制在传入 Native 层的 GLSurfaceView 上,从而展示在应用中,用户便能实时预览滤镜效果。
3.4滤镜功能模块概要设计
滤镜功能模块主要实现原始图像数据的格式转换、对于预览数据的实时滤镜处理、处理后数据的界面绘制以及最后带有滤镜效果的图像保存。同样是在 Android 的 Native层实现滤镜,在 Native 层使用 C/C++结合 CPU 处理数据效率低下,而在 Native 层采用OpenGL 结合 GLSL的技术能够充分利用 GPU 的高并发性以及其强大的计算性能,并采用 JNI 技术将Java层Native 层通过接口连接。可以得出本文的方案通过分析相机应用中的滤镜处理模块内部调用的层次时序图如图 3-2所示。
用户在作出滤镜实时预览的请求之后,请求由 Java 层通过 JNI 层传递给 Native 层的滤镜渲染控制层,进行渲染的准备工作包括初始化、成员变量的传入之后返回成功初始化的状态;然后应用会在 Java 层面获得 Android 系统 Camera 提供的接口回调中的原始图像数据,此时获得的数据格式是 YUV420,通过前面的分析处理滤镜操作在 GPU中执行,数据转换的操作同样在 GPU 中执行保持执行的高效性。故数据通过 JNI 层传递给 Native 的滤镜渲染系统,渲染系统将数据和 GLSL 编写的处理脚本通过 OpenGL 传入 GPU,GPU 中进行数据格式转换以及数据滤镜处理的两步操作;处理完成由 OpenGL将图像绘制在屏幕上,绘制的操作同样是在 Native 层;最后对滤镜图像的保存是 Java层通过 JNI 提供接口通知 Native 层执行保存操作,保存成功会将结果回调给 Java 层,至此,整个滤镜的时序完成。应用的实时性通过 OpenGL 控制 GPU 进行高效计算保证,并通过测试 FPS 参数进行验证实时性。通过对整个滤镜功能模块的时序图分析,明确相机 APP 的实现需要开发多个架构层次的结合,从上层的用户界面到底层 Native 层滤镜实现的的开发层次结构如图 3-3 示。
以下对滤镜功能模块的每个层次进行概要分析:
1)JNI 层
Android 系统的架构和应用层由 Java 语言实现,但由于 Java 语言的跨平台性,每次程序的执行都是运行在虚拟机上,导致其在与本地代码的交互上能力很弱。而在实际应用中,程序应用需要有与本地进行交互实现功能,因为虚拟机能够获得的资源有限,在执行大量且复杂的运算时需要脱离虚拟机使用本地的硬件设备。而本地代码也就是Native 层是使用 C 或 C++实现,Java 和 C++之间的交互就需要通过 JNI 的接口完成。JNI层为 Java 层提供接口,Native 层作为滤镜处理实时渲染的具体实现提供功能。JNI 层的简要设计如图 3-4 所示。
从简要设计图中能知道主要功能由 NDK 与 OpenGL 实现。OpenGL 此时担当了一个控制脚本语言 GLSL 的角色。NDK 层的图像处理主要是通过 C++实现,然后利用 JNI 层与应用层交互。OpenGL 通过将 GLSL 脚本和待处理数据送入 GPU 中,通过第二章的分析,首先需要执行 YUV420 数据向 RGB 数据格式的转换,然后利用 GLSL 中两种着色器对数据中每一个具体像素做滤镜算法处理。顶点着色器主要功能是确定像素的位置,片元着色器是对每一个具体的片元(像素)进行滤镜操作。并在 Java 层实现 FilterUtils类达到与 NDK 层交互通信的目的。
3)OpenGL 绘制层
绘制层实现的功能是将使用 GLSL 在 GPU 中处理完成的数据绘制到屏幕上,在开发中是绘制到最初传入 Native 层的 GLSurfaceView 上,绘制层的简要设计如图 3-6所示。
滤镜效果的绘制以一个绘制基类 BaseDrawer 为基础,通过载入不同的滤镜效果的GLSL 脚本和已经由 GPU 处理好的图像数据来绘制不同的滤镜效果。基类内部的主要方法如表3-1, 3-2 所示,内部控制具体的绘制相关操作。
NativeMagicFilter 提供的接口
BaseDrawer 绘制基类中主要方法
MyFrameBuffer 是对 FrameBuffer 类的继承,重写父类中的方法达到覆盖的作用。在 OpenGL 的绘制流程中,待处理的数据例如几何数据或者纹理经过 GPU 的处理运算,最后以二维像素的形式被绘制到屏幕上,而最终 OpenGL 需要绘制的数据就在FrameBuffer[24][36]中。帧缓存相当于一个待绘制图像的接收站,不断的有待绘制图像进入并被 OpenGL 绘制,但需要注意的是 FrameBuffer 本身是没有存储机制的,它只是对待渲染图像进行关联。本文所要实现的多种滤镜同屏展示使用的是帧缓存的离线缓存,它指的是数据在与 FrameBuffer 进行关联之前已经经过了渲染处理,然后通过FrameBuffer 执行进一步的处理并绘制的操作。
3.5 滤镜绘制的设计与实现
图像的绘制利用 OpenGL 中的 Shader 脚本语言实现,即 GLSL。绘制的主要过程分为着色器的编译和运行。GLSL 脚本语言需要事先编写放在 Android 应用的 raw 文件夹下,待程序完成编译脚本文件也随之编译成功。着色器的编译过程主要由 JNI 层编写的NativeMagicFilter 类执行完成,其中包括着色器对象的实例化、脚本语言的编译、与着色器对象的绑定、链接、并向外提供绘制的方法接口。主要的过程如图 3-7 所示。
着色器代码以字符串的形式载入,在编码过程中,可以将着色器代码以 glsl 为后缀的文件形式放入 Android 系统目录的 raw 文件夹下,在该文件夹的文件在编译后获取各自的 id,通过 openRawResource 方法直接读取并返回 InputStream,最后 BufferedReader以 StringBuilder 的形式读取着色代码。读取着色器代码后,依次进行编译、创建、链接工作。GLSL 脚本语言是通过字符串的形式被编译,将已经编写好的放在 Android 文件的 raw 文件夹下,编译之后 Android 系统会给文件分配 id,此 id 能为多种滤镜效果做识别,为之后的九宫格多种滤镜效果做准备。在获得id 之后需要将脚本语言与新建的两种着色器进行绑定,使用方法函数利用不同的 id 绑定九个着色器实例并将读取流对象 InputStream,使用 Android 中的 BufferReader 通过StringBuffer 的形式读取脚本代码。相比较于 StringBuilder,StringBuffer具有线程安全的特性。本文中需要利用 GPU 的多线程高计算的特性实现多种滤镜同时实时预览的功能对线程同步要求高故选择 StringBuffer 来载入脚本代码。在完成脚本代码的载入之后,接下来完成脚本的编译、创建绘制程序、流程以及具体绘制方法,其中使用的主要方法 如表 3-3 所示
着色器编译主要方法
完成了绘制前的着色器编译准备工作接下来需要具体执行绘制工作。绘制工作的具体工作,包括绘制位置,绘制前的预处理,后处理等等。实时预览的数据处理也就是滤镜算法的实现是将其写入 shader 脚本文件随着绘制过程一并执行,利用 GPU强大的高 速计算能力。而针对本文中需要实现的九宫格多种滤镜同时预览的效果需要在绘制具体效果前将九种效果的每一种具体绘制位置进行确定,以及滤镜之间的顺序排列需要动态执行,故将其静态写入 shader 文件是不合理的。综上,绘制的整体工作是常规的 OpenGL绘制操作与 GLSL 脚本文件的结合。OpenGL 的绘制工作流程如图 3-8 所示。
OpenGL 绘制流程
滤镜效果的实现是本文相机中的重要功能,具体的过程是在图像处理层和绘制层完成,主要是通过 OpenGL 中的 GLSL 实现图像的滤镜处理。为了实现目标的九宫格多种滤镜同时实时预览的效果,选取了八种差异性较大的效果以及无效果的原图组成九宫格。由于 GLSL 处理图像是交给 GPU 执行命令,而 GPU 处理图像的格式只能是 RGB格式,故在进行滤镜算法之前需要将相机底层获得的 YUV 数据转换为 RGB 数据。
3.6 YUV 格式转换为 RGB 格式的设计与实现
YUV 是 Android 系统中默认的视频流格式,从文献中能够知道它的原理是将图片像素信息分成两个通道信息,分别是 Y 通道(亮度信息)和 UV 通道(灰度信息)。即使数据格式不同,针对同一张图片格式之间的转换[33]并不会改变图片本身,假设一幅像素为 8 * 4 的图片格式为 YUV420,它的存储格式如表 3-4 所示
像素 8*4 格式为 YUV420 图片存储结构
从表 3-4 可以看出:一副 8 * 4 个像素的图片前 32byte 是 Y 通道,也就是亮度通道,余下的 8*4/2byte 是 UV 交错排列的,同时可以看出每 4 个像素取一次 U 和 V。这样排列视为了有效节省视屏流的大小。
对比 8*4 RGB 图像占用的内存空间,一共 32 个像素,因为每个通道需要 3 通道来
表示,所以总共需要 32*3byte,比 YUV420 的占用空间多了一倍。
通过存储结构得到的 YUV 向 RGB 转换的公式
R = Y + 1.4075 * (V - 128)
G = Y - 0.3455 * (U - 128) - 0.7169 * (V - 128)
B = Y + 1.779 * (U - 128)
4. 结果与结论
以上是关于基于安卓平台的滤镜功能相机的主要内容,如果未能解决你的问题,请参考以下文章