前段时间合伙人想做一个早教类的 AR 项目,并且扔给了我一个小册子:
大概的功能是:
iPad 扫瞄识别右侧的积木
屏幕上出现主角按照顺序执行操作
最后显示运行结果,闯关是否成功
折腾了一段时间,基本做完了上述功能,可以在这里看到演示的效果。
整个项目从第一次提交代码,到最后出演示效果,花了两天的时间,看了下 git commit 累计花了 25 小时(主要是炒股浪费了不少时间,也浪费了不少钱,此处略过不谈)。
看上去最终的开发时间不多,不过前面还是做了不少功课,在此简单的记录一下。
文章中不会涉及任何工具的具体使用过程,所有的基础操作在官方文档里都有详细的讲解。
正式开发之前,先做了一些准备工作,主要是搜索 AR 相关的功能,看看有哪些工具可供使用。一番调研对比和测试之后整理了以下待选方案:
OpenCV :计算机视觉库,主要进行右侧积木的识别。
Vuforia :可以方便的在软件中实现 AR 功能,主要用于关卡识别和主角模型展示。
Python :主要是围绕 OpenCV 的一系列科学运算工具,用于快速开发图像识别功能的原型。
Unity3D :一款 3D 游戏引擎,可以很方便的进行 3D 场景搭建。
OpenGL :如果不用 Unity3D 就需要在 iOS 项目里基于 Vuforia 手写 OpenGL 实现 AR 功能。
在正式开发之前,先使用 OpenCV for Python 开发图像识别原型,看看这个项目好不好搞。
在 Jupyter Notebook 里开发图像识别项目真是一种非常流畅的体验:
加载图像之后,只需要在新的 Cell 里写图像识别相关的算法就可以了,图像数据已经被加载到了内存里,不需要每次运行都执行全部脚本。然后 OpenCV 进行图像处理, numpy 进行像素运算, matplotlib 展示图像,一条龙服务,十分方便。
具体的图像识别算法不再赘述,上一篇《 使用 OpenCV 识别 QRCode 》里基本都已包含。
搞定了积木识别之后,接下来就是做 AR 功能,主要有两个方案可供选择: iOS App 或者 Unity3D 。
先用 iOS App 试一下效果。
Vuforia 官方的 Sample Code 里已经包含了一个可以完整运行的项目,可以下载体验一下。然后用 pod 'OpenCV' 就能装好 OpenCV for C++ ,基本的开发环境就齐全了。折腾了一段时间之后我决定放弃使用 iOS App 的方案,因为实在是太繁琐了。
首先需要一个 ARSession 对象来管理 AR 的相关事务,里面包括了视频的渲染(需要等比拉伸并裁切之后渲染在屏幕上)、资源的回收和处理(比如手机退到后台)、线程切换(绘图需要在主线程)等等;然后需要一个 ARViewController 对象来负责具体页面的显示,里面包括基础资源的加载、识别模型的激活与切换、设备相关的事件监听等等;然后需要一个 ARImageTargetGLView 来渲染 AR 场景,包括 buffer 的维护、model 的加载、shader 的渲染等等。
而最让我崩溃的,是 Swift、Objective-C、C++ 的混写,项目中大量这样的代码:
[self setFramebuffer]; glVertexAttribPointer(vertexHandle, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)buildingModel.vertices); glEnableVertexAttribArray(textureCoordHandle); if (offTargetTrackingEnabled) { glBindTexture(GL_TEXTURE_2D, augmentationTexture[3].textureID); } else { glBindTexture(GL_TEXTURE_2D, augmentationTexture[targetIndex].textureID); } glUniformMatrix4fv(mvpMatrixHandle, 1, GL_FALSE, (const GLfloat*)&modelViewProjection.data[0]); glUniform1i(texSampler2DHandle, 0); glDisableVertexAttribArray(textureCoordHandle);
最后发现写了上千行代码,几十个文件,才只是搭建好了一个基础的 AR 开发环境,还不包括自定义的 3D 效果和自己的业务代码,细思恐极,赶紧放弃。
接下来就是转投 Unity3D 的怀抱。
遇到的第一个问题是如何集成 OpenCV ,通过买买买这个 OpenCV for Unity 插件解决了,它是基于 OpenCV for Java 的,所以接口和 Python C++ 相比略有些变化,不过基本是相同的。在整合 OpenCV 和 Vuforia 的时候看到了 OpenCV for Unity 开发团队的 Voforia with OpenCV for Unity Sample 这个示例项目,再结合自带的 Samples 文件基本就没问题了。
遇到的第二个问题是 Unity3D 中如何处理项目文件的问题,在 iOS 项目里我的项目目录是这样的:
General
Macro
View
Extension
Section
Vendor
在 Unity3D 里,由于项目里不止是代码文件,还包括 fbx 之类的模型文件、 mat 等材质文件、prefab 等预设文件、unity 等场景文件,如果都放在一起十分混乱,很难检索。
参照 iOS 的项目目录,现在 Unity3D 里的项目里是这样安排的:
General:全局通用的文件
Scripts:通用的代码文件
Class:自定义的通用类
Extension:基础模块的扩展,比如 List 的方差运算等等
Static:静态工具类
Section:业务相关的文件,一个场景一个文件夹
Scene1:具体场景的文件夹,包括所有该场景下的资源
Scripts:该场景所需的代码
Prefabs:该场景内的预设对象
Resources:该场景下的其他资源
Materials:该场景下的材质
基本上单个场景的目录结构和 General 的目录结构是一致的, General 像是所有场景的『基类』。
遇到的第三个问题是 OpenCV 绘图如何处置的问题。我希望能够将 OpenCV 的一些 Debug 信息绘制在屏幕上,比如找到的 contours 、比如计算出的方差/均值、比如画面里的积木总数等等,可以很方便的了解图像识别的情况,找到出现问题的原因。本来是通过注释掉绘图代码的方式进行状态切换,后来发现实在是太麻烦了,于是在所有的 OpenCV 绘图方法外面套了一层,放在 CVUtil 里:
public class CVUtil { public class Draw { public static void Text(Mat mat, string str, double x, double y, double fontScale = 1, Scalar color = null, LogLevel level = LogLevel.Debug) { if (level >= Global.CurrentLogLevel) { Imgproc.putText (mat, str, new Point (x,y), Core.FONT_HERSHEY_PLAIN, fontScale, color); } } public static void Rectangle(Mat mat, OpenCVForUnity.Rect rect, Scalar color = null, int thickness = 1, LogLevel level = LogLevel.Debug) { if (level >= Global.CurrentLogLevel) { Imgproc.rectangle (mat, rect.tl (), rect.br (), color, thickness); } } } }
然后这样只要切换全局变量 Global.CurrentLogLevel 就能控制 Debug 内容的显示和隐藏了。
整个 Unity3D 项目里,AR 相关的渲染完全不用操心,只需要把 Vuforia 里的 ImageTarget 这个 Prefab 脱拽到场景中就能实现基础的 AR 功能。
就简单的写这么多啦,没什么干货,只是简单回顾一下自己的开发过程。
AR 开发的技术门槛并不是很高,目前现成的 SDK 很多,可以自行选择。而如何通过 AR 做出有趣的产品,这才是核心所在。
玩得开心。