ar unity 小程序_实战 | Unity下ARKit与OpenCV的结晶

前言

Unity是由Unity Technologies研发的跨平台游戏引擎,ARKit是苹果公司在2017年发布的在ios上运行的增强现实SDK。Unity-ARKit-Plugin 是Unity3D研发的库,可以在Unity环境下编写有趣的ARKit小程序。现在的ARKit2.0功能可谓十分强大,可以追踪面部表情,识别图像,识别物体等等。然而,这一切的功能开发非常受制于苹果公司,这让人不禁想到,我们是否可以把强大的OpenCV和Unity-ARKit结合起来呢?今天我们就来教大家如何把三者结合起来。先来看看完成后是什么样子:

安装

首先是一些必要软件和库的安装,包括Unity和Unity-ARKit-Plugin。安装Unity时,主要选项有Unity主体,IOS支持和Visual Studio IDE,如下图所示:

三者结合

ARKit -> Unity

三者结合的关键是搭建沟通的桥梁。以Unity为主体,我们首先获取苹果设备中的视频帧。打开⁨Assets⁩ ▸ ⁨UnityARKitPlugin⁩ ▸ ⁨Plugins⁩ ▸ ⁨iOS⁩ ▸ ⁨UnityARKit⁩ ▸ ⁨NativeInterface下的ARKitDefines.h. 这里我们要定义一个新的类型。注意因为大多数OpenCV操作都在灰度图像上进行,所以这里的类型中只包括了灰度图像,需要彩色图像的小伙伴可以直接使用上面的UnityPixelBuffer。

接下来打开ARSessionNative.mm以做修改,mm是一类源代码文件,带有这种扩展名的文件,除了可以包含Objective-C和C代码以外还可以包含C++代码。打开并加入以下高亮部分。

细心的小伙伴会发现,这里OpenCVPixelBuffer模仿了UnityPixelBuffer的定义。第一部分生成一个对象,第二部分将设备的视频帧拷贝到对象的YPixelbytes,YPixelByte是一个指针,将会在Unity中定义。

有的同学可能会问,为什么不直接用s_UnityPixelBuffer呢?因为s_UnityPixelBuffer指向背景图像,然而我们不希望改动背景图像,所以建立了一个新的函数。那为什么不使用OpenCV里获取视频帧的函数呢?我尝试用OpenCV教程里的方法但失败了,有兴趣的同学可以亲自试一下。

OpenCV -> Unity

在回到Unity前,我们先解决OpenCV和Unity的链接。这里我给大家准备好了一个pakcage,可以直接导入Unity

(https://github.com/TerryLiu007/ARCV2.0/blob/master/ARCV2.0_opencv_starter.unitypackage)导入后你会在⁨Assets⁩ ▸ ⁨UnityOpenCVPlugin⁩ ▸ ⁨Plugins⁩ ▸ ⁨iOS⁩ ▸ ⁨NativeInterface⁩下看到NativeInterface.mm. 里面的重要部分包括:

// OpenCV interface 界面定义

@interface videoCapture : NSObject

{

int width;

int height;

}

@end

// OpenCV implementation 类定义

@implementation videoCapture

// 初始函数

- (instancetype)initWithWidth:(int)w height:(int)h {

if (self) {

width = w;

height = h;

}

return self;

}

// 每一帧的更新

- (void)updateWithWidth: (int)inputWidth height: (int)inputHeight input: (unsigned char*)inputData output: (unsigned char*)outputData {

Mat img(inputHeight, inputWidth, CV_8UC1, inputData);

// Resized to specified size

Mat gray(360, 640, img.type());

resize(img, gray, gray.size(), cv::INTER_AREA);

// Canny edge

Mat edge;

Canny(gray, edge, 100, 200);

edge = ~edge;

// Convert to Unity's texture format (RGBA)

Mat argb;

cvtColor(edge, argb, CV_GRAY2RGBA);

// Copy to buffer secured by Unity side

memcpy(outputData, argb.data, argb.total() * argb.elemSize());

}

// Declare functions to export to C# 桥接到C#的定义

extern "C" {

void* allocateVideoCapture(int width, int height);

void releaseVideoCapture(void* capture);

void updateVideoCapture(void* capture, int width, int height, unsigned char* inputImage, unsigned char* outputImage);

}

// Generate objects 对象初始化,_bridge_retained 指手动内存管理

void* allocateVideoCapture(int width, int height) {

videoCapture* capture = [[videoCapture alloc] initWithWidth:width height:height];

return (__bridge_retained void*)capture;

}

// destroy object 对象终止,_bridge_transfer 指内存交还系统(ARC)管理

void releaseVideoCapture(void* capture) {

videoCapture* cap = (__bridge_transfer videoCapture*)capture;

cap = nil;

}

// for calling every frame 每一帧的更新

void updateVideoCapture(void* capture, int width, int height, unsigned char* inputImage, unsigned char* outputImage) {

videoCapture* cap = (__bridge videoCapture*)capture;

[cap updateWithWidth:width height:height input:inputImage output:outputImage];

}

(左右滑动试试)

函数定义的框架使用Object-C,而对于OpenCV部分使用C++,对于每一帧的更新,大家可以自由发挥,这里以Canny edge为例。

Unity

接下来我们回到Unity环境中,对接上面定义的四个函数,打开Assets⁩ ▸ ⁨UnityOpenCVPlugin⁩ ▸ ⁨Plugins⁩ ▸ ⁨iOS⁩ ▸ ⁨Helper⁩下的VideoCaptureSimple.cs

// Import external library 对接四个函数

[DllImport("__Internal")]

private static extern IntPtr allocateVideoCapture(int width, int height);

[DllImport("__Internal")]

private static extern void releaseVideoCapture(IntPtr capture);

[DllImport("__Internal")]

private static extern void updateVideoCapture(IntPtr capture, int width, int height, IntPtr inputImage, IntPtr outputImage);

[DllImport("__Internal")]

private static extern void OpenCVPixelData (int enable, IntPtr YPixelBytes);

接下来就像引用Unity环境内的函数一样,可以直接引用以上函数,首先定义指针

// Pointer to device capture object 定义指向OpenCV界面的指针

private IntPtr nativeCapture;

// Video texture 设备视频帧的指针

private byte[] textureYBytes;

private GCHandle textureYHandle;

private IntPtr textureYInputPtr;

// Output render image 输出图像的指针

private Texture2D texture;

private Color32[] pixels;

private GCHandle pixelsHandle;

private IntPtr pixelsOutputPtr;

接着初始化指针,这里以输出图像为例

void Start () {

#if UNITY_IOS

UnityARSessionNativeInterface.ARFrameUpdatedEvent += UpdateCamera;

texturesInitialized = false;

// 初始化OpenCV界面指针

nativeCapture = allocateVideoCapture(inputWidth, inputHeight);

// 初始化输出图像指针

texture = new Texture2D(640, 360, TextureFormat.ARGB32, false);

pixels = texture.GetPixels32();

pixelsHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned);

pixelsOutputPtr = pixelsHandle.AddrOfPinnedObject();

#endif

//渲染目标的纹理即是输出图像

renderTarget.material.mainTexture = texture;

}

