Webcam pulsometer (032; 11.10.2012; image processing)
Artykuł przedstawiający koncepcję pomiaru tętna z użyciem kamery internetowej. Na początek wideo prezentujące działanie aplikacji:
Webcam pulsometer from MyInventions on Vimeo.
Zasada działania
Koncepcja pomiaru tętna oparta została na zasadzie działania pulsoksymetru medycznego. Za pomocą programu napisanego w środowisku Processing analizujemy obraz światła przechodzącego przez palec przyłożony do obiektywu. Dodatkową inspiracją do projektu była praca Eulerian Video Magnification (polecam wideo).
Pierwsze podejście
Rozpocznijmy od najprostszego kodu w Processingu, który wyświetli obraz z kamery w oknie. Tu ograniczymy się do obejrzenia tylko niebieskiej składowej obrazu (w testach niebieski kolor okazał się lepszy od czerwonego). Bardzo istotne będzie tu odpowiednie skonfigurowanie kamery internetowej - ustalenie stałej ekspozycji i balansu bieli. Do obsługi wideo w środowisku Processing użyłem biblioteki GSVideo zamiast standardowej Video (GSVideo nie wymaga instalacji QuickTime'a lub WinVDIG).
import codeanticode.gsvideo.*; GSCapture webcam; int pixNumber; color Kolor; int A, R, G, B; void setup() { size(640, 480); webcam = new GSCapture(this, 640, 480); webcam.start(); pixNumber = webcam.width*webcam.height; stroke(255, 0, 0); A = 255 << 24; } void draw() { if (webcam.available() == true) { webcam.read(); for (int i = 0; i < pixNumber; i++ ) { Kolor = webcam.pixels[i]; B = Kolor & 0xFF; R = 0; G = 0; webcam.pixels[i]=A|R|G|B; } set(0, 0, webcam); } }
Po przyłożeniu nieruchomo palca do obiektywu kamery (nie naciskamy) i odpowiednim ustawieniu oświetlenia zauważymy na ekranie regularne migotanie obrazu.
Drugie podejście
Przeprowadźmy teraz analizę obrazu aby automatycznie wydobyć z niego informację o zmianie obrazu. Zaproponuję prostą metodę zsumowania kolorów pewnej liczby pikseli równomiernie rozłożonych na obrazie. Zmiany jasności koloru będą zatem wpływać na ostateczną wartość owej sumy. Do wykrycia uderzenia serca najlepiej jest śledzić zmiany sumarycznego koloru między kolejnymi klatkami obrazu. A oto kod:
import codeanticode.gsvideo.*; GSCapture webcam; int pixNumber; color Kolor; int A, R, G, B, S, pS, D, block=0; float interrupt = 50.0; // wartość sumy kolorów której przekroczenie // traktujemy jako impuls float scal = 25.0; // skalowanie sumy kolorów pikseli float ms, pms; int ticks = 0; int dT = 10; int dTick = 15; // liczba uderzeń po których przeliczamy tętno void setup() { size(160, 350); webcam = new GSCapture(this, 160, 120); // wystarczy najmniejszy obraz z kamery webcam.start(); pixNumber = webcam.width*webcam.height; } void draw() { if (webcam.available() == true) { webcam.read(); for (int i = 0; i < pixNumber; i++) { Kolor = webcam.pixels[i]; A = 255 << 24; R = 0; G = 0; B = Kolor & 0xFF; webcam.pixels[i]=A|R|G|B; } background(100); set(0, 0, webcam); // wyświetlenie obrazu z kamery // sumujemy kolory 100 równomiernie rozłożonych pikseli S = 0; for (int i=0; i<liczbaPikseli; i+=pixNumber/100) { S += webcam.pixels[i] & 0xFF; } D = S-pS; // przyrost sumy pS = S; fill(0,0); stroke(0); rect(40, 330, 80, -200); // ramka wskaźnika fill(255,0,0); stroke(0,200,0); line(40,230-interrupt,120,230-interrupt); // poziom zliczany stroke(0); rect(40, 230, 80, (int)((float)D/scal)); // wartość sumy // detect pulses if ((-(float)D/scal) > interrupt && (block == 0)) { print("*"); ticks++; block = 3; // pozwala wyeliminować podwójne zliczanie jednego impulsu } if (block > 0) { block--; } // ostateczne obliczenie pulsu if (ticks == dTick) { ms = millis(); print(60.0*dTick*1000.0/(ms - pms)); pms = ms; ticks = 0; } } }
Co dalej?
Kolejnym etapem pracy może być oczywiście stworzenie ładnego interfejsu graficznego dla programu (tak jak zrobiłem to w filmie). Warto również spróbować uniezależnić funkcjonowanie programu od zmiennych warunków oświetlenia. Wyzwaniem może być próba wydobycia informacji o pulsie z obrazu nieruchomej osoby bez stosowania metody "prześwietlania".