39 votes

iOS Algorithme de détection de la fréquence cardiaque

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 : enter image description here

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.

1voto

AndrewS Points 3208

Je présume que vous utilisez votre propre doigt. Vous êtes sûr que vous n'avez pas un rythme cardiaque irrégulier ? De plus, vous allez veulent traiter les personnes avec des battements cardiaques irréguliers. En d'autres termes, vous devez tester avec une grande variété de valeurs d'entrée. Essayez-le sans hésiter sur vos parents ou d'autres personnes âgées, car ils sont plus susceptibles d'avoir des problèmes cardiaques. En dehors de cela, votre problème de base est que votre source d'entrée sera bruyante ; vous essayez essentiellement de récupérer le signal à partir de ce bruit. Parfois, cela sera impossible, et vous devrez décider si vous voulez intégrer le bruit dans votre rapport ou simplement ignorer le flux de données lorsqu'il est trop bruyant.

Continuez à essayer différentes valeurs de filtre Peut-être avez-vous besoin d'un filtre passe-bas encore plus bas. D'après les commentaires, il semble que votre filtre passe-bas n'était pas bon ; il y a des tonnes de ressources sur le filtrage sur le web. Si vous disposez de bons outils de visualisation, ce sera le meilleur moyen de tester votre algorithme.

Vous pouvez essayer sous-échantillonnage des données ce qui le rendra plus facile. Vous pouvez également jeter les échantillons qui se trouvent en dehors d'une plage valide, soit en rejetant complètement la valeur, en la remplaçant par la moyenne de l'échantillon précédent et de l'échantillon suivant, et/ou en la fixant à un maximum prédéterminé ou calculé.

Mon plus gros problème avec ce genre d'applications est que hoquet sont traitées comme des données réelles, en direct. L'un des vélos de ma salle de sport donne des indications inutiles sur les bpm parce que, de temps en temps, il ne trouve pas mon pouls et pense soudain que mon cœur bat à 300 bpm (ce qui est le cas, j'ai demandé à mon médecin). (Ce qui n'est pas le cas, j'ai demandé à mon médecin.) Pour une séance de 20 minutes, la moyenne qu'il donne est inutile. Je pense qu'il est plus utile de voir la moyenne des (par exemple) dix derniers battements normaux plus le taux d'anomalie plutôt que "j'ai essayé de faire entrer les 20 dernières secondes dans cet algorithme et voici les déchets qu'il a crachés". Si vous pouvez créer un filtre séparé qui indique les anomalies, je pense que vous aurez une application beaucoup plus utile.

1voto

Kiko Lobo Points 86

Je le ferais :

1) Détecter la période de pic à pic... Si la période est cohérente dans un certain seuil de période... Alors marquez le HB comme valide.

2) J'implémenterais un algorithme de détection des outliars... Tout battement qui dépasse un certain seuil normalisé... Serait considéré comme une aberration et donc j'utiliserais le dernier temps détecté à la place pour calculer mon BPM.

Une autre approche plus complexe serait d'utiliser un filtre Kalman ou quelque chose du genre pour pouvoir prédire le bpm et obtenir des lectures plus précises . Ce n'est qu'après quelques sauts que l'application détectera que les lectures ne sont pas valides et arrêtera la lecture.

0voto

user1118321 Points 10828

Il semble que vous ayez déjà une autre méthode, mais vous pourriez essayer d'utiliser un petit filtre médian . Par exemple, l'utilisation de la médiane de, disons, 3 à 7 valeurs d'entrée pour chaque valeur de sortie permettra de lisser ces pics sans détruire la forme globale des données non anomiques.

0voto

HBu Points 181

Vous essayez de détecter un seul battement de cœur "manuellement", ce qui ne sera pas très robuste. Je dirais que votre meilleur pari est quelque chose comme une bibliothèque de détection de hauteur ou de fréquence (les mathématiques pour détecter la fréquence d'un changement de couleur et pour détecter la fréquence d'un son doivent être identiques).

Peut-être quelque chose comme aubio que j'ai trouvé via este La réponse de stackoverflow à la recherche "frequency detection" peut vous aider. Sinon, consultez wikipedia pour détection du pas et/ou certaines des tonnes de bibliothèques de traitement du signal qui existent.

0voto

Ameer Sheikh Points 181

Aborder d'abord le problème du doigt sur l'objectif. Lorsque le doigt est sur l'objectif, vous n'obtenez pas un cadre noir statique (comme on pourrait le supposer). La lumière ambiante passe en fait à travers votre doigt pour créer un cadre rougeâtre. De plus, le schéma de circulation sanguine dans le doigt entraîne de légères variations périodiques dans ce cadre rouge (essayez d'ouvrir votre application appareil photo et de placer votre doigt complètement sur l'objectif). Par ailleurs, si la lumière ambiante n'est pas suffisante, vous pouvez toujours activer le flash de l'appareil photo pour compenser.

Pour une procédure open source (et pas à pas), essayez :

http://www.ignaciomellado.es/blog/Measuring-heart-rate-with-a-smartphone-camera

Je vous conseille également de lire le brevet suivant sur la mesure du pouls :

http://www.google.com/patents/WO2013042070A1?cl=en

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X