iOS心率检测算法

42

我正在开发一个应用程序,尝试实现心率记录功能。

最好的方法是使用iPhone的相机并打开闪光灯,让用户将手指放在镜头上,检测视频流中的波动,这些波动对应于用户的心跳。

我在以下Stack Overflow问题中找到了一个非常好的起点here

该问题提供了有用的代码来绘制心跳时间图。

它展示了如何启动AVCaptureSession并打开相机的闪光灯:

session = [[AVCaptureSession alloc] init];

AVCaptureDevice* camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if([camera isTorchModeSupported:AVCaptureTorchModeOn]) {
    [camera lockForConfiguration:nil];
    camera.torchMode=AVCaptureTorchModeOn;
    //  camera.exposureMode=AVCaptureExposureModeLocked;
    [camera unlockForConfiguration];
}
// Create a AVCaptureInput with the camera device
NSError *error=nil;
AVCaptureInput* cameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:camera error:&error];
if (cameraInput == nil) {
    NSLog(@"Error to create camera capture:%@",error);
}

// Set the output
AVCaptureVideoDataOutput* videoOutput = [[AVCaptureVideoDataOutput alloc] init];

// create a queue to run the capture on
dispatch_queue_t captureQueue=dispatch_queue_create("catpureQueue", NULL);

// setup our delegate
[videoOutput setSampleBufferDelegate:self queue:captureQueue];

// configure the pixel format
videoOutput.videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA], (id)kCVPixelBufferPixelFormatTypeKey,
                             nil];
videoOutput.minFrameDuration=CMTimeMake(1, 10);

// and the size of the frames we want
[session setSessionPreset:AVCaptureSessionPresetLow];

// Add the input and output
[session addInput:cameraInput];
[session addOutput:videoOutput];

// Start the session
[session startRunning];

在这个例子中,Self必须是一个<AVCaptureVideoDataOutputSampleBufferDelegate>,因此必须实现以下方法来获取原始相机数据:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
static int count=0;
count++;
// only run if we're not already processing an image
// this is the image buffer
CVImageBufferRef cvimgRef = CMSampleBufferGetImageBuffer(sampleBuffer);
// Lock the image buffer
CVPixelBufferLockBaseAddress(cvimgRef,0);
// access the data
int width=CVPixelBufferGetWidth(cvimgRef);
int height=CVPixelBufferGetHeight(cvimgRef);
// get the raw image bytes
uint8_t *buf=(uint8_t *) CVPixelBufferGetBaseAddress(cvimgRef);
size_t bprow=CVPixelBufferGetBytesPerRow(cvimgRef);
float r=0,g=0,b=0;
for(int y=0; y<height; y++) {
    for(int x=0; x<width*4; x+=4) {
        b+=buf[x];
        g+=buf[x+1];
        r+=buf[x+2];
        //          a+=buf[x+3];
    }
    buf+=bprow;
}
r/=255*(float) (width*height);
g/=255*(float) (width*height);
b/=255*(float) (width*height);

float h,s,v;

RGBtoHSV(r, g, b, &h, &s, &v);

// simple highpass and lowpass filter 

static float lastH=0;
float highPassValue=h-lastH;
lastH=h;
float lastHighPassValue=0;
float lowPassValue=(lastHighPassValue+highPassValue)/2;

lastHighPassValue=highPassValue;

    //low pass value can now be used for basic heart beat detection


}

将RGB转换为HSV,监测色调的波动。

RGB转换为HSV的实现如下:

void RGBtoHSV( float r, float g, float b, float *h, float *s, float *v ) {
float min, max, delta; 
min = MIN( r, MIN(g, b )); 
max = MAX( r, MAX(g, b )); 
*v = max;
delta = max - min; 
if( max != 0 )
    *s = delta / max;
else {
    // r = g = b = 0 
    *s = 0; 
    *h = -1; 
    return;
}
if( r == max )
    *h = ( g - b ) / delta; 
else if( g == max )
    *h=2+(b-r)/delta;
else 
    *h=4+(r-g)/delta; 
*h *= 60;
if( *h < 0 ) 
    *h += 360;
}

capureOutput:计算的低通值最初提供不规则数据,但之后稳定在以下数值:

2013-11-04 16:18:13.619 SampleHeartRateApp[1743:1803] -0.071218
2013-11-04 16:18:13.719 SampleHeartRateApp[1743:1803] -0.050072
2013-11-04 16:18:13.819 SampleHeartRateApp[1743:1803] -0.011375
2013-11-04 16:18:13.918 SampleHeartRateApp[1743:1803] 0.018456
2013-11-04 16:18:14.019 SampleHeartRateApp[1743:1803] 0.059024
2013-11-04 16:18:14.118 SampleHeartRateApp[1743:1803] 0.052198
2013-11-04 16:18:14.219 SampleHeartRateApp[1743:1803] 0.078189
2013-11-04 16:18:14.318 SampleHeartRateApp[1743:1803] 0.046035
2013-11-04 16:18:14.419 SampleHeartRateApp[1743:1803] -0.113153
2013-11-04 16:18:14.519 SampleHeartRateApp[1743:1803] -0.079792
2013-11-04 16:18:14.618 SampleHeartRateApp[1743:1803] -0.027654
2013-11-04 16:18:14.719 SampleHeartRateApp[1743:1803] -0.017288

