
Зачем это понадобилось
Я разрабатываю спортивный гаджет, и он умеет принимать пульс со стандартных BLE-датчиков с профилем Heart Rate. Датчиков у меня было три: наручные часы Galaxy Watch 7, которые снимают пульс оптикой на запястье, и два нагрудных ремня, Garmin HRM и Polar H9, меряющие пульс электрической активностью сердца. Ставку я делал на часы: очень не хотелось всякий раз надевать ремень на грудь, если можно обойтись тем, что и так на руке. Вопрос был один: можно ли часам доверять, то есть показывают ли все три одно и то же?
Проверить это оказалось нечем. Спортивные часы, телефонные приложения, велокомпьютеры: все они подключаются к одному датчику за раз. Положить пульс с трех датчиков рядом, секунда в секунду, на одном графике, стандартными средствами не выходит. А без такой картинки любые рассуждения о том, какой датчик точнее, остаются гаданием.
Так и появился этот проект: телефонное приложение, которое держит несколько датчиков пульса одновременно и рисует их на общем графике. Изначально я всего лишь хотел понять, можно ли доверять часам вместо ремня. Получился инструмент, который заодно повышает надежность измерения (когда датчиков несколько, сбой одного сразу виден на фоне остальных) и наглядно показывает, чего стоит каждый сенсор.
Я люблю метрики. По двум причинам. Во-первых, они геймифицируют тренировку: когда есть что мерить и с чем сравнивать, монотонная работа превращается в соревнование с собственными рекордами, и заниматься становится интереснее. Во-вторых, метрика дает чувство контроля: пока я вижу число и понимаю, откуда оно берется, тело перестает быть черным ящиком. Так этот маленький инструмент для сравнения датчиков у меня и оброс зонами, статистикой, вариабельностью и скоростью восстановления пульса. Каждая новая метрика это еще один понятный рычаг.
Проверяем нагрудные пульсометры
Надел оба ремня одновременно. Garmin реагирует заметно быстрее. На резком ускорении его линия уходит вверх раньше, на восстановлении быстрее падает.Линия Garmin чуть более дерганая, с мелкими зубцами. Polar H9 идет глаже и спокойнее, но за скачками пульса тянется с небольшим запаздыванием, как будто подсглаживает.
Почему Polar отстает: прикидываем его фильтр
Раз уж линии легли рядом, захотелось докопаться: а какой именно фильтр внутри Polar дает эту гладкость и запаздывание? Точной формулы производитель не публикует, но прикинуть можно прямо из графиков. Я принял линию Garmin за более сырую (она зубчатее и реагирует раньше), а линию Polar за ее сглаженную и задержанную версию, и подобрал самый простой фильтр низких частот первого порядка, который превращает одно в другое.
Получилось, что Polar ведет себя как Garmin, пропущенный через фильтр с постоянной времени около 4 секунд. Частота среза выходит примерно 0.04 герца: все, что быстрее периода в 25 секунд, прибор приглаживает. Отсюда и видимое запаздывание на пару-тройку секунд.
Тогда я взял зубчатый сигнал Garmin, прогнал через этот самый фильтр, и результат почти идеально лег на реальную линию Polar: расхождение около 0.4 удара, это уже на уровне округления до целого. На графике крупным планом видно все три линии сразу: оранжевый сырой Garmin бежит впереди с мелкими зубцами, а голубой Polar и тонкая красная линия отфильтрованного Garmin идут вместе, гладко и с небольшим отставанием.
Но мы видим только то, что приборы отдают наружу, а не сырой сигнал с электродов. Garmin ведь тоже что-то фильтрует, так что настоящая постоянная времени у Polar еще побольше. И первый порядок это упрощение: такое же сглаживание дало бы и скользящее среднее секунд на восемь.
Отсюда вывод: Polar показывает пульс спокойнее, чем он есть на самом деле. Его ровная линия недоговаривает, насколько сердце дергается на коротких масштабах, и вариабельность глазами недооцениваешь. Garmin в этом честнее, он прячет меньше, хотя часть его зубцов это уже не живой сигнал, а измерительный шум.
Пульсовые зоны и что они значат
За графиком приложение рисует пять полупрозрачных полос: это пульсовые зоны. Границы считаются от максимального пульса (его задаешь в настройках) и идут по 50, 60, 70, 80 и 90 процентах от максимума. Смысл зон в том, что одна и та же тренировка на разном пульсе дает разный эффект:
-
Разминка (50-60% от максимума). Совсем легкая нагрузка. Сердце просыпается, разгоняется кровоток, организм готовится к работе.
-
Сжигание жира (60-70%). Спокойный устойчивый темп, при котором тело охотнее берет энергию из жиров. Здесь проходят долгие восстановительные тренировки.
-
Аэробная (70-80%). Рабочая зона выносливости. Дышится еще ровно, кислорода хватает, растет производительность сердечно-сосудистой системы. В этой зоне я делаю силовые подходы.
-
Анаэробная (80-90%). Тяжело. Кислорода уже не хватает, копится лактат, тренируется способность держать высокий темп. Это моя любимая зона. В ней я провожу больше всего времени.
-
Максимум (90-100%). Красная зона, короткие рывки на пределе. Долго тут не пробыть.
Полосы за графиком сразу показывают, в какой зоне ты сейчас и сколько времени в ней провел. Не надо держать в голове числа: линия пульса поднялась в оранжевую полосу, значит, идет анаэробная работа.
Какую статистику ведет приложение
Пока идет тренировка (кнопка Start фиксирует ее начало), приложение копит цифры и по запросу показывает сводку. По каждому источнику отдельно:
-
минимальный, средний и максимальный пульс за тренировку;
-
число принятых отсчетов (грубая оценка того, насколько стабильно датчик был на связи);
-
время, проведенное в каждой из пяти пульсовых зон.
Плюс общее: время старта и длительность тренировки. Разбивка по зонам полезнее среднего пульса: два занятия с одинаковым средним могут быть совершенно разными, если одно прошло ровно в аэробной зоне, а другое прыгало между разминкой и максимумом.
А поверх графика крупно выводится average: текущий пульс, усредненный по тем источникам, у которых данные свежие. Отсчеты старше 10 секунд из усреднения выпадают, так что отвалившийся или заглючивший датчик итог не портит. Это и есть надежное число: когда сенсоров несколько, среднее по живым устойчивее показаний любого одного.
HRV и диаграмма Пуанкаре: зачем они
Хорошие ремни передают не только пульс, но и RR-интервалы: точные промежутки времени между ударами сердца, в миллисекундах. Из них вырастает целый отдельный экран про вариабельность сердечного ритма (HRV).
Идея вариабельности простая. Сердце не тикает как метроном: даже при ровном пульсе 60 промежутки между ударами чуть разные, то 980 мс, то 1020 мс. Этот разброс и есть HRV. Чем он больше (в покое), тем, как правило, лучше восстановление и тонус вегетативной нервной системы; падение вариабельности часто говорит об усталости или стрессе.
На этом экране приложение показывает четыре параметра: RMSSD, SDNN, SD1 и SD2. RMSSD и SDNN считаются прямо по ряду RR-интервалов. SD1 и SD2 относятся к диаграмме Пуанкаре: это не новое измерение, а описание формы того же облака точек — его ширины поперек диагонали и длины вдоль нее.
Пусть RR[1]...RR[N] — интервалы между ударами, d[i] = RR[i+1] - RR[i], mean() — среднее, а std() — стандартное отклонение по выбранному окну. Тогда формулы такие:
-
RMSSD = sqrt(mean(d[i]^2)): средний размер шага от одного удара к следующему. Это короткая beat-to-beat вариабельность: быстрые межударные колебания, которые обычно связывают с парасимпатическим влиянием и восстановлением. -
SDNN = std(RR[i]): общий разброс всех RR-интервалов вокруг среднего. Он чувствует не только быстрые колебания, но и медленный дрейф, смену нагрузки и редкие крупные хвосты. -
SD1 = std(d[i]) / sqrt(2): ширина облака Пуанкаре поперек диагонали. Для аккуратного ряда SD1 близок кRMSSD / sqrt(2), поэтому тоже описывает краткосрочную вариабельность. -
SD2 = std((RR[i] + RR[i+1]) / sqrt(2)): длина облака Пуанкаре вдоль диагонали. Это более общий и медленный разброс: чем он больше, тем сильнее облако вытягивается вдоль линииRR(n) = RR(n+1).
Отсюда важная практическая штука: близкий RMSSD говорит только о похожей краткосрочной вариабельности. При этом SDNN и SD2 могут быть выше, если у сигнала похожее основное облако, но больше редких хвостов, выбросов или медленного дрейфа. RMSSD и SDNN приложение считает и по всей записи, и по скользящему окну, так что видно не только итог, но и как вариабельность плавала по ходу тренировки.
С этими числами легче читать саму диаграмму Пуанкаре. На ней каждый интервал откладывается против следующего: по горизонтали RR(n), по вертикали RR(n+1). Получается облако точек, и по его форме о датчике и о сердце понятно сразу многое:
-
плотное компактное облако вдоль диагонали: ровный ритм и чистый сигнал;
-
вытянутое сигарообразное облако: нормальная здоровая вариабельность;
-
отдельные точки, улетевшие в стороны: пропущенные или лишние удары, помехи, экстрасистолы.
Приложение рисует облака всех RR-источников сразу, каждый своим цветом, а нефизиологичные выбросы отфильтровывает. И здесь качество датчика видно даже лучше, чем на обычном графике: у аккуратного ремня облако собранное и чистое, у шумящего разлетается брызгами. Именно на Пуанкаре разница в характере сигнала между моими двумя ремнями бросается в глаза сильнее всего. Каждый источник в приложении гасится отдельным переключателем, так что облака можно разглядеть и поодиночке:
Здесь же удобно проверить подозрение, которое напрашивается после разговора про фильтр: не режет ли Polar вместе с шумом и саму вариабельность? По RMSSD этого не видно: у ремней он почти совпадает (у Polar 64 мс, у Garmin 67), значит быстрые межударные колебания Polar в этом фрагменте не теряет. А вот SDNN у Garmin выше, и это как раз совпадает с картинкой: у него больше редких крупных скачков, поэтому облако шире и хвостатее. Так что точнее сказать так: Polar, похоже, агрессивнее чистит RR-выбросы, но основную краткосрочную HRV по RMSSD не съедает.
Заодно приложение прямо здесь прикидывает характер ритма и красит вывод цветом. Логика та же, что у манжетных тонометров с функцией AFib: смотрит не на средний пульс, а на беспорядочность интервалов. В дело идут коэффициент вариации (насколько интервалы разбросаны относительно среднего) и доля ударов, скачущих больше чем на 10% от соседних. Зеленое regular: ритм ровный. Оранжевое irregular beats: заметная нерегулярность, обычно экстрасистолы или помехи (коэффициент вариации выше 6% либо больше 12% скачущих ударов). Красное possible AFib: устойчивая беспорядочность, какой она бывает при мерцательной аритмии (коэффициент выше 12% и при этом больше 30% скачущих ударов сразу). Пока ударов меньше тридцати, вердикта нет, висит серое collecting. И да, это грубый скрининг, а не диагноз: мерцательную аритмию ставят по ЭКГ, а не по нагрудному ремню.
Работает это и вживую, и на загруженной записи: тренировку можно выгрузить в CSV, а потом открыть обратно и спокойно разобрать облака и метрики.
Три датчика вместо двух
Два нагрудных ремня попадают на график сами, они и так вещают пульс по стандарту BLE. А часы, на которые я и делал ставку, свой пульс наружу так просто не отдают, он оседает внутри Samsung Health. Чтобы поставить часы на тот же график, что и ремни, я научил их прикидываться обычным ремнем: отдавать оптический пульс с запястья по тому же профилю Heart Rate. Для приложения это просто еще один датчик в списке сканирования.
Так на одном графике сходятся сразу три разных способа померить пульс: два нагрудных ремня и оптика на запястье. И тут уже совсем наглядно видно, как оптический датчик отстает и заглаживает резкие фронты по сравнению с электрическими ремнями. Но инерция это еще полбеды: с часами есть история поинтереснее, и к ней я вернусь в самом конце.
Скорость восстановления пульса, и почему тут нужны все датчики сразу
Когда на графике сошлись сразу три линии, я добавил еще одну вещь, которую давно хотел видеть своими глазами: как быстро падает пульс после нагрузки.
Метрика простая и на удивление показательная. Берем пик пульса и смотрим, на сколько ударов он опустится за следующую минуту. Называется это heart rate recovery, HRR. Чем круче падение, тем лучше сердце переключается из режима работы в режим отдыха, а это прямой признак тренированности и того, насколько бодро работает вегетативная нервная система. Есть большое исследование Кливлендской клиники (ссылка в конце), где больше двух тысяч человек наблюдали шесть лет. Вывод получился жестким. Если за первую минуту пульс падал на 12 ударов или меньше, риск смертности примерно удваивался, и это не зависело ни от возраста, ни от лекарств, ни от прочих поправок. Двенадцать ударов за минуту, вот и вся граница между спокойным и тревожным.
Приложение ищет ровно это. По всей записи оно скользит минутным окном и находит самое крутое падение пульса за минуту после каждого выхода из рабочей зоны. Такое восстановление отмечается прямо на графике, и рядом пишется его скорость в ударах в минуту. Одна цифра, и сразу видно, как быстро сердце отпустило.
Но самое интересное снова про то, зачем тут несколько датчиков. Первая же версия детектора уверенно отметила мне спад, которого не было. Оптика на часах на секунду дернулась вверх и тут же вернулась на место, детектор увидел падение и подписал внушительную цифру. А никакого восстановления не случилось, был короткий глюк одного датчика. Тогда я сделал главное правило: спад засчитывается, только если его подтверждают все датчики, которые в этот момент на связи. Ложный рывок часов два нагрудных ремня не поддержали, и фантомная отметка пропала сама собой. А настоящее восстановление, где все три линии проваливаются вниз вместе, осталось на месте с честной цифрой. Плюс перед анализом пульс сглаживается медианным фильтром, чтобы одиночные выбросы не превращались в призрачные спады.
Отклик пульса на постоянную нагрузку
С восстановлением связана и обратная задача: как пульс выходит на режим под постоянной нагрузкой. На эллипсе я знаю, что держу, скажем, ровно 205 ватт, и тогда пульс это чистый отклик тела на нагрузку, а не догадка. Выделяю на графике участок ровной работы, и приложение прямо на нем пишет разбор: установившийся пульс (плато), как быстро я на это плато вышел, и насколько пульс потом дрейфует вверх при той же мощности. Если ввести ватты, добавляется эффективность, вроде ударов на сто ватт.
Мотив тот же. При фиксированной нагрузке пульс перестает быть загадкой и становится показателем формы: чем ниже плато на тех же 205 ваттах от недели к неделе и чем меньше дрейф, тем лучше идут дела. А поскольку датчиков несколько, приложение берет для разбора самый полный из них, и можно свериться, что ремни говорят об одном и том же.
Итог
Так кто же врет?
Ну, то, что часы врут, наверное, видел каждый, кто носил их хотя бы день. Тут ничего нового, однако теперь хотя бы понятно, когда они врут с наибольшей вероятностью. К сожалению, не нашлось эвристики, как быстро привести их в чувство. Придется хорошенько разогреться, чтобы они стали показывать правильно.
Ремни Polar сильно фильтруют: им лишь бы показать гладкий пульс. Фибрилляцию ими точно не поймать.
С Garmin непонятно: то ли он шумит, то ли сердечный ритм действительно специфический. Но, во всяком случае, отсутствие непонятной фильтрации дает надежду на более точное измерение. Это как фотография в формате RAW или дизеринг у АЦП. Шум несет информацию. Надо только ее расшифровать.
Оба приложения открыты:
-
HeartRate_Monitor: сам мультисенсорный монитор пульса с зонами, статистикой и HRV.
-
HeartRate_Strap_Emulator: часы Wear OS, отдающие пульс как стандартный нагрудный ремень.
Небольшая честная оговорка про сам код. Оба приложения я собрал с помощью Claude Opus и Fable: разработкой на уровне кода я не занимаюсь. За мной была идея, постановка задач, схема экранов и испытания на живых датчиках, а собственно код писала модель.
И да, моим выводам категорически нельзя верить: всё это одна тренировка на одном человеке, без контроля сторонних факторов. Это единичные измерения, а не статистика: ни репрезентативной выборки, ни проверки на достоверность за ними нет. Это просто DIY!
Про скорость восстановления пульса, то самое исследование Кливлендской клиники: Cole CR и соавторы, «Heart-Rate Recovery Immediately after Exercise as a Predictor of Mortality», The New England Journal of Medicine, 1999. Ссылка: nejm.org/doi/full/10.1056/NEJM199910283411804.
ссылка на оригинал статьи https://habr.com/ru/articles/1056938/