├── Heart_Rate_Prediction.ipynb ├── README.md ├── video.mp4 └── yolov8n-face.pt /Heart_Rate_Prediction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "525d72bb", 6 | "metadata": {}, 7 | "source": [ 8 | "Gerekli kütüphaneleri yüklüyoruz." 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 1, 14 | "id": "38b9f808", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import cv2\n", 19 | "import numpy as np\n", 20 | "from scipy.signal import find_peaks\n", 21 | "from scipy.signal import butter, filtfilt" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 2, 27 | "id": "45d7cbfa", 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "from ultralytics import YOLO\n", 32 | "# Burada kullanacağımız modeli seçiyoruz. Bu model ile yüz tespiti yapacağız\n", 33 | "model= YOLO(\"yolov8n-face.pt\") " 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "id": "3c8730ba", 39 | "metadata": {}, 40 | "source": [ 41 | "Bandpass filtre tanımlıyoruz. Bu filtre ile belirli değerler arasındaki sinyalleri alıyoruz. Bu değer aralığının dışındaki sıfır yapıyor." 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 3, 47 | "id": "f8c97880", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "def temporal_bandpass_filter(data, fs, lowcut, highcut, order=1):\n", 52 | " \n", 53 | " nyq = 0.5 * fs\n", 54 | " low = lowcut / nyq\n", 55 | " high = highcut / nyq\n", 56 | " b, a = butter(order, [low, high], btype='band')\n", 57 | " y = filtfilt(b, a, data, axis=0)\n", 58 | " return y" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "id": "3583f3f3", 64 | "metadata": {}, 65 | "source": [ 66 | "Kodda kullanacağımız önemli fonksiyonlardan bir tanesi. Burada ilk önce resmi levels sayısı kadar boyut olarak yarıda birine düşürüyoruz. Örneğin levels=3 olursa 3 sefer yarıda bire düşürme işlemi uygularız. Böylece görüntü hem genişlik hem de yükseklik olarak 1/8 boyutuna ulaşacak. Ardından levels sayısı kadar resmi 2 katına çıkarıyoruz. Böylece resim orijinal boyutuna ulaşmış olacak. Fakat arada bir kayıp olacak. Bu kaybı alpha katsayısı ile çarpıp orijinal resme ekliyoruz." 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 4, 72 | "id": "429116b6", 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "def amplify_spatial_laplacian_pyramid(frame, levels=3, alpha=50):\n", 77 | " \n", 78 | " pyramid = frame.copy()\n", 79 | " for _ in range(levels):\n", 80 | " pyramid = cv2.pyrDown(pyramid)\n", 81 | " for _ in range(levels):\n", 82 | " pyramid = cv2.pyrUp(pyramid)\n", 83 | " laplacian = frame - pyramid\n", 84 | " amplified_frame = frame + alpha * laplacian\n", 85 | " return amplified_frame" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "id": "1cd5b8fe", 91 | "metadata": {}, 92 | "source": [ 93 | "Burada amaç tahmini olarak kalp atış hızını belirlemek. Fakat burada birçok bağlı değişken var. Bundan dolayı videodan videoya bu değerlerin kalibre edilmesi gerekebilir. Ayrıca buradaki sonuçları direk bir fiziksel cihaza bağlayıp ölçmediğim için sonuçların %100 doğru olduğunu söyleyemem. Fakat yaptığım hesaplar sonucunda bu video için buradaki değerlerin yüksek doğrulukla sonuç verdiklerini buldum. siz kendi videonuz için buradaki değerleri değiştirebilirsiniz." 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "id": "2ddd0eff", 99 | "metadata": {}, 100 | "source": [ 101 | "Kodun çalışma mantığı ise şöyle. İnsan yüzünde çok küçük renk değişimleri olur. Bunlar gözle görülmesi çok zor. Burada bu değişimleri yakalayıp bunları büyütüp ardından bunların ortalamasını alıp bir sinyal oluşturyoruz.Bu sinyalde zirveyi temsil eden noktalar genelde nabzın attığı yeri ifade diyor. Yani renk değişiminin en fazla olduğu noktalarda nabız atışı oluyor diyebiliriz. Ardından bu noktaları kullanarak frekanstan kalp atış hızını hesaplama yapıyoruz. " 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "id": "7bc60d04", 107 | "metadata": {}, 108 | "source": [ 109 | "Burada ilk önce belirli sayıda frame birikmesi gerekiyor. Ardından bunları işleme sokup nabzı tahmin ediyoruz. Ben bu tahmin işleminde kullanılacak frame sayısını 150 olarak belirledim. 150. frame'den sonra hesaplama başlıyor. Sonraki tahmin işlemini ise anında değil 30 frame sonra yapsın diye belirledim. Yani her 30 frame'de bir tahmin işlemi yaplıyor. Bu da kabaca 1 saniyeye denk geliyor. Ayrıca burada sadece yeşil kanalı kullandım. Mavi ve kırmızı renkteki kanalı kullanmadım. Okuduğum makalelerde sadece yeşil rengin kullanılmasının daha iyi sonuçlar verdiği söyleniyordu. Siz isterseniz diğer renkleri de kullanabilirsiniz" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "id": "cedfe8c8", 115 | "metadata": {}, 116 | "source": [ 117 | "Buradaki kod videoda tek kişinin olduğu videolar için ayarlanmıştır. Videoda birden fazla kişi varsa tracking işlemi uygulanıp buradaki adaımlar her bir yüz için tekrarlanmalı" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 5, 123 | "id": "66813987", 124 | "metadata": {}, 125 | "outputs": [], 126 | "source": [ 127 | "# Videoyu yüklüyoruz\n", 128 | "cap= cv2.VideoCapture('video.mp4')\n", 129 | "\n", 130 | "# Bu kodu yorumdan çıkarırsanız bilgisayarın kamerası kullanılır.\n", 131 | "#cap= cv2.VideoCapture(0)\n", 132 | "\n", 133 | "# Videonun FPS'sini alıyoruz.\n", 134 | "frame_rate = cap.get(cv2.CAP_PROP_FPS)\n", 135 | "\n", 136 | "#Yazı fontunu ayarlıyoruz.\n", 137 | "font = cv2.FONT_HERSHEY_SIMPLEX\n", 138 | "\n", 139 | "# Kalp atışı hızı\n", 140 | "heart_rate=0\n", 141 | "\n", 142 | "# Renk değşimlerinin ortalamasını bu listeye ekliyoruz.\n", 143 | "mean_values = []\n", 144 | "\n", 145 | "# Her 30 Frame'de bir tahmin yapıyoruz. Bunu kontrol etmek için kullanacağımız değişken\n", 146 | "buffer_count=0\n", 147 | "\n", 148 | "\n", 149 | "while True:\n", 150 | " # Görüntüyü alıyoruz videodan\n", 151 | " ret, frame = cap.read()\n", 152 | " \n", 153 | " # Bu değişkeni her frame'de bir arttıryoruz.\n", 154 | " buffer_count+=1\n", 155 | " \n", 156 | " # Kameradan görüntü gelmezse döngğden çıkar.\n", 157 | " if not ret:\n", 158 | " break\n", 159 | " \n", 160 | " # Resmi RGB uzayına çeviriyoruz.\n", 161 | " imgs=cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)\n", 162 | " \n", 163 | " # Resme blur uyguluyoruz, gürültüyü azaltmak için\n", 164 | " imgs = cv2.GaussianBlur(imgs, (5, 5), 0)\n", 165 | " \n", 166 | " # Resmi yüz tespiti yapabilen modele resmi verdik.\n", 167 | " results = model(imgs,verbose=False) \n", 168 | " \n", 169 | " # Modelin tespit edebildiği sınıflar. Buradaki sonuç sadace insan yüzü.\n", 170 | " labels=results[0].names\n", 171 | " \n", 172 | " # Her bir nesne yani yüz için bir for döngüsü çalışacak. Burada yüzün koordinatlarını buluyoruz.\n", 173 | " for i in range(len(results[0].boxes)):\n", 174 | " x1,y1,x2,y2=results[0].boxes.xyxy[i]\n", 175 | " score=results[0].boxes.conf[i]\n", 176 | " label=results[0].boxes.cls[i]\n", 177 | " x1,y1,x2,y2,score,label=int(x1),int(y1),int(x2),int(y2),float(score),int(label)\n", 178 | " \n", 179 | " # Yüzü mavi bir dikdörtgen içine alıyoruz.\n", 180 | " cv2.rectangle(frame,(x1,y1),(x2,y2),(255,0,0),10)\n", 181 | " \n", 182 | " \n", 183 | " # Burada yüzün olduğu olduğu kısmı görselden kırpacağız. Görseli 3 kere 2'de bir oranı küçültüp büyüteceğimiz \n", 184 | " # Yani 8 kat olduğu için resmin hem genişliği hem de hem de yüksekliği 8' tam bölünebilmeli. \n", 185 | " # Diğer türlü resim boyutu uyumsuz olacağı için hata verecek küçülüp büyürken\n", 186 | " # O yüzden iki uç noktadan çıkarıp genişlik ve yükseliklik tanımlarken bu değerlerden 8'e bölününce kalan \n", 187 | " # değerleri de çıakrıyoruz.\n", 188 | " h=y2-y1-((y2-y1)%8)\n", 189 | " w=x2-x1-((x2-x1)%8)\n", 190 | " \n", 191 | " # Görseli yüz kısmından kesitk. İsterseniz sadece alın bölgesini de kullanabilirsiniz.\n", 192 | " roi_color = frame[y1:y1+h, x1:x1+w]\n", 193 | " \n", 194 | " # Yüzü amplify fonksiyonuna veriyoruz.\n", 195 | " roi_color = amplify_spatial_laplacian_pyramid(roi_color, levels=3, alpha=200)\n", 196 | " \n", 197 | " # Resmin yeşil kanalını alıyoruz.\n", 198 | " green_channel = roi_color[:, :, 1]\n", 199 | " \n", 200 | " # Burada ortalama alıyoruz.\n", 201 | " mean_val = np.mean(green_channel)\n", 202 | " \n", 203 | " #Bunu da bu listeye ekliyoruz. Bu her bir frame için yapılıyor. Yani bu listedeki her bir değer\n", 204 | " # her bir frame'nin yeşil kanalındaki değişimin ortalaması\n", 205 | " mean_values.append(mean_val)\n", 206 | " \n", 207 | " # burada fonksiyondan gelen görüntüyü ekranda gösteriyoruz.\n", 208 | " cv2.imshow(\"roicolor\",roi_color)\n", 209 | " # Sadece tek bir yüz alsın diye koddan çıkıyoruz.\n", 210 | " break\n", 211 | " \n", 212 | " # Eğer biriken ortalama sayısı 150 olmuşsan ve son tahmin işleminden itibaren 30 frame geçmişse tahmin kısmına geçiyoruz.\n", 213 | " \n", 214 | " if len(mean_values)>149 and buffer_count>29:\n", 215 | " \n", 216 | " # Bu değişkeni her tahmin işleminde sıfırlıyoruz.\n", 217 | " buffer_count=0 \n", 218 | " \n", 219 | " # Numpy array'e çeviriyoruz\n", 220 | " mean_values_array = np.array(mean_values)\n", 221 | " \n", 222 | " # Her seferinde 150 değere baktığımız için ilk 30 değeri birikme olmasın diye siliyoruz\n", 223 | " del mean_values[:30]\n", 224 | " \n", 225 | " # Sinyale bandpass filtre uyfuluyoruz. Buradaki 0.8 48 nabzı 2 ise 120 nabzı temsil ediyor. \n", 226 | " # Bu değerleri tahmini en düşük ve en yüksek nabız değerleri\n", 227 | " filtered_signal = temporal_bandpass_filter(mean_values_array, frame_rate, lowcut=0.8, highcut=2)\n", 228 | " \n", 229 | " # Burada zirveleri buluyoruz. Buradaki 2.0 değeri zirveler arasındaki fark için ayarladığımız bir\n", 230 | " # Bu koddaki en önemli parametre. O yüzden bunu değiştirirken dikkatli değiştirin. Yoksa çok uçuk\n", 231 | " # değerler bulabilirsiniz.\n", 232 | " peaks, _ = find_peaks(filtered_signal, distance=frame_rate/2.0)\n", 233 | " \n", 234 | " # Zirve sayısı birden büyükse kod çalışacak\n", 235 | " if len(peaks) > 1:\n", 236 | " \n", 237 | " # burada zirveler arasındaki farkı bulup FPS'e bölüyoruz.\n", 238 | " intervals = np.diff(peaks) / frame_rate\n", 239 | " \n", 240 | " # Ardından bunların ortalamasını alıp 60'a bölüyoruz. \n", 241 | " # Çünkü kalp atış hızı olan BPM dakikadaki kalp atış hızını söyler\n", 242 | " heart_rate = 60 / np.mean(intervals)\n", 243 | " \n", 244 | " # Kalp atış hızı hesaplanmışsa 0'dan büyüktür. Diğer türlü default olan 0'dır\n", 245 | " if heart_rate>0:\n", 246 | " \n", 247 | " # Burada ekranda iyi gözüksün diye sol üst köşeyi mor renk yapıyoruz.\n", 248 | " frame[0:100,0:400]=(102,0,153)\n", 249 | " \n", 250 | " # Ekranın sol üst köşesine kalp atış hızını yazdırıyoruz.\n", 251 | " text='BPM:'+str(int(heart_rate))\n", 252 | " cv2.putText(frame, text,(30, 80), font, 3, (255,255,255), 3)\n", 253 | " \n", 254 | " cv2.imshow(\"kamera\",frame)\n", 255 | " if cv2.waitKey(1) & 0xFF == ord('q'):\n", 256 | " break\n", 257 | "cap.release()\n", 258 | "cv2.destroyAllWindows()" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": null, 264 | "id": "f0a332c1", 265 | "metadata": {}, 266 | "outputs": [], 267 | "source": [] 268 | } 269 | ], 270 | "metadata": { 271 | "kernelspec": { 272 | "display_name": "Python 3 (ipykernel)", 273 | "language": "python", 274 | "name": "python3" 275 | }, 276 | "language_info": { 277 | "codemirror_mode": { 278 | "name": "ipython", 279 | "version": 3 280 | }, 281 | "file_extension": ".py", 282 | "mimetype": "text/x-python", 283 | "name": "python", 284 | "nbconvert_exporter": "python", 285 | "pygments_lexer": "ipython3", 286 | "version": "3.9.13" 287 | } 288 | }, 289 | "nbformat": 4, 290 | "nbformat_minor": 5 291 | } 292 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Heart-Rate-Measurement-Python -------------------------------------------------------------------------------- /video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deepandreinforcement/Heart-Rate-Measurement-Python/54a9dfb570fe7b5618716a6656ed2450aac0629f/video.mp4 -------------------------------------------------------------------------------- /yolov8n-face.pt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deepandreinforcement/Heart-Rate-Measurement-Python/54a9dfb570fe7b5618716a6656ed2450aac0629f/yolov8n-face.pt --------------------------------------------------------------------------------