最初提供的不稳定数据的示例在这里:

2013-11-04 16:17:28.747 SampleHeartRateApp[1743:3707] 17.271435
2013-11-04 16:17:28.822 SampleHeartRateApp[1743:1803] -0.049067
2013-11-04 16:17:28.922 SampleHeartRateApp[1743:1803] -6.524201
2013-11-04 16:17:29.022 SampleHeartRateApp[1743:1803] -0.766260
2013-11-04 16:17:29.137 SampleHeartRateApp[1743:3707] 9.956407
2013-11-04 16:17:29.221 SampleHeartRateApp[1743:1803] 0.076244
2013-11-04 16:17:29.321 SampleHeartRateApp[1743:1803] -1.049292
2013-11-04 16:17:29.422 SampleHeartRateApp[1743:1803] 0.088634
2013-11-04 16:17:29.522 SampleHeartRateApp[1743:1803] -1.035559
2013-11-04 16:17:29.621 SampleHeartRateApp[1743:1803] 0.019196
2013-11-04 16:17:29.719 SampleHeartRateApp[1743:1803] -1.027754
2013-11-04 16:17:29.821 SampleHeartRateApp[1743:1803] 0.045803
2013-11-04 16:17:29.922 SampleHeartRateApp[1743:1803] -0.857693
2013-11-04 16:17:30.021 SampleHeartRateApp[1743:1803] 0.061945
2013-11-04 16:17:30.143 SampleHeartRateApp[1743:1803] -0.701269
低通值在每次心跳时都会变为正数。因此,我尝试了一个非常简单的实时检测算法,基本上只看当前值,看它是否为正数,还查看先前的值,如果是负数,则检测到从负数到正数的变化并播放蜂鸣声。
这样做的问题是数据并不总是像上面那样完美,有时负值读数中间会出现异常的正读数,反之亦然。
低通值随时间的图形如下所示:
有趣的是,上面的异常情况非常普遍。如果我记录一段时间的图表,我会看到类似形状的异常发生多次。
在我的非常简单的节拍检测算法中,如果发生如上所示的异常,检测期内(10秒)计算的心跳次数可能增加4或5次。这使得计算的BPM非常不准确。但是尽管它很简单,它确实有70%的成功率。
为了解决这个问题,我尝试了以下方法。
1.开始记录最后3个低通值的数组
2.然后查看中间值前后是否有两个较小的值。 (基本峰值检测)
3.将此情况计为一个节拍,并将其添加到给定时间内的节拍总数中。
然而,这种方法也像其他任何方法一样容易受到异常情况的影响。实际上似乎是一个更糟糕的方法。 (在检测后播放蜂鸣声时,它们似乎比正向到负向的算法更加不稳定)
我的问题是,您能否帮助我想出一种能够可靠地检测心跳发生的算法,并具有合理的准确性。
我意识到还需要解决的另一个问题是检测用户的手指是否在镜头上。
我考虑过检测不规则的低通值,但问题在于低通滤波器可以解释不规则值并随时间平滑处理它们。因此,在那里提供帮助将不胜感激。
感谢您的时间。

2
我的建议是看一下信号处理中使用的任何降噪算法,例如高斯算法等。 - Mike M
3
你好,这是我发布代码的样例项目链接:http://dl.dropbox.com/u/508075/SampleHeartRateApp.zip。在这个项目中,他们使用了一个名为SimpleChart的简单类来绘制图表。 - Sam
1
@Unheilig 你说得对,它确实是垃圾。在我的试验中,我已经放弃了它,并且正在使用另一种信号处理技术,现在已经取得了更好的结果(如果完全成功,我会详细说明)。哦,它并不能真正检测出节拍,是的,它在图表上显示它们,但我正在尝试计算诸如每分钟节拍之类的东西。我使用低通值只是因为当我查看从中获得的值时,我可以想出易于检测BPM的简单算法。 - Sam
@Sam,你找到获取适当的心跳计数的合适解决方案了吗? - Piyush Hirpara
1
我会考虑对数据应用快速傅里叶变换,然后选择大约在0.5赫兹到4赫兹频带内的频率成分。这将去除低频和高频噪声。 - Hot Licks
显示剩余6条评论
7个回答

6
这个问题的答案有点复杂,因为你需要做几件事来处理信号,而且没有单一的“正确”方法。但是,对于你的过滤器,你需要使用一个带通滤波器。这种类型的滤波器允许你指定接受高端和低端的一系列频率。对于人类心跳,我们知道这些范围应该是什么(不低于40 bpm,不高于250 bpm),所以我们可以创建一个过滤器,去除这个范围之外的频率。滤波器还将数据移动到零点中心,因此峰值检测变得更加容易。即使用户增加/减少他们的手指压力(在一定程度上),这个滤波器也会给你一个更平滑的信号。之后,还需要进行额外的平滑和异常值去除。
我使用过一种特定类型的带通滤波器,名为Butterworth滤波器。由于滤波器的变化取决于采集数据的频率,因此手动创建它有些麻烦。幸运的是,有一个网站可以帮助你创建 这里。如果您的数据采集频率为30 fps,则频率将为30 Hz。
我创建了一个项目,将所有内容整合在一起,能够很好地检测用户的心率,并将其包含在我的iOS应用商店中的应用程序中。我已经在 github 上公开了心率检测代码。

