J'essaie d'implémenter une fonctionnalité d'enregistrement des battements de cœur dans une application que je développe.
La méthode privilégiée consiste à utiliser la caméra de l'iPhone avec la lumière allumée, à demander à l'utilisateur de placer son doigt sur l'objectif et à détecter les fluctuations du flux vidéo, qui correspondent au cœur de l'utilisateur.
J'ai trouvé un très bon point de départ avec la question suivante de stack overflow aquí
La question fournit un code utile pour tracer un graphique du temps de battement du cœur.
Il montre comment démarrer une AVCaptureSession et allumer la lumière de la caméra comme suit :
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 dans cet exemple doit être un <AVCaptureVideoDataOutputSampleBufferDelegate>
Et devra donc mettre en œuvre la méthode suivante pour obtenir les données brutes de la caméra :
- (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
}
Le RGB est converti en HSV et c'est la teinte qui est contrôlée pour les fluctuations.
Et la conversion de RGB en HSV est implémentée comme suit
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;
}
La valeur du passe-bas calculée dans capureOutput:
fournit d'abord des données erratiques, mais se stabilise ensuite comme suit :
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
Un exemple des données erratiques fournies initialement est ici :
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
La valeur passe-bas devient positive lorsqu'il y a un battement de cœur. J'ai donc essayé un algorithme de détection en direct très simple qui regarde la valeur actuelle, et voit si elle est positive, il regarde également la valeur précédente, si elle est négative, il détecte le passage du négatif au positif et émet un bip sonore.
Le problème, c'est que les données ne sont pas toujours aussi parfaites que celles présentées ci-dessus. Il arrive que des relevés positifs anormaux se trouvent parmi les relevés négatifs et vice versa.
Un graphique de la valeur du passe-bas dans le temps ressemble à ceci :
Il est intéressant de noter que l'anomalie ci-dessus est assez commune, si j'enregistre un graphique pendant un certain temps, je verrai une anomalie de forme très similaire plusieurs fois.
Dans mon algorithme très simple de détection des battements, si une anomalie comme celle illustrée ci-dessus se produit, le nombre de battements comptés dans la période de détection (10 secondes) peut augmenter de 4 ou 5 battements. Cela rend le calcul du BPM très imprécis. Mais aussi simple qu'il soit, cet algorithme fonctionne dans 70% des cas.
Pour combattre ce problème, j'ai essayé ce qui suit.
1.Démarré l'enregistrement des 3 dernières valeurs passe-bas dans un tableau
Ensuite, on regarde si la valeur centrale est entourée ou non de deux valeurs plus petites avant et après. (Détection de pic de base)
Il a compté ce scénario comme un temps et l'a ajouté au total des temps dans un temps donné.
Cette méthode est cependant tout aussi vulnérable aux anomalies que les autres. Et semble en fait être une méthode pire. (En jouant les bips en direct après la détection, ils semblaient beaucoup plus erratiques que l'algorithme positif à négatif).
Ma question est la suivante : pouvez-vous m'aider à trouver un algorithme capable de détecter de manière fiable le moment où un battement de cœur se produit avec une précision raisonnable ?
Un autre problème que je réalise et que je vais devoir résoudre est de détecter si le doigt de l'utilisateur est sur l'objectif ou non.
J'ai pensé à la détection de valeurs passe-bas erratiques, mais le problème est que le filtre passe-bas prend en compte les valeurs erratiques et les lisse dans le temps. Une aide dans ce domaine serait donc également appréciée.
Merci pour votre temps.