在 iOS 相机中启动定期任务以使用帧数据的最佳方法是啥?

Posted

技术标签:

【中文标题】在 iOS 相机中启动定期任务以使用帧数据的最佳方法是啥?【英文标题】:What's the best way to launch a periodic task in iOS camera to use frames data?在 iOS 相机中启动定期任务以使用帧数据的最佳方法是什么? 【发布时间】:2020-07-08 13:01:23 【问题描述】:

我正在构建一个跨平台工具,该工具使用来自相机的帧对它们进行一些处理。我从this code-base 开始,这是相机Flutter 插件。我的目标是有一个定期任务(每 200 毫秒运行一次),它可以并行执行一些工作(因此它不会干扰帧速率)。为此,我的目标是存储来自相机的每一帧,并在触发任务时,使用最后存储的帧进行一些计算。

下面,我将展示我所做的事情,但我认为这不是正确的做法,它并没有真正在单独的线程上运行。

    将 CVPixelBuffer 帧保存为 FLTCam 的属性
    @property(readwrite, atomic) CVPixelBufferRef volatile lastFramePixelBuffer;
    将lastFramePixelBuffer 保存在captureOutput 中,在(l350) CFRetain(newBuffer); 之后
    _lastFramePixelBuffer = newBuffer;
    在camera start 中启动周期性任务
  [self startTimedTask]; //in [camera start]
- (void)startTimedTask  //start periodic task in 5 seconds

    dispatch_async(dispatch_get_main_queue(), ^
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(startTimedTaskRepeat) userInfo:nil repeats:NO];
    );


- (void)startTimedTaskRepeat  // trigger periodic task every 0.2secs

    dispatch_async(dispatch_get_main_queue(), ^
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(performBackgroundTask) userInfo:nil repeats:YES];
    );


- (void)performBackgroundTask 

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
        //Do background work
        [self doBackgroundWork];
    );


- (void)doBackgroundWork  // the task, which takes about 100ms
    CVPixelBufferRef frame = _pixelBufferToInfere;
    CFRetain(frame);
    NSLog(@"Image size is: H = %zd", CVPixelBufferGetHeight(frame));NSLog(@"Image size is: W = %zd", CVPixelBufferGetWidth(frame));
    [self calculate:frame]; 
    CFRelease(frame);    


我想知道以可靠和安全的方式实现这种并行计算和最新帧“缓存”的最佳方式,现在任务执行之间的间隔似乎不一致,并且 fps 似乎有点低。

更新

我目前正在使用(而不是函数startTimedTask)下面的函数

- (void)startTimedTaskRepeat2

    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    self->timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, backgroundQueue);
    dispatch_source_set_timer(timerSource, dispatch_time(DISPATCH_TIME_NOW, 0), 0.2*NSEC_PER_SEC, 0*NSEC_PER_SEC); 
    dispatch_source_set_event_handler(timerSource, ^
        [self infereFrame];
    );
    dispatch_resume(timerSource);

另外,我要指出的是,实现完全并行和高效计算实际上对我来说更重要,即使我不得不牺牲几毫秒的周期精度

【问题讨论】:

请注意,在您的问题中,您说“每 .2 个左右”,但后来您断言您想要高精度。 (请注意,无论如何你的目标是不可能的。) 出于连贯性的原因,我已经更新了问题,更多地关注我想要实现的目标 是的,您编辑的 dispatch_source_set_timer 肯定比 NSTimer 更精确。如果您以较低的延迟(例如 10 毫秒而不是 200 毫秒)启动它并且在它的处理程序内部使用 [NSDate new] 中的当前时间戳来检查是否从上次处理过去了 200 毫秒,那么您可能会获得更好的精度,现在是时候触发了再次进行此处理。当您在 backgroundQueue 上启动此计时器时,您已经并行运行它,因此您似乎已经拥有想要实现的目标。 使用 ios / 相机系统,您可以得到如此准确的时间,这是不可思议的。它甚至不在球场上。 我非常同意@Fattie,因为处理某些事情可能需要时间。您无法定义任务必须加载的时间。如果您设置了一个计时器(200 毫秒),那么当时间到时,任务可能会或可能不会被处理。这个过程需要时间。但是,我鼓励您进行测试,看看您想要的时间是否可以实现。如果在时间阈值下有想要的结果,那就太好了。 【参考方案1】:

为此不需要计时器,因为您首先需要一个视频帧来完成您的工作,因此请使用接收到的帧作为您的任务的触发器。 相机启动时:

NSTimeInterval lastProcessingTime = CurrentMediaTime()

在 captureOutput 回调中:

NSTimeInterval now = CurrentMediaTime();
if (now-lastProcessingTime > 0.2) 
    lastProcessingTime = now;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^
       // Do your background work
    );

当然,0.2 对应于 200ms。它应该相当精确,因为帧速率相当规律。 但请确保您的后台处理时间少于 200 毫秒,否则您的应用迟早会崩溃。

【讨论】:

【参考方案2】:

OP,

“我需要非常精确……”

很遗憾,完全忘记了。

没有办法用 iPhone(事实上,几乎所有常见的商业操作系统)进行“非常精确”的计时。

很抱歉,您的项目存在误解。

尤其是当你从相机中拉出来的时候,特别没希望,时间很不稳定。

(这对于游戏引擎来说是一个巨大的误解。人们会尝试“精确地”将计时器设置为某个值。当然,游戏引擎是基于帧的 - 它只是运行任何帧中的项目恰好超过了计时器数量。帧计时完全不稳定。)

【讨论】:

以上是关于在 iOS 相机中启动定期任务以使用帧数据的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

React Native:关闭应用程序时在 iOS 上运行后台任务的最佳方法?

在 iOS 中定期在后台线程中运行任务

对静态相机的背景图像进行建模以检测移动物体

尝试在 4.0 硬件手机上启动 android 相机示例应用程序并仅显示一些帧

使用 Django 启动和停止定期后台任务

任务终止的最佳实践