pixels在每一帧会被修改,要想在渲染目标上显示,需要apply()。在Update()函数中

void Update()

{

#if UNITY_IOS

if (!texturesInitialized)

return;

//Fetch the video texture 获取新的视频帧

SetOpenCVPixelData (true, textureYInputPtr);

//Display video 处理视频帧并在渲染目标上显示

updateVideoCapture(nativeCapture, inputWidth, inputHeight, textureYInputPtr, pixelsOutputPtr);

texture.SetPixels32(pixels);

texture.Apply();

#endif

}

最后还有一些释放指针的处理,这里就不细讲了。

Demo

程序的桥接部分已经基本完成,接下来是完成Unity项目。打开UnityARKitScene,只保留图中所示的三项,并创建新的平面,把VideoCaptureSimple和Capture材料拖进去。这里因为Unity坐标原点位于左下,OpenCV坐标原点位于左上,为了让图像旋转准确,对于X轴对称,X的Scale为负。

在导出项目前还有重要的一步,就是导入opencv2.framework。 这一步在Xcode Project中也可以做,但是每次都要手动设置很麻烦。Assets⁩ ▸ ⁨UnityOpenCVPlugin⁩ ▸ ⁨Plugins⁩ ▸ ⁨iOS⁩ ▸ ⁨Editor⁩下的UnityOpenCVBuildPostprocessor.cs帮我们自动完成了opencv2.framework的导入。

proj.AddFrameworkToProject(proj.TargetGuidByName("Unity-iPhone"), "opencv2.framework", false);

注意这里Unity会在默认文件夹找这个framework,我们需要提前把opencv2.framework放到下面这个文件夹。其中iPhoneOSxx.x.sdk是你安装的sdk版本。关于如何编译opencv2.framework,网站上有很多教程,这里就不赘述了。

Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOSxx.x.sdk/System/Library/Frameworks/

最后一步就是build project,在打开的Xcode Project中使用你自己的账号,安装到手机即可。

Beyond this Demo

这个小demo到这里就结束了,然而这仅仅是一个框架,小伙伴们可以自由发挥,填充进去更多的东西。笔者在一年前使用OpenCV标定图像放置虚拟物品,只可惜几个月后苹果发布了ARKit1.5,实现了同样的功能。然而理论上OpenCV可以做到的东西远不止图像处理,使用这个框架加上Unity的渲染引擎,能做到的就靠大家的想象力了。

关于标定图像放置物品的场景也已上传,有兴趣的同学自行研究,做出来是这个样子的:

觉得有帮助的小伙伴记得点个赞哦~

项目地址:

推荐阅读

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值