iOS心率检测algorithm

我试图在我正在开发的应用程序中实现心跳loggingfunction。

这样做的首选方法是使用iPhone的照相机,使用户将手指放在镜头上,并检测videoinput中与用户心脏相对应的波动。

我发现这里有一个很好的起点,下面的堆栈溢出问题

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

它显示了如何启动一个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+(br)/delta; else *h=4+(rg)/delta; *h *= 60; if( *h < 0 ) *h += 360; } 

capureOutput:计算的低通值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 

每当有心脏跳动时,低通值变为正值。 所以我尝试了一个非常简单的实时检测algorithm,它基本上查看当前值,看看它是否为正值,也会查看以前的值,如果为负值,则检测负值变为正值并发出嘟嘟声。

问题在于数据并不总是像上述那样完美,有时在负面阅读中有exception的正面阅读,反之亦然。

低通值在时间上的图如下所示: 在这里输入图像说明

有趣的是,上述exception现象相当普遍,如果我logging一段时间,我会多次看到一个非常相似的exceptionexception。

在我非常简单的节拍检测algorithm中,如果出现上述exception情况,则在检测周期(10秒)内的拍子数可能会增加4或5拍。 这使得计算的BPM非常不准确。 但是就这么简单,大约70%的时间都可以工作。

为了解决这个问题,我尝试了以下方法。

1.开始logging数组中最后3个低通值

然后看看中间值前后是否有两个较小的值。 (基本峰值检测)

3.把这个情景作为一个节拍,并把它添加到给定时间内的节拍总数。

然而,这种方法与其他任何exception一样易受攻击。 实际上似乎是一个更糟糕的方法。 (在检测后播放实时蜂鸣声时,它们看起来比正面到负面algorithm更不稳定)

我的问题是你能帮助我想出一个algorithm,可以可靠地检测到心跳发生时的合理准确性。

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

我想过检测不稳定的低通值,但是存在低通滤波器会导致不稳定的值,并且随着时间的推移将其平滑。 所以帮助那里也将被赞赏。

谢谢你的时间。

这个问题的答案有一点涉及,因为你需要做几件事情来处理信号,没有一个“正确”的方法来做到这一点。 但是,对于您的滤波器,您希望使用带通滤波器 。 这种types的滤波器允许您指定在高端和低端均可接受的频率范围。 对于人类心脏跳动,我们知道这些边界应该是什么(不低于40 bpm,不高于250 bpm),所以我们可以创build一个滤波器来消除这个范围之外的频率。 滤波器还将数据移动到中心位置,因此峰值检测变得更容易。 即使您的用户增加/减less手指压力(某种程度上),这个filter也会给您一个更加平滑的信号。 之后,还需要进行额外的平滑和exception移除。

我使用的特定types的带通滤波器是巴特沃思滤波器。 由于filter根据您正在收集数据的频率而变化,因此手动创build会有一点涉及。 幸运的是,有一个网站可以帮助在这里 。 如果您以30 fps的速度采集数据,则频率将为30 hz。

我已经创build了一个项目,将所有这些包装在一起,并检测用户的心率,以将其包含在iOSapp store的应用中。 我已经在github上提供了心率检测代码。

我猜你在用自己的手指。 你确定你没有不规则的心跳吗? 另外,你会想要处理心跳不规则的 换句话说,你应该testing各种各样的input值。 肯定会尝试你的父母或其他年长的亲属,因为他们可能更容易有心脏问题。 除此之外,你的基本问题是,你的input源将是嘈杂的; 你基本上试图从噪音中恢复信号。 有时候这是不可能的,你将不得不决定是否要在你的报告中join噪声,或者当它太吵时忽略数据stream。

继续尝试不同的filter值 ; 也许你需要更低通滤波器。 从评论,这听起来像你的低通滤波器不好, networking上有大量的资源被过滤掉。 如果你有良好的可视化工具,这将是testing你的algorithm的最好方法。

您可以尝试对数据进行降采样 ,从而使数据平滑。 您也可能希望丢弃位于有效范围之外的样品 ,或者将其完全丢弃,将其replace为前一个样品和下一个样品的平均值,和/或将其固定在某个预定的或计算的最大值上。

我对这类应用的最大的兴趣是打嗝被视为真实的实时数据。 在我的健身房里的一辆自行车给了无用的BPM读数,因为每隔一段时间,它都找不到我的脉搏,突然想到我的心脏每分钟300转。 (这不是,我问过我的医生。)对于一个20分钟的会议,它的平均数是无用的。 我认为(例如)最后十次正常跳动的平均值加上exception速率而不是“我试图把最后20秒塞进这个algorithm,这里是它吐出的垃圾”是更有用的。 如果您可以创build一个单独的filter,指出exception,我想你会有一个更有用的应用程序。

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

你试图做“手动”检测一个单一的心跳,这不会是非常强大的。 我想说,你最好的select是音高或频率检测库(检测颜色变化的频率和检测声audio率的math必须相同)。

也许像我find通过这个 stackoverflow答案search“频率检测”的aubio可以帮助你。 否则检查维基百科的音调检测和/或一些吨的信号处理库。

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

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

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

另外,我build议你阅读下面的脉搏测量专利:

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

我会 :

1)检测从峰到峰的周期…如果周期在一定的时间段内一致,则将HB标记为有效。

2)我会实现一个outliar检测algorithm…超过一定的规范化阈值的任何节拍..将被视为一个exception,因此我会使用最后检测到的节拍,而不是计算我的BPM。

另一个更复杂的方法是使用卡尔曼滤波器或其他类似的东西,以便能够预测bpm并获得更准确的读数。只有经过几次跳跃,应用才会感觉到读数无效,并停止读。

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

光学脉冲阅读器