在iPhone上使用GPU(metal)和CPU(OpenCV)处理相机数据流

16

我正在 iOS 上进行 120 帧实时视频处理,并希望首先在 GPU 上预处理图像(降采样、转换颜色等),然后使用 OpenCV 在 CPU 上后处理帧。

使用 Metal,分享相机数据流在 GPU 和 CPU 之间的最快方法是什么?

换句话说,管道将如下所示:

CMSampleBufferRef -> MTLTexture or MTLBuffer -> OpenCV Mat

我正在以下述方式将CMSampleBufferRef转换为MTLTexture

CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);

// textureRGBA
{
    size_t width = CVPixelBufferGetWidth(pixelBuffer);
    size_t height = CVPixelBufferGetHeight(pixelBuffer);
    MTLPixelFormat pixelFormat = MTLPixelFormatBGRA8Unorm;

    CVMetalTextureRef texture = NULL;
    CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, _textureCache, pixelBuffer, NULL, pixelFormat, width, height, 0, &texture);
    if(status == kCVReturnSuccess) {
        textureBGRA = CVMetalTextureGetTexture(texture);
        CFRelease(texture);
    }
}

完成我的金属着色器后,我将MTLTexture转换为OpenCV

cv::Mat image;
...
CGSize imageSize = CGSizeMake(drawable.texture.width, drawable.texture.height);
int imageByteCount = int(imageSize.width * imageSize.height * 4);
int mbytesPerRow = 4 * int(imageSize.width);

MTLRegion region = MTLRegionMake2D(0, 0, int(imageSize.width), int(imageSize.height));
CGSize resSize = CGSizeMake(drawable.texture.width, drawable.texture.height);
[drawable.texture getBytes:image.data bytesPerRow:mbytesPerRow  fromRegion:region mipmapLevel:0];

一些观察结果:

1)不幸的是,MTLTexture.getBytes 看起来很昂贵(从GPU复制数据到CPU?),在我的iPhone 5S上大约需要5毫秒,当处理约100fps时这太多了。

2)我注意到一些人使用MTLBuffer代替MTLTexture并使用以下方法:metalDevice.newBufferWithLength(byteCount, options: .StorageModeShared) (参见:Memory write performance - GPU CPU Shared Memory

但是 CMSampleBufferRef 和相关的 CVPixelBufferRef 是由CoreVideo管理的。


GPU并不支持所有分辨率。我知道这不是你想要的答案,只是提供一个关于GPU的信息。 - HariKrishnan.P
你尝试过 GPUImage 吗?https://github.com/BradLarson/GPUImage - Sunil Sharma
我尝试过使用GPUImage,但最大的瓶颈是从GPU传输数据到CPU。GPUImage在底层使用OpenGL,与Metal API相反,无法共享内存。 - pzo
2
我会寻找一种在GPU上执行OpenCV工作的方法。OpenCV的某些部分可在MetalPerformanceShaders.framework中使用,主要是图像处理方面。iOS 10增加了卷积神经网络。如果您需要其他运算符,请向Apple提交功能请求错误报告。 - Ian Ollmann
我正在尝试使用Metal为实时相机视频应用一个简单的暗角滤镜。然而,效果非常慢且延迟高,请帮我确认一下这篇文章并告诉我缺少了什么:https://stackoverflow.com/q/53898780/1364053 - nr5
1个回答

7
最快的方法是使用由MTLBuffer支持的MTLTexture; 它是一种特殊类型的MTLTexture,可以与MTLBuffer共享内存。但是,您的C处理(openCV)将落后于一帧或两帧,这是不可避免的,因为您需要提交命令到GPU(编码),GPU需要渲染它,如果您使用waitUntilCompleted来确保GPU完成操作,那么会消耗CPU并很浪费。
因此,步骤如下:首先创建MTLBuffer,然后使用MTLBuffer方法“newTextureWithDescriptor:offset:bytesPerRow:”创建特殊的MTLTexture。您需要预先创建特殊的MTLTexture(作为实例变量),然后设置标准渲染管线(比使用计算着色器更快),该管线将接收从CMSampleBufferRef创建的MTLTexture,并将其传递到您的特殊MTLTexture中,在该传递中,您可以在一个步骤中缩小并进行任何必要的颜色转换。然后将命令缓冲区提交到GPU,在随后的传递中,您只需调用[theMTLbuffer contents]即可获取指向支持特殊MTLTexture的字节的指针,以供openCV使用。
强制CPU / GPU行为停止的任何技术都永远不会有效,因为一半的时间都会花在等待上,即CPU等待GPU完成,而GPU也必须等到下一次编码(当GPU在工作时,您希望CPU对下一帧进行编码和执行任何openCV工作,而不是等待GPU完成)。
此外,当人们通常提到实时处理时,他们通常指的是具有实时反馈(视觉)的某些处理,所有从4S及以上的现代iOS设备都具有60Hz的屏幕刷新率,因此任何超过该速度的反馈都是无意义的,但如果您需要2帧(以120Hz)来制作1帧(以60Hz),那么您必须使用自定义计时器或修改CADisplayLink。

好的提示是GPU渲染(纹理着色器)可能被限制在60fps - 这很有道理。我实际上需要尽可能小的延迟 - 我有一个使用声音作为用户反馈而不是渲染到显示器的自定义自然用户界面。我不介意CPU等待GPU完成 - 我只想将一些预处理移动到GPU(调整对比度,过滤颜色大小),它们在GPU上非常快而在CPU上非常慢(即使使用NEON),考虑到我的紧密计算预算。但是似乎无法将其他部分移动到GPU中,例如轮廓分析。看来GPU对我来说是死路一条。 - pzo
我不认为这是死路,至少相对容易设置一个以60Hz运行的流水线,在其中每帧进行编码和轮廓分析,同时GPU进行必要的预处理,一旦你将其优化到60Hz(Metal Frame Debugger和Metal System Trace是非常有用的工具),尝试将其提高到120Hz。我从未尝试过使用定时器或CADisplayLink那么快,所以无法帮助您,但请查看:http://stackoverflow.com/questions/23885638/change-interval-of-cadisplaylink。 - Gary
另外,我对轮廓分析不是很熟悉,但是使用Metal的计算功能,您可能能够执行它,因为对比度调整或调整大小不会影响GPU(如果滤镜复杂,请使用LUT)。即使使用标准的顶点和片段着色器,通常也有技巧可以在GPU上执行非友好型GPU操作,我使用Metal实现了一个连接组件标记算法,并且对于小图像而言,与C版本相差不大。 - Gary
我正在尝试使用Metal将简单的晕影滤镜应用于实时相机视频流。结果非常慢,有延迟,请查看以下链接以了解缺失的内容:https://stackoverflow.com/questions/53898780/how-to-get-high-performance-with-ios-metal-and-cifilter-combination - nr5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接