unity打断点直接卡住_实战 | Unity下ARKit与OpenCV的结晶

 点“计算机视觉life”关注,置顶更快接收消息!


本文阅读大概6分钟

本文作者项目地址:https://github.com/TerryLiu007/ARCV2.0

前言

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

1012764a215100123cbf11f63b228c1c.gif

安装

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

7714daceca2b1e8631816d8e11963289.png

Vuforia是另一个AR识别库,这里可以不安装。接下来下载Unity-ARKit-Plugin (https://bitbucket.org/Unity-Technologies/unity-arkit-plugin/downloads/?tab=downloads). 解压并用Unity打开。

三者结合

ARKit -> Unity

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

424803b328a3a9f6d408016bfbe8b97e.png

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

fdc1bf8577c94ebaaab205a45e49ea63.png

7ae1f3766fcff41f89f3109c8b52c757.png
54990f6409fc4342d579feefabf3e3be.png

细心的小伙伴会发现,这里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为负。

0739b2f81ae6c1f54bff0d665ff84329.png

在导出项目前还有重要的一步,就是导入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的渲染引擎,能做到的就靠大家的想象力了。

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

125fba7e6a60ec0c1dcb56b67fb47fd7.gif

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

项目地址:

https://github.com/TerryLiu007/ARCV2.0

推荐阅读

计算机视觉方向简介 | 从全景图恢复三维结构

计算机视觉方向简介 | 阵列相机立体全景拼接

计算机视觉方向简介 | 单目微运动生成深度图

计算机视觉方向简介 | 深度相机室内实时稠密三维重建

计算机视觉方向简介 | 深度图补全

计算机视觉方向简介 | 人体骨骼关键点检测综述

计算机视觉方向简介 | 人脸识别中的活体检测算法综述

计算机视觉方向简介 | 目标检测最新进展总结与展望

计算机视觉方向简介 | 唇语识别技术

计算机视觉方向简介 | 三维深度学习中的目标分类与语义分割

计算机视觉方向简介 | 用深度学习进行表格提取

0fe29cfbf0b966292824ed522f520b23.png

欢迎关注公众号:计算机视觉life,一起探索计算机视觉新世界~ 好文!给个好看啦~    bad3a266a70848ad5de43351b86463e0.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值