我正试图在我正在开发的应用程序中实现心跳记录功能.

实现这一点的首选方法是使用iPhone的摄像头,打开灯,让用户将手指放在镜头上,并检测视频馈送中的波动,这与用户的心脏相对应.

我从下面的堆栈溢出问题中找到了一个很好的起点

这个问题提供了绘制心跳时间图的有用代码.

它显示了如何启动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

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

问题是数据并不总是像上面那样完美,有时在负读数中会出现异常正读数,反之亦然.

低通值的时间曲线图如下所示:

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

在我非常简单的节拍检测算法中,如果出现如上所示的异常,检测周期(10秒)内计数的节拍数可能会增加4或5次.这使得计算的BPM非常不准确.但是,尽管它很简单,但在大约70%的时间里,它确实有效.

为了解决这个问题,我try 了以下方法.

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

2.然后观察中间值前后是否有两个较小的值.(基本峰值检测)

3.将此场景计算为一个节拍,并将其添加到给定时间内的 run 总节拍中.

然而,这种方法和其他方法一样容易受到异常情况的影响.实际上,这似乎是一种更糟糕的方法.(在检测后播放实时蜂鸣音时,它们似乎比正转负算法更不稳定)

My question is can you help me come up with an algorithm that can reliably detect when a heart beat occurs with reasonable accuracy.

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

我曾想过检测不稳定的低通值,但问题是低通滤波器会考虑不稳定的值,并随着时间的推移进行平滑处理.因此,在那里的帮助也将被感激.

谢谢你抽出时间.

推荐答案

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

我使用的一种特殊类型的带通滤波器是巴特沃斯滤波器.由于过滤器会根据您收集数据的频率进行更改,因此手动创建有点麻烦.幸运的是,有一个网站可以帮助解决这个问题.如果以每秒30帧的速度采集数据,则频率为30赫兹.

我已经创建了一个项目,将所有这些都打包在一起,并能够很好地检测用户的心率,将其包含在iOS应用store 的我的应用程序中.我已经在github上提供了心率检测代码.

Objective-c相关问答推荐

如何使 UIView 动画序列重复和自动反转

Xcode 中架构的重复符号

在@implementation 而不是@interface 定义的类变量?

Objective-C 中的正式协议和非正式协议有什么区别?

当前时间是 HH:MM:SS am/pm 格式吗?

防止其他程序员调用 -init 的最佳方法

iOS 指定初始化器:使用 NS_DESIGNATED_INITIALIZER

如何在目标 C 中创建单例类

iOS:将 URL 解析为段

UIWebView 不会zoom 内容以适应

如何在 iPhone 上默认显示数字键盘?

以编程方式创建 UITableView

检测代码是否在 Objective-C 的主线程上运行的正确方法是什么? (iOS)

通过索引访问 NSMutableDictionary 中的对象

在 UICollectionView 上重置滚动

主队列上的 performSelectorOnMainThread: 和 dispatch_async() 有什么区别?

NS_ASSUME_NONNULL_BEGIN 宏

选中时选中的 UItableViewCell 保持蓝色

Highlighted 和 Selected UIButton 的 State 有什么区别?

UIPageViewController,如何在不打乱数据源指定顺序的情况下正确跳转到特定页面?