学习ios Metal(9)—iphone X真实感深度相机True Depth Camera的调用和metal GPGPU

        metal的基础知识入门,首推Metal By Example系列:http://metalbyexample.com/。博主的相关文章,主要给出工程实际遇到的典型问题及其解决方案。

                                        

        本节源码:https://github.com/sjy234sjy234/Learn-Metal/tree/master/TrueDepthStreaming。从第7节开始,渲染统一采用该节介绍的工程化渲染框架:https://blog.csdn.net/sjy234sjy234/article/details/82497799

        这一次,主要介绍两个内容:1)iphone X的真实感深度相机调用,获取实时深度帧;2)利用metal的通用计算kernel核函数将深度帧可视化为纹理。如图所示,是实时获取的彩色帧和深度帧的可视化效果。注意这个项目只能在iphone X平台上才可以运行,目前只有iphone X支持真实感深度相机。

1、iphone X的真实感深度相机调用:

        参考博主封装的FrontCamera类,要获取最高的深度图像帧率,需要设置如下:

    if(isDepthEnabled)
    {
        [self.avCaptureSession setSessionPreset: AVCaptureSessionPreset640x480];
        self.videoDevice=[AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInTrueDepthCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionFront];
    }

        这里的preset,有一些支持深度,有一些不支持深度。但是只有AVCaptureSessionPreset640x480支持最高的帧率和最大的深度利用率。但是这个设置下,得到的彩色帧的分辨率也只有640x480,可以尝试用其他的preset。调用真实感深度相机的主要坑点是在AVCaptureDeviceTypeBuiltInTrueDepthCamera这个配置上,当时没有搜到demo,博主自己尝试出来的。其余的AVCaptureSession、AVCaptureVideoDataOutput、AVCaptureDepthDataOutput、AVCaptureDataOutputSynchronizer这些都非常常规,而且iphone双摄支持深度图像,官方有demo可以参考的。

2、metal通用计算GPGPU的kernel函数

        对于从相机获取的彩色帧,直接调用VideoRenderer类进行纹理的渲染即可,里面封装了TextureRendererEncoder。而对于深度帧,是不能直接进行纹理渲染的,DepthRenderer类里面封装了DisparityToTextureEncoder和TextureRendererEncoder。首先,在DepthRenderer中,把深度帧转化为16进制的id<MTLBuffer>,作为kernel函数的输入:

id<MTLBuffer> inDisparityBuffer = [_metalContext bufferWithF16PixelBuffer: depthPixelBuffer];

        然后调用DisparityToTextureEncoder类,把id<MTLBuffer>格式的深度帧转化为可视化的纹理id<MTLTexture>,这里实现了一个非常简单的GPGPU的kernel函数的封装,首先是encode函数:

- (void)encodeToCommandBuffer: (id<MTLCommandBuffer>) commandBuffer inDisparityBuffer:(const id<MTLBuffer>)inDisparityBuffer outTexture: (id<MTLTexture>) outTexture
{
    if(!commandBuffer)
    {
        NSLog(@"invalid commandBuffer");
        return ;
    }
    if(!inDisparityBuffer)
    {
        NSLog(@"invalid disparity buffer");
        return ;
    }
    if(!outTexture)
    {
        NSLog(@"invalid out texture");
        return ;
    }
    
    const NSUInteger width = 8;
    const NSUInteger height = 8;
    const NSUInteger depth = 1;
    _threadgroupSize = MTLSizeMake((width), (height), depth);
    _threadgroupCount.width  = (outTexture.width  + _threadgroupSize.width -  1) / _threadgroupSize.width;
    _threadgroupCount.height = (outTexture.height + _threadgroupSize.height - 1) / _threadgroupSize.height;
    _threadgroupCount.depth = depth;
    
    id<MTLComputeCommandEncoder> computeEncoder = [commandBuffer computeCommandEncoder];
    [computeEncoder setComputePipelineState:_computePipeline];
    [computeEncoder setBuffer: inDisparityBuffer offset:0 atIndex:0];
    [computeEncoder setTexture: outTexture atIndex:0];
    [computeEncoder dispatchThreadgroups:_threadgroupCount
                   threadsPerThreadgroup:_threadgroupSize];
    [computeEncoder endEncoding];
}

        encode函数中,首先根据纹理尺寸,配置kernel的thread group size和thread group count,然后分配computeEncoder(这里区别于渲染时分配的renderEncoder),并对其进行程序编码。然后是DisparityToTextureEncoder.metal文件中的kernel函数:        

#include <metal_stdlib>
using namespace metal;

// disparityToTexture compute kernel
kernel void
disparityToTexture(constant half*  currentDisparityBuffer  [[buffer(0)]],
               texture2d<float, access::write> outTexture [[texture(0)]],
               uint2  gid         [[thread_position_in_grid]],
               uint2  tspg        [[threads_per_grid]])
{
    uint invid = gid.y * tspg.x + gid.x;
    half inDisparity = currentDisparityBuffer[invid];
    half inDepth = 1.0 / inDisparity;
    float4 outColor = {inDepth, inDepth, inDepth, inDepth};
    outTexture.write(outColor, gid);
}

        这里,tspg是整个kernel的size,等于输入帧的尺寸。gid是当前执行线程在kernel中的位置,用于数据的重定位。在这个kernel中,gid即纹理坐标,但是对于MTLBuffer来说,不能用纹理方式读取数据,只能用重新计算的方式定位,它们在内存中是行优先存储的,有重定位计算式 —— invid = gid.y * tspg.x + gid.x。

       在调用完DisparityToTextureEncoder类得到可以渲染的可视化纹理以后,再调用TextureRendererEncoder类进行视图渲染即可。

        PS:

       (1)说tspg等于输入帧的尺寸是不准确的,只有输入帧的尺寸整除thread group size的时候这才是成立的,实际上tspg等于thread group size * thread group count。但在这个demo中是成立的,因为输入帧的尺寸是640x480,可以整除8。因此这个DisparityToTextureEncoder只支持分辨率尺寸能整除8的帧作为输入,否则需要修改kernel的重定位算式。

        (2)iphone X获取的实时深度帧是以float16的格式存储的,并且存储的值叫做disparity,它的单位是(1/m),因此换算成深度值是:inDepth = 1.0 / inDisparity。

        (3)metal的GPGPU计算,和其他语言的GPGPU是相似的,如Cuda,可以学习Cuda进行入门。Metal的学习资料比较有限,基本上只能靠查官方文档,并且官方文档也有一些过时,有时靠自己试错。

                 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值