iOS: 同步相机帧和运动数据

10

我正在尝试从相机中捕获帧和相关的运动数据。为了同步,我使用时间戳。视频和运动数据被写入文件,然后进行处理。在此过程中,我可以计算出每个视频的运动帧偏移。

结果发现,相同时间戳的运动数据和视频数据彼此之间的偏移量不同,从0.2秒到0.3秒不等。这个偏移量对于一个视频来说是恒定的,但对于不同的视频则有所不同。如果每次都是相同的偏移量,我就可以减去一些校准值,但事实并非如此。

有没有好的方法来同步时间戳?也许我没有正确记录它们?有没有更好的方法将它们带到同一参考帧上?

CoreMotion返回相对于系统正常运行时间的时间戳,因此我需要添加偏移量才能得到Unix时间:

uptimeOffset = [[NSDate date] timeIntervalSince1970] - 
                   [NSProcessInfo processInfo].systemUptime;

CMDeviceMotionHandler blk =
    ^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error){
        if(!error){
            motionTimestamp = motion.timestamp + uptimeOffset;
            ...
        }
    };

[motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXTrueNorthZVertical
                                                   toQueue:[NSOperationQueue currentQueue]
                                               withHandler:blk];

为了高精度地获取帧时间戳,我使用了AVCaptureVideoDataOutputSampleBufferDelegate。它也与Unix时间偏移量相关:

-(void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection
{
    CMTime frameTime = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer);

    if(firstFrame)
    {
        firstFrameTime = CMTimeMake(frameTime.value, frameTime.timescale);
        startOfRecording = [[NSDate date] timeIntervalSince1970];
    }

    CMTime presentationTime = CMTimeSubtract(frameTime, firstFrameTime);
    float seconds = CMTimeGetSeconds(presentationTime);

    frameTimestamp = seconds + startOfRecording;
    ...
}

Servalex,你最终解决了这个问题吗? - Shai Ben-Tovim
@ShaiBen-Tovim,已添加答案。 不幸的是,我在API中没有找到有用的东西,所以不得不进行一些黑客攻击。 - servalex
2个回答

3
实际上,将这些时间戳进行关联相当简单 - 虽然没有明确的文档说明,但摄像机帧和运动数据时间戳都基于mach_absolute_time()时间基准。这是一个单调计时器,在引导时重置,但重要的是在设备休眠时也停止计数。因此,没有简单的方法将其转换为标准的“挂钟”时间。幸运的是,您不需要这样做,因为时间戳是直接可比较的 - 运动时间戳以秒为单位,您可以在回调中记录mach_absolute_time()以查看它是否是相同的时间基准。我的快速测试显示,运动时间戳通常比处理程序中的mach_absolute_time()早约2毫秒,这似乎与数据报告给应用程序所需的时间大致相同。请注意,mach_absolute_time()以需要转换为纳秒的滴答单位表示;在iOS 10及更高版本中,您可以使用等效的clock_gettime_nsec_np(CLOCK_UPTIME_RAW);执行相同的操作
    [_motionManager
     startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical
     toQueue:[NSOperationQueue currentQueue]
     withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
        // motion.timestamp is in seconds; convert to nanoseconds
        uint64_t motionTimestampNs = (uint64_t)(motion.timestamp * 1e9);
        
        // Get conversion factors from ticks to nanoseconds
        struct mach_timebase_info timebase;
        mach_timebase_info(&timebase);
        
        // mach_absolute_time in nanoseconds
        uint64_t ticks = mach_absolute_time();
        uint64_t machTimeNs = (ticks * timebase.numer) / timebase.denom;
        
        int64_t difference = machTimeNs - motionTimestampNs;
        
        NSLog(@"Motion timestamp: %llu, machTime: %llu, difference %lli", motionTimestampNs, machTimeNs, difference);
    }];

对于相机而言,时间基准也是相同的:
// In practice gives the same value as the CMSampleBufferGetOutputPresentationTimeStamp
// but this is the media's "source" timestamp which feels more correct
CMTime frameTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
uint64_t frameTimestampNs = (uint64_t)(CMTimeGetSeconds(frameTime) * 1e9);

时间戳和处理程序调用之间的延迟有点大,通常在10毫秒左右。

现在我们需要考虑摄像机帧上的时间戳实际上意味着什么 - 这里有两个问题; 有限曝光时间和滚动快门。

滚动快门意味着并非所有图像的扫描线实际上都是同时捕获的 - 首先捕获顶部行,最后捕获底部行。数据的这种滚动读取分散在整个帧时间上,因此在30 FPS相机模式下,最终扫描线的曝光开始/结束时间几乎正好比第一扫描线的相应开始/结束时间晚1/30秒。

我的测试表明AVFoundation框架中的展示时间戳是帧读出的开始 - 即第一扫描线曝光结束的时间。因此,最后一个扫描线曝光结束时间比这个时间晚frameDuration秒,而第一扫描线曝光开始时间比这个时间早exposureTime秒。因此,在帧曝光的中心位置(图像中间扫描线的曝光中点)的时间戳可以计算为:

const double frameDuration = 1.0/30; // rolling shutter effect, depends on camera mode
const double exposure = avCaptureDevice.exposureDuration;
CMTime frameTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
double midFrameTime = CMTimeGetSeconds(frameTime) - exposure * 0.5 + frameDuration * 0.5;

在室内环境中,曝光通常会占满整个帧时间,因此上面的midFrameTimeframeTime相同。这种差异在从明亮的户外场景获得的短曝光时(在极快的运动下)是显着的。

为什么原始方法具有不同的偏移量

我认为你的偏移量的主要原因是你假设第一帧的时间戳是处理程序运行的时间-即它没有考虑捕获数据和传递给应用程序之间的任何延迟。特别是如果您正在使用主队列来处理这些处理程序,我可以想象第一帧的回调被拖延了0.2-0.3秒。

很遗憾,我目前无法验证这一点,但这似乎是一个很好的见解。 - servalex

0
我能找到的最好的解决方案是,在记录的视频上运行一个特征跟踪器,选择一个强特征并绘制其沿着X轴的运动速度,然后将此图与加速度计Y数据相关联。
当有两个类似的沿横坐标轴偏移的图形时,有一种称为交叉相关的技术可以找到偏移量。
这种方法的明显缺点是它很慢,因为它需要一些视频处理。

那个钉子用这么大的锤子也未免有些太过了吧!你尝试过不同的“deviceMotionUpdateInterval”设置吗?CM采样率可能对视频帧速率太“慢”了吗?在偏移或时间计算中可能存在隐藏的四舍五入误差吗? - Shai Ben-Tovim

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