实战 | Unity下ARKit与OpenCV的结晶

Posted 计算机视觉life

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实战 | Unity下ARKit与OpenCV的结晶相关的知识,希望对你有一定的参考价值。

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


本文阅读大概6分钟

前言

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,如下图所示:

实战 | Unity下ARKit与OpenCV的结晶

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。

实战 | Unity下ARKit与OpenCV的结晶

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

实战 | Unity下ARKit与OpenCV的结晶

实战 | Unity下ARKit与OpenCV的结晶
实战 | Unity下ARKit与OpenCV的结晶

细心的小伙伴会发现,这里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(360640, img.type());
    resize(img, gray, gray.size(), cv::INTER_AREA);

    // Canny edge
    Mat edge;
    Canny(gray, edge, 100200);
    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" {
    voidallocateVideoCapture(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 指手动内存管理
voidallocateVideoCapture(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(640360, 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为负。

实战 | Unity下ARKit与OpenCV的结晶

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

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

实战 | Unity下ARKit与OpenCV的结晶

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

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

推荐阅读










欢迎关注公众号:计算机视觉life,一起探索计算机视觉新世界~
好文!给个好看啦~   

以上是关于实战 | Unity下ARKit与OpenCV的结晶的主要内容,如果未能解决你的问题,请参考以下文章

Forge ARKit - Navisworks 到 Unity

ARKit - 环境遮挡

ARKit For Unity windows开发教程

Unity ARKit XR 插件 – 人脸追踪

Unity ARKit开发配置+升级URP

兼容ARKit和ARCore,Unity发布AR跨平台开发工具ARInterface