感谢分享。虽然这不是一个完整的项目,但这个模型已经足够我使用了。 - DzungPV
这个项目到目前为止对我非常有用,但是你能否更新它以兼容iOS 10.1?那将不胜感激。(如果您这样做,我会给您的答案颁发奖励。) - fi12
它已经兼容了 :) 该项目从未有过一个可工作的示例,但是 read-me 显示了调用所需方法的简单性。确保将 iOS 10 隐私-相机条目添加到您的 plist 中。 - lehn0058
@lehn0058 我已经仔细查看了这段代码。有一件事我不明白,就是你如何使Butterworth滤波器独立于帧速率工作。你在上面说“滤波器的变化基于你收集数据的频率”,但是在代码中只定义了一个滤波器。如果我尝试在每个可能的帧速率(30、60等)下定义一个单独的滤波器,会不会更好?据我所知,你是这样推导出滤波器的:mkfilter -Bu -Bp -o 4 -a 0.0222 0.1389,但如果帧速率为60,则会变成mkfilter -Bu -Bp -o 4 -a 0.0222/2 0.1389/2,对吗? - pnadeau

2
我猜你是用自己的手指。你确定你没有心跳不规律吗?此外,你将要处理有心跳不规律的人。换句话说,你应该尝试使用各种输入值进行测试。一定要在父母或其他年长亲戚身上尝试,因为他们可能更容易出现心脏问题。除此之外,你的基本问题是输入源会带来噪声;你基本上正在尝试从噪音中恢复信号。有时这是不可能的,你需要决定是否想将噪声融入报告中,或者当数据流过于嘈杂时忽略它。
尝试使用不同的过滤器值;也许你需要更低通的滤波器。根据评论,听起来你的低通滤波器不好;网络上有大量关于滤波的资源。如果你拥有良好的可视化工具,那将是测试算法的最佳方式。
你可以尝试对数据进行下采样,这将使其平滑。你可能还想丢弃超出有效范围的样本,可以通过完全丢弃该值,用前后样本的平均值替换它,或将其夹紧到某个预定或计算的最大值来实现。
我对这些应用程序最大的不满是,它们将打嗝视为真实的数据。我的健身房里有一辆自行车提供无用的心率读数,因为每隔一段时间它就找不到我的脉搏,突然认为我的心跳速度达到了300次/分钟。(实际上并没有;我问过医生。)对于20分钟的运动,它的平均值是无用的。我认为,看到(例如)最后十个正常心跳的平均值加上异常率更有用,而不是“我试图将最后20秒钟塞入这个算法中,这里是它吐出来的垃圾”。如果你能创建一个单独的过滤器来指示异常,那么你将拥有一个更有用的应用程序。

1
我会:
1)检测峰值之间的周期... 如果周期在一定周期范围内保持一致,则将HB标记为有效。
2)我会实现一个异常值检测算法... 任何超过一定归一化阈值的心跳都将被视为异常值,因此我将使用上次检测到的心跳来计算我的BPM。
另一种更复杂的方法是使用卡尔曼滤波器或类似的东西来预测bpm并获得更准确的读数.. 只有在几次跳过后,应用程序才会感知到读数无效并停止读数。

1
我制作了一个项目,使用GPUImage过滤器,平均颜色和曝光来检测你的脉搏。脉搏是根据过滤图像中绿色分量的运行平均值进行估计的。

光学脉搏阅读器


0
首先解决你手指遮挡镜头的问题。当手指遮挡镜头时,你不会得到一个静态的黑色画面(正如人们可能会认为的那样)。环境光实际上通过你的手指来创建一个带有红色边框的画面。此外,手指中的血流模式会导致该红色边框出现轻微的周期性变化(尝试打开相机应用程序,将手指完全放在镜头上)。此外,如果环境光不足,你可以随时打开相机闪光灯/手电筒来补偿这一点。
对于一个开源(并且逐步)的过程,请尝试:

http://www.ignaciomellado.es/blog/Measuring-heart-rate-with-a-smartphone-camera

此外,我建议您阅读有关脉冲测量的以下专利:

http://www.google.com/patents/WO2013042070A1?cl=en


0

听起来你可能已经有了另一种方法,但你可以尝试使用一个小的中值滤波器。例如,对于每个输出值,使用3到7个输入值的中值将平滑那些峰值,而不会破坏非异常数据的整体形状。


0

你正在试图手动检测单个心跳,但这不会很可靠。我建议你使用像音高或频率检测库这样的东西(检测颜色变化频率和声音频率的数学原理是相同的)。

也许像aubio这样的东西可以帮助你,在thisstackoverflow答案中搜索“频率检测”。否则,请查看维基百科上的pitch detection或者其他大量信号处理库。


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