1. Улучшение качества измерений (Принцип 1: Разделение)

Проблема: Текущий метод определения пульса основан только на средней интенсивности зеленого канала в области лба, что может быть подвержено шумам и артефактам (например, изменения освещения, движения головы).

Решение:

    Разделение по времени и пространству: Вместо использования только средней интенсивности, можно применять временные и пространственные фильтры для улучшения качества сигнала. Например, использовать многоканальную обработку для разных областей лба и объединять результаты.
    Использование нескольких областей интереса (ROI): Обнаружение и анализ нескольких областей лба одновременно для повышения точности измерений.

Реализация:

python

def detect_pulse(self, frame):
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    faces = self.face_cascade.detectMultiScale(
        gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

    if len(faces) == 0:
        print("Лицо не обнаружено")
        return None

    (x, y, w, h) = faces[0]
    roi_regions = [
        (int(x + w * 0.3), int(y + h * 0.1), int(w * 0.1), int(h * 0.2)),
        (int(x + w * 0.5), int(y + h * 0.1), int(w * 0.1), int(h * 0.2)),
        (int(x + w * 0.7), int(y + h * 0.1), int(w * 0.1), int(h * 0.2))
    ]

    mean_greens = []
    for (fx, fy, fw, fh) in roi_regions:
        forehead = frame[fy:fy+fh, fx:fx+fw]
        if forehead.size == 0:
            continue
        green_channel = forehead[:, :, 1]
        mean_green = np.mean(green_channel)
        mean_greens.append(mean_green)

    if not mean_greens:
        print("Области лба имеют нулевой размер")
        return None

    mean_green = np.mean(mean_greens)
    # Остальная часть кода остается без изменений 2. Повышение устойчивости к шумам (Принцип 15: Динамичность)

Проблема: Измерения пульса могут быть подвержены шумам из-за изменений освещения или движений головы.

Решение:

    Адаптивные фильтры: Внедрение адаптивных фильтров, таких как фильтр Калмана или адаптивные медианные фильтры, для динамического уменьшения шумов.
    Использование спектрального анализа: Применение быстрых преобразований Фурье (FFT) для выделения частот, соответствующих пульсу, и подавления шумовых частот.

Реализация:

python

from scipy.fft import fft

def calculate_bpm(self):
    if len(self.times) < 2:
        print("Недостаточно данных для расчета BPM")
        return

    time_diff = self.times[-1] - self.times[0]
    if time_diff <= 0:
        print("Некорректный временной интервал")
        return

    fps = len(self.data_buffer) / time_diff
    if fps <= 0:
        print(f"Некорректное значение FPS: {fps}")
        return

    # Нормализация данных
    normalized_data = np.array(self.data_buffer)
    normalized_data = (normalized_data - np.mean(normalized_data)) / np.std(normalized_data)

    # Применение FFT
    N = len(normalized_data)
    yf = fft(normalized_data)
    xf = np.linspace(0.0, fps/2.0, N//2)
    spectrum = 2.0/N * np.abs(yf[0:N//2])

    # Определение доминирующей частоты в диапазоне BPM
    bpm_min = BPM_LOWER_LIMIT / 60.0
    bpm_max = BPM_UPPER_LIMIT / 60.0
    idx_min = np.argmax(xf > bpm_min) - 1
    idx_max = np.argmax(xf > bpm_max) - 1

    peak_freq = xf[np.argmax(spectrum[idx_min:idx_max]) + idx_min]
    self.bpm = peak_freq * 60.0

    if self.bpm < BPM_LOWER_LIMIT or self.bpm > BPM_UPPER_LIMIT:
        print(f"BPM вне допустимого диапазона: {self.bpm}")
        self.bpm = 0.0 3. Увеличение удобства использования (Принцип 10: Предварительное действие)

Проблема: Текущий интерфейс может быть неинтуитивным для пользователей, особенно если они не знакомы с техническими аспектами.

Решение:

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

Реализация:

python

def detect_pulse(self, frame):
    # ... существующий код ...

    # Добавление индикатора стабильности
    if len(self.data_buffer) >= BUFFER_SIZE:
        stability = np.std(self.data_buffer[-BUFFER_SIZE//10:])  # Оценка стабильности последних данных
        cv2.putText(frame, f"Стабильность: {stability:.2f}", (x, y + h + 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
    
    # Рекомендации по положению
    if len(faces) > 1:
        cv2.putText(frame, "Пожалуйста, фокусируйтесь на одном лице", (10, 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
    
    return self.bpm 5. Увеличение точности и надежности (Принцип 17: Другие свойства)

Проблема: Точность измерений может быть снижена из-за вариаций в цвете кожи, освещении или движениях.

Решение:

    Многофакторный анализ: Использование дополнительных цветовых каналов (например, красного и синего) для более точного определения пульса.
    Машинное обучение: Обучение модели на основе машинного обучения для более точного выделения сигналов пульса из видео.

Реализация:

python

def detect_pulse(self, frame):
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    faces = self.face_cascade.detectMultiScale(
        gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

    if len(faces) == 0:
        print("Лицо не обнаружено")
        return None

    (x, y, w, h) = faces[0]
    forehead = frame[int(y + h * 0.1):int(y + h * 0.3),
                     int(x + w * 0.3):int(x + w * 0.7)]

    if forehead.size == 0:
        print("Область лба имеет нулевой размер")
        return None

    # Использование нескольких цветовых каналов
    r = np.mean(forehead[:, :, 2])
    g = np.mean(forehead[:, :, 1])
    b = np.mean(forehead[:, :, 0])
    mean_color = (r + g + b) / 3  # Можно использовать взвешенное среднее

    current_time = time.time() - self.start_time
    self.times.append(current_time)
    self.data_buffer.append(mean_color)

    # Остальная часть кода остается без изменений
    # ...6. Оптимизация производительности (Принцип 13: Инверсия)

Проблема: Обработка видео в реальном времени может быть ресурсоемкой, что приводит к задержкам и снижению FPS.

Решение:

    Инверсия порядка обработки: Предварительная обработка данных или использование многопоточности для разделения задач обнаружения лица и анализа сигнала.
    Оптимизация алгоритмов: Использование более быстрых алгоритмов или библиотек, оптимизированных для работы с видео в реальном времени (например, использование OpenCV с CUDA).

Реализация:

python

import threading

def main():
    detector = PulseDetector()
    cap = cv2.VideoCapture(0)

    if not cap.isOpened():
        print("Ошибка при открытии камеры")
        return

    root = tk.Tk()
    root.title("Определение пульса")

    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 10))
    canvas = FigureCanvasTkAgg(fig, master=root)
    canvas_widget = canvas.get_tk_widget()
    canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)

    bpm_history = []
    time_history = []

    def update_frame():
        while True:
            ret, frame = cap.read()
            if not ret:
                print("Не удалось получить кадр")
                continue

            bpm = detector.detect_pulse(frame)

            ax1.clear()
            ax1.imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            ax1.axis('off')

            if bpm is not None and bpm > 0:
                bpm_history.append(bpm)
                time_history.append(time.time() - detector.start_time)

                if len(bpm_history) > 100:
                    bpm_history.pop(0)
                    time_history.pop(0)

                ax2.clear()
                ax2.plot(time_history, bpm_history, 'b-')
                ax2.set_xlabel('Время (с)')
                ax2.set_ylabel('BPM')
                ax2.set_ylim(BPM_LOWER_LIMIT, BPM_UPPER_LIMIT)
                ax2.set_title('История измерений BPM')

            canvas.draw()

    def run_thread():
        thread = threading.Thread(target=update_frame)
        thread.daemon = True
        thread.start()

    def on_closing():
        cap.release()
        root.quit()
        root.destroy()

    root.protocol("WM_DELETE_WINDOW", on_closing)
    run_thread()
    root.mainloop()  import cv2
import numpy as np
from scipy.fft import fft
import time

# Глобальные параметры
BUFFER_SIZE = 150  # Размер буфера для сохранения данных интенсивности
BPM_LOWER_LIMIT = 40  # Нижняя граница допустимого диапазона ЧСС
BPM_UPPER_LIMIT = 180  # Верхняя граница допустимого диапазона ЧСС

class PulseDetector:
    def __init__(self):
        self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
        self.data_buffer = []
        self.times = []
        self.bpm = 0.0
        self.start_time = time.time()

    def detect_pulse(self, frame):
        # Преобразование кадра в оттенки серого
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

        if len(faces) == 0:
            print("Лицо не обнаружено")
            return None

        # Определение первой области лица
        (x, y, w, h) = faces[0]

        # Многоканальная обработка (выделение нескольких областей лба)
        roi_regions = [
            (int(x + w * 0.3), int(y + h * 0.1), int(w * 0.1), int(h * 0.2)),
            (int(x + w * 0.5), int(y + h * 0.1), int(w * 0.1), int(h * 0.2)),
            (int(x + w * 0.7), int(y + h * 0.1), int(w * 0.1), int(h * 0.2))
        ]

        # Извлечение средних значений интенсивности для разных цветовых каналов
        mean_greens = []
        mean_reds = []
        mean_blues = []

        for (fx, fy, fw, fh) in roi_regions:
            forehead = frame[fy:fy+fh, fx:fx+fw]
            if forehead.size == 0:
                continue
            green_channel = forehead[:, :, 1]
            red_channel = forehead[:, :, 2]
            blue_channel = forehead[:, :, 0]
            
            mean_green = np.mean(green_channel)
            mean_red = np.mean(red_channel)
            mean_blue = np.mean(blue_channel)
            
            mean_greens.append(mean_green)
            mean_reds.append(mean_red)
            mean_blues.append(mean_blue)

        if not mean_greens:
            print("Области лба имеют нулевой размер")
            return None

        # Усреднение значений по каналам
        mean_green = np.mean(mean_greens)
        mean_red = np.mean(mean_reds)
        mean_blue = np.mean(mean_blues)

        # Вставка данных в буфер
        self.data_buffer.append(mean_green)
        self.times.append(time.time() - self.start_time)

        # Ограничение буфера
        if len(self.data_buffer) > BUFFER_SIZE:
            self.data_buffer.pop(0)
            self.times.pop(0)

        # Расчет ЧСС
        self.calculate_bpm()

        # Визуальные подсказки
        stability = np.std(self.data_buffer[-BUFFER_SIZE//10:]) if len(self.data_buffer) >= BUFFER_SIZE else 0
        cv2.putText(frame, f"Стабильность: {stability:.2f}", (x, y + h + 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)

        # Вывод значения BPM
        if self.bpm > 0:
            cv2.putText(frame, f"BPM: {self.bpm:.2f}", (x, y + h + 50),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)

        return self.bpm

    def calculate_bpm(self):
        if len(self.times) < 2:
            print("Недостаточно данных для расчета BPM")
            return

        time_diff = self.times[-1] - self.times[0]
        if time_diff <= 0:
            print("Некорректный временной интервал")
            return

        fps = len(self.data_buffer) / time_diff
        if fps <= 0:
            print(f"Некорректное значение FPS: {fps}")
            return

        # Нормализация данных
        normalized_data = np.array(self.data_buffer)
        normalized_data = (normalized_data - np.mean(normalized_data)) / np.std(normalized_data)

        # Применение FFT
        N = len(normalized_data)
        yf = fft(normalized_data)
        xf = np.linspace(0.0, fps/2.0, N//2)
        spectrum = 2.0/N * np.abs(yf[0:N//2])

        # Определение доминирующей частоты в диапазоне BPM
        bpm_min = BPM_LOWER_LIMIT / 60.0
        bpm_max = BPM_UPPER_LIMIT / 60.0
        idx_min = np.argmax(xf > bpm_min) - 1
        idx_max = np.argmax(xf > bpm_max) - 1

        peak_freq = xf[np.argmax(spectrum[idx_min:idx_max]) + idx_min]
        self.bpm = peak_freq * 60.0

        if self.bpm < BPM_LOWER_LIMIT or self.bpm > BPM_UPPER_LIMIT:
            print(f"BPM вне допустимого диапазона: {self.bpm}")
            self.bpm = 0.0

# Основная часть программы
if __name__ == "__main__":
    detector = PulseDetector()
    cap = cv2.VideoCapture(0)

    while True:
        ret, frame = cap.read()
        if not ret:
            print("Не удалось захватить кадр")
            break

        # Определение пульса
        bpm = detector.detect_pulse(frame)

        # Отображение кадра
        cv2.imshow('Pulse Detector', frame)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()
import cv2
import numpy as np
import time
from scipy.signal import find_peaks
import matplotlib.pyplot as plt
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

# Константы
BUFFER_SIZE = 150
BPM_LOWER_LIMIT = 40.0
BPM_UPPER_LIMIT = 180.0

class PulseDetector:
    def __init__(self):  # Исправлено: init -> __init__
        self.face_cascade = cv2.CascadeClassifier(
            cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
        if self.face_cascade.empty():
            raise ValueError("Ошибка при загрузке каскада Хаара для обнаружения лиц")
        self.data_buffer = []
        self.times = []
        self.bpm = 0.0
        self.start_time = time.time()

    def detect_pulse(self, frame):
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(
            gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

        if len(faces) == 0:
            print("Лицо не обнаружено")
            return None

        (x, y, w, h) = faces[0]
        forehead_top = int(y + h * 0.1)
        forehead_bottom = int(y + h * 0.3)
        forehead_left = int(x + w * 0.3)
        forehead_right = int(x + w * 0.7)

        forehead = frame[forehead_top:forehead_bottom, forehead_left:forehead_right]

        if forehead.size == 0:
            print("Область лба имеет нулевой размер")
            return None

        green_channel = forehead[:, :, 1]
        mean_green = np.mean(green_channel)

        current_time = time.time() - self.start_time
        self.times.append(current_time)
        self.data_buffer.append(mean_green)

        if len(self.data_buffer) > BUFFER_SIZE:
            self.data_buffer.pop(0)
            self.times.pop(0)

        if len(self.data_buffer) == BUFFER_SIZE:
            self.calculate_bpm()

        cv2.rectangle(frame, (forehead_left, forehead_top),
                      (forehead_right, forehead_bottom), (0, 255, 0), 2)
        cv2.putText(frame, f"BPM: {self.bpm:.1f}", (x, y - 10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)

        return self.bpm

    def calculate_bpm(self):
        if len(self.times) < 2:
            print("Недостаточно данных для расчета BPM")
            return

        time_diff = self.times[-1] - self.times[0]
        if time_diff <= 0:  # Исправлено: проверка на нулевой и отрицательный интервал
            print("Некорректный временной интервал")
            return

        fps = len(self.data_buffer) / time_diff
        if fps <= 0:
            print(f"Некорректное значение FPS: {fps}")
            return

        # Нормализация данных
        normalized_data = np.array(self.data_buffer)
        normalized_data = (normalized_data - np.mean(normalized_data)) / np.std(normalized_data)

        # Поиск пиков
        peaks, _ = find_peaks(normalized_data, distance=int(fps * 0.5))

        if len(peaks) >= 2:
            peak_intervals = np.diff(peaks) / fps
            bpm_values = 60 / peak_intervals
            self.bpm = np.median(bpm_values)

            if self.bpm < BPM_LOWER_LIMIT or self.bpm > BPM_UPPER_LIMIT:
                print(f"BPM вне допустимого диапазона: {self.bpm}")
                self.bpm = 0.0
        else:
            print(f"Недостаточно пиков для расчета BPM. Найдено пиков: {len(peaks)}")

def main():
    detector = PulseDetector()
    cap = cv2.VideoCapture(0)

    if not cap.isOpened():
        print("Ошибка при открытии камеры")
        return

    root = tk.Tk()
    root.title("Определение пульса")

    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 10))
    canvas = FigureCanvasTkAgg(fig, master=root)
    canvas_widget = canvas.get_tk_widget()
    canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)

    bpm_history = []
    time_history = []

    def update():
        ret, frame = cap.read()
        if not ret:
            print("Не удалось получить кадр")
            root.after(50, update)
            return

        bpm = detector.detect_pulse(frame)

        ax1.clear()
        ax1.imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
        ax1.axis('off')

        if bpm is not None and bpm > 0:
            bpm_history.append(bpm)
            time_history.append(time.time() - detector.start_time)

            if len(bpm_history) > 100:
                bpm_history.pop(0)
                time_history.pop(0)

            ax2.clear()
            ax2.plot(time_history, bpm_history, 'b-')
            ax2.set_xlabel('Время (с)')
            ax2.set_ylabel('BPM')
            ax2.set_ylim(BPM_LOWER_LIMIT, BPM_UPPER_LIMIT)
            ax2.set_title('История измерений BPM')

        canvas.draw()
        root.after(50, update)

    def on_closing():
        cap.release()
        root.quit()
        root.destroy()

    root.protocol("WM_DELETE_WINDOW", on_closing)
    root.after(0, update)
    root.mainloop()

if __name__ == "__main__":  # Исправлено: name -> __name__
    main()