iOS心率检测算法

时间:2013-11-04 17:36:08

标签: ios iphone objective-c algorithm camera

我正在尝试在我正在开发的应用中实现心跳录制功能。

这样做的首选方法是使用iPhone的相机打开灯,让用户将手指放在镜头上,并检测视频输入中与用户心脏相对应的波动。

我发现了以下堆栈溢出问题的一个非常好的起点 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];

此示例中的自我必须是<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,并监控Hue的波动。

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

只要有心跳,低通值就会变为正值。所以我尝试了一种非常简单的实时检测算法,它基本上会查看当前值,并查看它是否为正,它还会查看前一个值,如果为负,则检测到负值变为正值并发出哔声。

问题在于数据并不总是如上所述,有时在负读数中会出现异常的正读数,反之亦然。

低通值的图表如下所示: enter image description here

有趣的是,上述异常很常见,如果我记录一段时间的图形,我会多次看到非常相似的形状异常。

在我非常简单的节拍检测算法中,如果出现如上所示的异常,则在检测周期(10秒)内计数的节拍数可以上升4或5拍。这使得计算的BPM非常不准确。但就像它一样简单,它确实在70%的时间内起作用。

为了解决这个问题,我尝试了以下内容。

1.开始记录数组中的最后3个低通值

2.然后看看中间值是否在它之前和之后有两个较小的值。 (基本峰值检测)

3.将此场景设为节拍并将其添加到给定时间内的节拍总数中。

然而,这种方法与任何其他方法一样容易受到异常的影响。实际上似乎是一种更糟糕的方法。 (在检测后播放现场嘟嘟声时,它们似乎比正面到负面算法更不稳定)

我的问题是你可以帮我提出一种能够以合理的准确度可靠地检测出心跳的算法。

我意识到我必须解决的另一个问题是检测用户的手指是否在镜头上。

我考虑过检测不稳定的低通值,但问题是低通滤波器会导致不稳定的值,并随着时间的推移平滑它们。所以帮助也会受到赞赏。

感谢您的时间。

7 个答案:

答案 0 :(得分:4)

这个问题的答案有点牵扯,因为你需要做几件事情来处理信号,而且没有一种“正确”的方法可以做到这一点。但是,对于您的过滤器,您要使用band-pass filter。这种类型的滤波器允许您指定在高端和低端都可接受的频率范围。对于人类心跳,我们知道这些界限应该是什么(不低于40 bpm且不高于250 bpm),因此我们可以创建一个滤波器来消除此范围之外的频率。滤波器还将数据移动到零中心,因此峰值检测变得更加容易。即使您的用户增加/减少手指压力(在一定程度上),此滤镜也会为您提供更加平滑的信号。之后,还需要进行额外的平滑和异常值去除。

我使用的特定类型的带通滤波器是一种butterworth滤波器。由于过滤器根据您收集数据的频率而变化,因此手动创建有点牵涉。幸运的是,有一个网站可以帮助解决这个问题here。如果您以30 fps收集数据,那么频率将为30 hz。

我创建了一个项目,将所有这些组合在一起并检测用户的心率,以便将其包含在iOS应用商店的应用中。我已经在github上提供了心率检测代码。

答案 1 :(得分:1)

我认为你是在用自己的手指。你确定你的心跳不规律吗?此外,您希望处理心跳不规则的人。换句话说,您应该使用各种输入值进行测试。一定要试试你的父母或其他年长的亲戚,因为他们可能更容易出现心脏问题。除此之外,你的基本问题是你的输入源会很吵;你基本上试图从噪音中恢复信号。有时这是不可能的,您将不得不决定是否要将噪音烘焙到报告中,或者只是在数据流过于嘈杂时忽略它。

继续尝试不同的过滤值;也许你需要一个更低通过滤波器。从评论中,听起来你的低通滤波器并不好;在网上过滤那里有大量的资源。如果您拥有良好的可视化工具,那将是测试算法的最佳方式。

您可以尝试对数据进行下采样,这将使其平滑。您可能还希望丢弃超出有效范围的样本,或者完全丢弃该值,将其替换为上一个和下一个样本的平均值,和/或将其钳制到某个预定值或计算出的最大值。

我对这些应用的最大优势是打嗝被视为真实的实时数据。我健身房里的一辆自行车给了无用的bpm读数,因为每隔一段时间,它就找不到我的脉搏,突然觉得我的心脏以300 bpm的速度运行。 (事实并非如此;我已经问过我的医生了。)对于20分钟的疗程,它的平均值是没用的。我认为看到(例如)最后十次正常节拍的平均值加上异常率而不是#34时更有用;我试着将最后20秒塞进这个算法中,这里是垃圾吐出来#34;如果您可以创建一个指示异常的单独过滤器,我认为您将拥有更有用的应用程序。

答案 2 :(得分:1)

我会:

1)检测从峰值到峰值的周期...如果周期在一定的周期阈值内是一致的,那么将HB标记为有效。

2)我会实现一个outliar检测算法...超出某个标准化阈值的任何节拍。将被视为异常值,因此我将使用最后检测到的节拍来计算我的BPM。

另一种更复杂的方法是使用卡尔曼滤波器或类似的东西来预测bpm并获得更准确的读数。只有在几次跳过后,应用程序才会感觉到读数无效并停止阅读。

答案 3 :(得分:0)

听起来你可能已经有了另一种方法,但你可以尝试的一件事就是使用一个小的median filter。例如,对每个输出值使用3到7个输入值的中值将平滑掉那些峰值,而不会破坏非同色数据的整体形状。

答案 4 :(得分:0)

你试图“手动”检测单个心跳,这不会非常强大。我想说你最好的选择就是音调或频率检测库(用于检测颜色变化频率和检测声音频率的数学必须相同)。

我通过aubio stackoverflow找到的this之类的内容可能会对搜索“频率检测”有所帮助。否则,请查看维基百科上的pitch detection和/或其中的一些信号处理库。

答案 5 :(得分:0)

首先解决你的镜头手指问题。当手指在镜头上时,你不会得到一个静态的黑框(正如人们所假设的那样)。环境光实际上通过您的手指创建一个微红色的框架。此外,手指中的血流模式会导致红色框架出现轻微的周期性变化(尝试打开相机应用程序,将手指完全放在镜头上)。此外,如果环境光线不足,您可以随时打开相机闪光灯/手电筒来补偿它。

对于开源(和逐步)过程,请尝试:

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

另外,我建议你阅读以下脉冲测量专利:

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

答案 6 :(得分:0)

我做了一个项目,使用GPUImage滤镜,平均颜色和曝光,来检测你的脉搏。基于滤波图像的绿色分量的运行平均值估计脉冲。

Optical Pulse Reader