Sam*_*Sam 39 iphone algorithm camera objective-c ios
我正在尝试在我正在开发的应用程序中实现心跳录音功能.
这样做的首选方法是使用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];
Run Code Online (Sandbox Code Playgroud)
此示例中的Self必须是a <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
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
最初提供的不稳定数据的示例如下:
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
Run Code Online (Sandbox Code Playgroud)
只要有心跳,低通值就会变为正值.所以我尝试了一种非常简单的实时检测算法,该算法基本上是查看当前值,并查看它是否为正,它还会查看前一个值,如果为负,则检测到负值变为正值并发出哔声.
问题是数据并不总是如上所述,有时在负读数中有异常的正读数,反之亦然.
低通值的图表如下所示:

有趣的是,上面的异常很常见,如果我记录一段时间的图形,我会多次看到非常相似的形状异常.
在我非常简单的节拍检测算法中,如果出现如上所示的异常,则在检测周期(10秒)内计数的节拍数可以上升4或5拍.这使得计算的BPM非常不准确.但它很简单,它确实可以在70%的时间内工作.
为了解决这个问题,我尝试了以下方法.
1.开始记录阵列中的最后3个低通值
然后看看中间值是否在它之前和之后有两个较小的值.(基本峰值检测)
3.将此场景设置为节拍并将其添加到给定时间内的节拍总计中.
然而,这种方法与其他方法一样容易受到异常的影响.实际上似乎是一种更糟糕的方法.(在检测后播放现场嘟嘟声时,它们似乎比正面到负面的算法更不稳定)
我的问题是你可以帮我提出一种能够以合理的准确度可靠地检测出心跳的算法.
我意识到我将不得不解决的另一个问题是检测用户的手指是否在镜头上.
我考虑过检测不稳定的低通值,但问题是低通滤波器会导致不稳定的值并随着时间的推移平滑它们.所以帮助也会受到赞赏.
谢谢你的时间.
这个问题的答案有点复杂,因为您需要做几件事来处理信号,并且没有单一的“正确”方法可以做到这一点。但是,对于您的滤波器,您要使用带通滤波器. 这种类型的滤波器允许您指定在高端和低端均可接受的频率范围。对于人类的心跳,我们知道这些界限应该是多少(不低于 40 bpm 且不高于 250 bpm),因此我们可以创建一个过滤器来去除此范围之外的频率。过滤器还将数据移动到以零为中心,因此峰值检测变得更加容易。即使您的用户增加/减少他们的手指压力(在一定程度上),此过滤器也会为您提供更平滑的信号。之后,还需要进行额外的平滑和异常值去除。
我使用的一种特定类型的带通滤波器是 Butterworth 滤波器。这需要手动创建,因为过滤器会根据您收集数据的频率而变化。幸运的是,这里有一个网站可以帮助解决这个问题。如果您以 30 fps 的速度收集数据,则频率将为 30 hz。
我创建了一个项目,将所有这些都包装在一起,并能很好地检测用户的心率,以便将其包含在我在 iOS 应用商店中的应用中。我已经在github 上提供了心率检测代码。