Русский
Русский
English
Статистика
Реклама

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

Привет Хабр.

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

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

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

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

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

import cv2import ioimport timecap = 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'):        breakcap.release()cv2.destroyAllWindows()

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

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

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

Обработка

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

heartbeat_count = 128heartbeat_values = [0]*heartbeat_countheartbeat_times = [time.time()]*heartbeat_countwhile 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.

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

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

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

Заключение

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

И раз уж мы анализируем видеопоток, может возникнуть отдельный вопрос - работает ли это со сжатыми данными, можно ли увидеть пульс у актера кино или диктора на телевидении? Ответа я не знаю, желающие могут попробовать самостоятельно.

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

Spoiler
import numpy as npfrom matplotlib import pyplot as pltimport cv2import ioimport timecap = 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)# Image cropx, y, w, h = 800, 500, 100, 100heartbeat_count = 128heartbeat_values = [0]*heartbeat_countheartbeat_times = [time.time()]*heartbeat_count# Matplotlib graph surfacefig = 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'):        breakcap.release()cv2.destroyAllWindows()

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

Источник: habr.com
К списку статей
Опубликовано: 01.09.2020 22:17:56
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Python

Алгоритмы

Лайфхаки для гиков

Научно-популярное

Программирование

Пульсометр

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru