J'essaie d'implémenter la fonctionnalité d'enregistrement du rythme cardiaque dans une application que je développe.
La méthode préférée consiste à utiliser l'appareil photo de l'iPhone avec la lumière allumée, à ce que l'utilisateur place 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 de débordement de pile suivante ici
La question fournit un code utile pour tracer un graphique du temps de battement cardiaque.
Il montre comment démarrer une AVCaptureSession et allumer la lumière de la caméra comme ceci:
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];
Dans cet exemple, self doit être un <AVCaptureVideoDataOutputSampleBufferDelegate>
Et devra donc implémenter la méthode suivante pour obtenir des données brutes de 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
}
RGB est converti en HSV et c'est Hue qui est surveillé pour les fluctuations.
Et RGB à HSV est implémenté 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 passe-bas calculée en capureOutput:
fournit initialement 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 à chaque battement cardiaque. J'ai donc essayé un algorithme de détection en direct très simple qui regarde essentiellement la valeur actuelle, et voit si elle est positive, elle regarde également la valeur précédente, si négative, elle détecte que le négatif devient positif et émet un bip.
Le problème avec cela est que les données ne sont pas toujours aussi parfaites que ci-dessus, parfois il y a des lectures positives anormales parmi les lectures négatives et vice versa.
Un graphique de la valeur passe-bas dans le temps ressemble à ceci:
Fait intéressant, l'anomalie ci-dessus est assez courante, si j'enregistre un graphique pendant un certain temps, je verrai plusieurs fois une anomalie de forme très similaire.
Dans mon algorithme de détection de battement très simple, si une anomalie comme indiqué ci-dessus se produit, le nombre compté de battements dans la période de détection (10 secondes) peut augmenter de 4 ou 5 temps. Cela rend le BPM calculé très imprécis. Mais aussi simple que cela fonctionne, cela fonctionne environ 70% du temps.
Pour lutter contre ce problème, j'ai essayé ce qui suit.
1.Démarrage de l'enregistrement des 3 dernières valeurs passe-bas dans un tableau
Ensuite, on a cherché à savoir si la valeur moyenne avait ou non deux valeurs plus petites qui l'entouraient avant et après. (Détection de pointe de base)
3.Compte ce scénario comme un battement et l'ajoute au total cumulé des battements dans un temps donné.
Cette méthode est cependant tout aussi vulnérable aux anomalies que toute autre. Et en fait, cela semblait être une pire méthode. (Lors de la lecture de bips en direct après détection, ils semblaient beaucoup plus erratiques que l'algorithme positif à négatif)
Ma question est de savoir si vous pouvez m'aider à trouver un algorithme capable de détecter de manière fiable le rythme cardiaque avec une précision raisonnable.
Un autre problème que je me rends compte que je vais devoir résoudre est de détecter si oui ou non le doigt d'un utilisateur est sur l'objectif.
J'ai pensé à détecter des valeurs passe-bas erratiques, mais le problème est que le filtre passe-bas tient compte des valeurs erratiques et les adoucit au fil du temps. Donc, de l'aide serait également appréciée.
Merci pour votre temps.
La réponse à cette question est un peu compliquée, car vous devez faire plusieurs choses pour traiter le signal et il n'y a pas de "bonne" façon de le faire. Cependant, pour votre filtre, vous souhaitez utiliser un filtre passe-bande . Ce type de filtre vous permet de spécifier une plage de fréquences acceptées à la fois aux extrémités hautes et basses. Pour un battement de cœur humain, nous savons quelles devraient être ces limites (pas moins de 40 bpm et pas plus de 250 bpm) afin que nous puissions créer un filtre qui supprime les fréquences en dehors de cette plage. Le filtre déplace également les données à centrer à zéro, de sorte que la détection des pics devient beaucoup plus facile. Ce filtre vous donnera un signal beaucoup plus fluide, même si vos utilisateurs augmentent/diminuent la pression de leurs doigts (dans une certaine mesure). Après cela, un lissage supplémentaire et une suppression des valeurs aberrantes devront également se produire.
Un type spécifique de filtre passe-bande que j'ai utilisé est un filtre Butterworth. C'est un peu compliqué à créer à la main car le filtre change en fonction de la fréquence à laquelle vous collectez vos données. Heureusement, il existe un site Web qui peut vous y aider ici . Si vous collectez vos données à 30 ips, la fréquence sera de 30 Hz.
J'ai créé un projet qui englobe tout cela et détecte suffisamment la fréquence cardiaque d'un utilisateur pour l'inclure dans mon application sur l'App Store iOS. J'ai rendu le code de détection de fréquence cardiaque disponible sur github .
Je suppose que vous utilisez votre propre doigt. Êtes-vous sûr de ne pas avoir un rythme cardiaque irrégulier? De plus, vous allez vouloir gérer les personnes avec battements de cœur irréguliers. En d'autres termes, vous devriez tester avec un large variété de valeurs d'entrée. Essayez-le certainement avec vos parents ou d'autres parents plus âgés, car ils pourraient être plus susceptibles d'avoir des problèmes cardiaques. En dehors de cela, votre problème de base est que votre source d'entrée va être bruyante; vous essayez essentiellement de récupérer le signal de ce bruit. Parfois, cela sera impossible, et vous devrez décider si vous voulez faire du bruit dans votre rapport ou simplement ignorer le flux de données lorsqu'il est trop bruyant.
Continuez à essayer différentes valeurs de filtre; vous avez peut-être besoin d'un filtre passe-bas encore plus faible. 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 avez de bons outils de visualisation, ce sera le meilleur moyen de tester votre algorithme.
Vous pouvez essayer sous-échantillonner les données, ce qui les lissera. Vous pouvez également vouloir éliminer les échantillons qui se trouvent en dehors d'une plage valide, soit en supprimant complètement la valeur, en la remplaçant par la moyenne de l'échantillon précédent et suivant, et/ou en la fixant à une valeur prédéterminée ou maximum calculé.
Mon plus gros bœuf avec ce genre d'applications est que hoquet sont traités comme de vraies données en direct. Un des vélos de mon gymnase donne des lectures de bpm inutiles parce que, de temps en temps, il ne trouve pas mon pouls et pense soudain que mon cœur va à 300 bpm. (Ce qui n'est pas le cas; j'ai demandé à mon médecin.) Pour une séance de 20 minutes, la moyenne qu'il a est inutile. Je pense qu'il est plus utile de voir la moyenne des dix derniers battements normaux (par exemple) plus le taux d'anomalies plutôt que "J'ai essayé de mettre les 20 dernières secondes dans cet algorithme et voici les ordures qu'il a crachées". Si vous pouvez créer un filtre séparé qui indique des anomalies, je pense que vous aurez une application beaucoup plus utile.
Je voudrais :
1) Détecter la période de pic à pic ... Si la période est cohérente à l'intérieur d'un certain seuil de période .. Ensuite, marquez le HB comme valide.
2) Je mettrais en œuvre un algorithme de détection outliar ... Tout battement qui dépasse un certain seuil normalisé .. Serait considéré comme une valeur aberrante et donc j'utiliserais le dernier battement détecté à la place pour calculer mon BPM.
Une autre approche plus complexe serait d'utiliser un filtre de Kalman ou quelque chose de ce 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 sentira que les lectures ne sont pas valides et arrêtera le en train de lire.
Il semble que vous ayez peut-être déjà une autre méthode, mais une chose que vous pouvez essayer est 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 lissera ces pics sans détruire la forme globale des données non anomylées.
Vous essayez de détecter un seul battement de cœur "manuellement", ce 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 calculs 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 que quelque chose comme aubio que j'ai trouvé via this stackoverflow réponse à la recherche "détection de fréquence" peut vous aider. Sinon, consultez wikipedia pour détection de hauteur et/ou certaines des tonnes de bibliothèques de traitement du signal.
Commencez par résoudre votre problème de doigt sur l'objectif. Lorsque le doigt est sur l'objectif, vous n'obtenez pas de cadre noir statique (comme on pourrait le supposer). La lumière ambiante passe réellement à travers votre doigt pour créer un cadre rougeâtre. En outre, le modèle de flux sanguin dans le doigt entraîne de légères variations périodiques dans ce cadre rouge (essayez d'ouvrir votre application d'appareil photo, en plaçant complètement votre doigt sur l'objectif). De plus, si la lumière ambiante n'est pas suffisante, vous pouvez toujours allumer le flash/la torche de l'appareil photo pour compenser cela.
Pour une procédure open source (et étape par étape), essayez:
http://www.ignaciomellado.es/blog/Measuring-heart-rate-with-a-smartphone-camera
aussi, je vous conseille de lire le brevet suivant sur la mesure du pouls:
J'ai fait un projet qui utilise GPUImage filtres, couleur moyenne et exposition, pour détecter votre Pulse. L'impulsion est estimée sur la base de la moyenne mobile de la composante verte de l'image filtrée.