Определяем пульс по вебкамере в 50 строчек кода

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


Привет, Хабр.

Однажды мне попалось описание приложения для Android, которое определяло пульс по камере телефона, просто по общей картинке. Камера не прикладывалась к пальцу, не просвечивалась светодиодом. Интересный момент был в том, что ревьюеры не поверили в возможность такого определения пульса, и приложение было отклонено. Чем дело кончилось у автора программы, не знаю, но стало интересно проверить, возможно ли это.

Для тех кому интересно что получилось, продолжение под катом.

Разумеется, я не буду делать приложение под Android, гораздо проще проверить идею на языке Python.

Получаем данные с камеры

Сначала мы должны получить поток с вебкамеры, для чего воспользуемся OpenCV. Код является кроссплатформенным, и может работать как под Windows, так и под Linux/OSX.

import cv2 import io import time   cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080) cap.set(cv2.CAP_PROP_FPS, 30)  while(True):     ret, frame = cap.read()      # Our operations on the frame come here     img = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)      # Display the frame     cv2.imshow('Crop', crop_img)     if cv2.waitKey(1) & 0xFF == ord('q'):         break  cap.release() cv2.destroyAllWindows()

Идея определения пульса состоит в том, что оттенок кожи слабо меняется из-за протекания крови в сосудах, поэтому нам понадобится кроп картинки, на котором будет только фрагмент кожи.

x, y, w, h = 800, 500, 100, 100 crop_img = img[y:y + h, x:x + w]  cv2.imshow('Crop', crop_img)

Если все было сделано правильно, при запуске программы мы должны получить примерно такую картинку с камеры (заблюрено из соображений приватности) и кропа:

Обработка

После того, как у нас есть поток с камеры, все довольно просто. Для выбранного фрагмента мы получаем усредненное значение цвета и добавляем его в массив вместе со временем измерения.

heartbeat_count = 128 heartbeat_values = [0]*heartbeat_count heartbeat_times = [time.time()]*heartbeat_count  while True:     ...     # Update the list     heartbeat_values = heartbeat_values[1:] + [np.average(crop_img)]     heartbeat_times = heartbeat_times[1:] + [time.time()] 

Функция numpy.average вычисляет среднее из двухмерного массива, на выходе мы получаем число, которое и является усредненной яркостью.

Остается вывести график на экран в реальном времени:

fig = plt.figure() ax = fig.add_subplot(111) while(True):     ...      ax.plot(heartbeat_times, heartbeat_values)     fig.canvas.draw()     plot_img_np = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')     plot_img_np = plot_img_np.reshape(fig.canvas.get_width_height()[::-1] + (3,))     plt.cla()          cv2.imshow('Graph', plot_img_np)

Тут есть небольшая тонкость: OpenCV работает с изображениями в формате numpy, поэтому мы должны получить из matplotlib график в виде массива, для чего используется функция numpy.fromstring.

Собственно и все.

Запускаем программу, подбираем такое положение, чтобы в кропе с камеры был только фрагмент кожи, принимаем "позу мыслителя", подперев голову рукой - изображение должно быть максимально неподвижно. И вуаля - это действительно работает!

Возможно, из заголовка не совсем очевидно, но камера не прикладывается к коже, мы просто анализируем общую картинку с человеком на экране. И удивительно, что даже на таком расстоянии изменение оттенка кожи вполне уверенно фиксируется камерой! Как видно из картинки, реальная разница яркости составляет менее 0.5% и конечно, не видна "невооруженным глазом", но на графике уверенно различима. Разумеется, по клеточкам считать не точно, примерный пульс получился около 75bpm. Для сравнения, результат с поверенного китайскими мастерами пульсоксиметра:

В комментариях был вопрос, меняется ли измеренный камерой пульс в зависимости от физ.нагрузки. Да, меняется, пример наложения двух графиков до и после зарядки приведен ниже в комментариях.

Заключение

Как ни странно, но это действительно работает. Если честно, в результате я был не уверен. Разумеется, для реального использования нужно сначала найти лицо на изображении, но встроенная функция поиска лиц в OpenCV также есть. И конечно, нужна несложная математика для выделения периода из достаточно шумных данных.

И раз уж мы анализируем видеопоток, может возникнуть отдельный вопрос - работает ли это со сжатыми данными, можно ли увидеть пульс у актера кино или диктора на телевидении? Ответа я не знаю, желающие могут попробовать самостоятельно. Для этого достаточно заменить в коде строку cap = cv2.VideoCapture(0) на cap = cv2.VideoCapture("video.mp4"), код программы остается тот же.

Для желающих поэкспериментировать, исходный код целиком под спойлером.

Spoiler

import numpy as np from matplotlib import pyplot as plt import cv2 import io import time  # Camera stream cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1280) cap.set(cv2.CAP_PROP_FPS, 30) # Video stream (optional) # cap = cv2.VideoCapture("videoplayback.mp4")  # Image crop x, y, w, h = 800, 500, 100, 100 heartbeat_count = 128 heartbeat_values = [0]*heartbeat_count heartbeat_times = [time.time()]*heartbeat_count  # Matplotlib graph surface fig = plt.figure() ax = fig.add_subplot(111)  while(True):     # Capture frame-by-frame     ret, frame = cap.read()      # Our operations on the frame come here     img = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)     crop_img = img[y:y + h, x:x + w]      # Update the data     heartbeat_values = heartbeat_values[1:] + [np.average(crop_img)]     heartbeat_times = heartbeat_times[1:] + [time.time()]      # Draw matplotlib graph to numpy array     ax.plot(heartbeat_times, heartbeat_values)     fig.canvas.draw()     plot_img_np = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')     plot_img_np = plot_img_np.reshape(fig.canvas.get_width_height()[::-1] + (3,))     plt.cla()      # Display the frames     cv2.imshow('Crop', crop_img)     cv2.imshow('Graph', plot_img_np)     if cv2.waitKey(1) & 0xFF == ord('q'):         break  cap.release() cv2.destroyAllWindows()

И как обычно, всем удачных экспериментов


Источник: habr.com

Комментарии: