iOS 心率检测算法
Posted
技术标签:
【中文标题】iOS 心率检测算法【英文标题】:iOS Heart rate detection Algorithm 【发布时间】:2013-11-15 10:05:26 【问题描述】:我正在尝试在我正在开发的应用中实现心跳记录功能。
这样做的首选方法是使用 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];
本例中的 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,并且监控的是 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
只要有心跳,低通值就会变为正值。所以我尝试了一个非常简单的活体检测算法,它基本上是看当前值,如果是正值,它也会查看之前的值,如果是负值,它会检测到负值变为正值并发出哔声。
问题在于数据并不总是像上面那样完美,有时在负读数中会出现异常的正读数,反之亦然。
低通值的时间图如下所示:
有趣的是,上述异常很常见,如果我记录一段时间的图表,我会多次看到形状非常相似的异常。
在我非常简单的节拍检测算法中,如果出现如上所示的异常情况,则检测期间(10 秒)内的节拍计数可以增加 4 或 5 次。这使得计算的 BPM 非常不准确。但尽管它很简单,但它确实在大约 70% 的时间里工作。
为了解决这个问题,我尝试了以下方法。
1.开始在数组中记录最后3个低通值
2.然后查看中间值前后是否有两个较小的值。 (基本峰值检测)
3.将此场景计为一个节拍,并将其添加到给定时间内的运行节拍总数中。
然而,这种方法和其他任何方法一样容易受到异常的影响。实际上似乎是一种更糟糕的方法。 (在检测后播放实时哔哔声时,它们似乎比正负算法更不稳定)
我的问题是你能不能帮我想出一个算法,该算法能够以合理的准确度可靠地检测心跳的发生时间。
我意识到我必须解决的另一个问题是检测用户的手指是否在镜头上。
我想过检测不稳定的低通值,但问题是低通滤波器会解释不稳定的值并随着时间的推移将它们平滑掉。因此,我们也将不胜感激。
感谢您的宝贵时间。
【问题讨论】:
我的建议是查看信号处理中使用的任何降噪算法。高斯等 您好,这是我从dl.dropbox.com/u/508075/SampleHeartRateApp.zip 发布代码的示例项目的链接。在这个项目中,他们使用一个名为 SimpleChart 的简单类来绘制图表 @Sam 感谢您的链接。我喜欢这种绘图方法,又好又简单。但是在查看代码时,它说不要使用低通和高通滤波器,因为它是垃圾,所以你为什么在这里使用低通值。我对绘图方法更感兴趣,但这让我很好奇——什么是低通和高通?我对心率一无所知,或者我尝试过任何可以做到这一点的应用程序,但在我未受过教育的眼中,该程序似乎完成了?我的意思是,它确实检测到节拍,对吧?谢谢。 @Unheilig 你是对的,它确实说它是垃圾。现在在我自己的试验中,我已经放弃了它,我已经使用另一种信号处理技术获得了更好的结果(如果它完全成功,我会详细说明)哦,它并没有真正检测到节拍,是的,它显示了它们在图表上,但我正在尝试计算每分钟节拍数之类的东西。我使用低通值只是因为当我查看从中得到的值时,我可以想出简单的算法来检测 BPM。 我会考虑对数据应用快速傅立叶变换,然后选择大约 0.5 Hz 到 4 Hz 频带中的频率分量。这将消除低频和高频噪声。 【参考方案1】:这个问题的答案有点复杂,因为您需要做几件事来处理信号,并且没有单一的“正确”方法可以做到这一点。但是,对于您的过滤器,您想使用band-pass filter。这种类型的滤波器允许您指定在高端和低端都可接受的频率范围。对于人类的心跳,我们知道这些界限应该是多少(不低于 40 bpm 且不高于 250 bpm),因此我们可以创建一个过滤器来去除该范围之外的频率。该过滤器还将数据移动到以零为中心,因此峰值检测变得更加容易。即使您的用户增加/减少他们的手指压力(在一定程度上),此过滤器也会为您提供更平滑的信号。之后,还需要进行额外的平滑和异常值移除。
我使用的一种特定类型的带通滤波器是巴特沃斯滤波器。这有点涉及手动创建,因为过滤器会根据您收集数据的频率而变化。幸运的是,有一个网站可以帮助解决这个问题here。如果您以 30 fps 的速度收集数据,那么频率将为 30 hz。
我创建了一个项目,将所有这些都封装在一起,并且可以很好地检测用户的心率,以便将其包含在我在 ios 应用商店的应用中。我已经在github上提供了心率检测代码。
【讨论】:
感谢分享。这不是一个完整的项目,但模型对我来说已经足够了。 到目前为止,该项目对我来说非常有用,但您能否将其更新为与 iOS 10.1 兼容?那将不胜感激。 (如果你这样做,我会奖励你的答案。) 它已经兼容 :) 该项目从来没有一个工作示例,但自述文件显示了调用所需方法是多么简单。确保将 iOS 10 Privacy-Camera 条目添加到您的 plist。【参考方案2】:我猜你是用自己的手指。你确定你没有心律不齐?另外,您将想要处理具有不规则心跳的人。换句话说,您应该使用各种输入值进行测试。一定要在你的父母或其他年长的亲戚身上试试,因为他们可能更有可能有心脏问题。除此之外,您的基本问题是您的输入源会很嘈杂。您基本上是在尝试从该噪声中恢复信号。有时这是不可能的,您必须决定是要在报告中加入噪音,还是在噪音太大时忽略数据流。
继续尝试不同的过滤器值;也许您需要一个更低通滤波器。从 cmets 看来,您的低通滤波器不好;有大量的资源可以在网络上过滤掉。如果你有很好的可视化工具,那将是测试你的算法的最佳方式。
您可以尝试对数据进行下采样,这会使数据变得平滑。您可能还想丢弃超出有效范围的样本,方法是完全丢弃该值,将其替换为上一个和下一个样本的平均值,和/或将其限制在某个预定值或计算出的最大值。
我对这类应用程序最大的不满是 打嗝 被视为真实的实时数据。我健身房里的一辆自行车给出了无用的 bpm 读数,因为它经常找不到我的脉搏,突然觉得我的心跳频率为 300 bpm。 (事实并非如此;我已经问过我的医生了。)对于 20 分钟的疗程来说,它的平均值毫无用处。我认为查看(例如)最后十次正常节拍的平均值加上异常率比“我试图将最后 20 秒塞进这个算法,这是它吐出的垃圾”更有用。如果您可以创建一个单独的过滤器来指示异常,我认为您将拥有一个更有用的应用程序。
【讨论】:
【参考方案3】:我愿意:
1) 检测从峰到峰的周期...如果周期在某个周期阈值内一致..然后将HB标记为有效。
2) 我将实施异常检测算法...任何超过某个归一化阈值的节拍......将被视为异常值,因此我将使用最后检测到的节拍来计算我的 BPM。
另一种更复杂的方法是使用卡尔曼滤波器或类似的东西来预测 bpm 并获得更准确的读数。只有在跳过几次之后,应用程序才会感觉到读数无效并停止阅读。
【讨论】:
【参考方案4】:我做了一个项目,它使用GPUImage 滤镜、平均颜色和曝光来检测您的脉搏。脉冲是根据滤波后图像绿色分量的运行平均值来估计的。
Optical Pulse Reader
【讨论】:
【参考方案5】:听起来您可能已经有了另一种方法,但您可以尝试的一件事是使用一个小的median filter。例如,对每个输出值使用 3 到 7 个输入值的中值将平滑这些峰值,而不会破坏非异常数据的整体形状。
【讨论】:
【参考方案6】:您正在尝试“手动”检测单个心跳,这不会很可靠。我想说你最好的选择是一个音高或频率检测库(检测颜色变化频率和检测声音频率的数学必须相同)。
也许像aubio 之类的东西,我通过this *** 对搜索“频率检测”的回答可以帮助你。否则,请查看***以获取 pitch detection 和/或那里的大量信号处理库。
【讨论】:
【参考方案7】:首先解决您的手指在镜头上的问题。当手指放在镜头上时,您不会得到静态黑框(正如人们可能假设的那样)。环境光实际上穿过您的手指以创建一个微红色的框架。此外,手指中的血流模式会导致该红框出现轻微的周期性变化(尝试打开相机应用程序,将手指完全放在镜头上)。此外,如果环境光不足,您可以随时打开相机闪光灯/手电筒进行补偿。
对于开源(逐步)过程,请尝试:
http://www.ignaciomellado.es/blog/Measuring-heart-rate-with-a-smartphone-camera
另外,我建议您阅读以下有关脉冲测量的专利:
http://www.google.com/patents/WO2013042070A1?cl=en
【讨论】:
以上是关于iOS 心率检测算法的主要内容,如果未能解决你的问题,请参考以下文章
心电信号基于matlab心率检测含Matlab源码 1993期