開発中のアプリに心拍記録機能を実装しようとしています。
これを実行するための推奨される方法は、ライトを点灯した状態で 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 は And である必要がある<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
ローパス値は、心拍があるときはいつでも正になります。そこで、基本的に現在の値を見て、それが正かどうかを確認し、また前の値を見て、負の場合は負から正に変わることを検出し、ビープ音を鳴らすという、非常に単純なライブ検出アルゴリズムを試しました。
これに関する問題は、データが常に上記のように完璧であるとは限らず、時には負の値の中に異常な正の値があったり、その逆があったりすることです。
時間に対するローパス値のグラフは次のようになります。
興味深いことに、上記の異常は非常に一般的であり、しばらくグラフを記録すると、非常によく似た形状の異常が複数回表示されます。
私の非常にシンプルなビート検出アルゴリズムでは、上記のような異常が発生すると、検出期間 (10 秒) 内のビートのカウント数が 4 または 5 ビート増加することがあります。これにより、計算された BPM が非常に不正確になります。しかし、シンプルであるにもかかわらず、約 70% の確率で機能します。
この問題に対処するために、私は次のことを試しました。
1.最後の3つのローパス値を配列に記録し始めました
2. 次に、中央の値の前後に 2 つの小さな値があるかどうかを確認します。(基本的なピーク検出)
3. このシナリオをビートとしてカウントし、指定された時間内のビートの合計に追加しました。
ただし、この方法は他の方法と同様に異常に対して脆弱です。実際、より悪い方法であるように思われます。(検出後にライブビープ音を再生すると、正から負へのアルゴリズムよりもはるかに不安定に見えました)
私の質問は、心拍がいつ発生したかを適度な精度で確実に検出できるアルゴリズムの開発を手伝っていただけるかどうかです。
私が対処しなければならないもう 1 つの問題は、ユーザーの指がレンズ上にあるかどうかを検出することです。
不規則なローパス値を検出することを考えましたが、問題はローパス フィルターが不規則な値を考慮し、時間の経過とともに平滑化することです。そのため、この点についてもご協力いただければ幸いです。
御時間ありがとうございます。
ベストアンサー1
この質問への答えは少し複雑です。信号を処理するにはいくつかのことを行う必要があり、これを行うための単一の「正しい」方法はありません。ただし、フィルターにはバンドパスフィルタこのタイプのフィルターを使用すると、上限と下限の両方で受け入れられる周波数の範囲を指定できます。人間の心拍の場合、これらの境界が何であるかはわかっています (40 bpm 以上 250 bpm 以下)。そのため、この範囲外の周波数を削除するフィルターを作成できます。フィルターはデータをゼロを中心に移動させるため、ピーク検出がはるかに簡単になります。このフィルターを使用すると、ユーザーが指の圧力を増減した場合でも (ある程度)、はるかにスムーズな信号が得られます。その後、追加のスムージングと外れ値の削除も行う必要があります。
私が使用したバンドパスフィルタの具体的なタイプはバターワースフィルタです。これは、データを収集する周波数に基づいてフィルタが変化するため、手動で作成するには少し複雑です。幸いなことに、これに役立つWebサイトがあります。ここ30 fps でデータを収集している場合、周波数は 30 Hz になります。
私はこれらすべてをまとめて、iOSアプリストアのアプリに組み込めるほどユーザーの心拍数を正確に検出するプロジェクトを作成しました。心拍数検出コードは以下で公開しています。ギットハブ。