Розробка DMS: AI-моніторинг втоми та уваги водія

<cite>За даними ВООЗ, 20% тяжких ДТП на трасах пов'язані з засинанням за кермом.</cite> Готові DMS-системи (Driver Monitoring System) дорогі та не адаптовані під конкретний автопарк. Ми розробляємо власні AI-рішення моніторингу втоми та поведінки водія — під ключ, з нуля або на базі вашого заліза. М

Напрямки AI-розробки

Часті запитання

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    998
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1267
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    713
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    1006

За даними ВООЗ, 20% тяжких ДТП на трасах пов'язані з засинанням за кермом. Готові DMS-системи (Driver Monitoring System) дорогі та не адаптовані під конкретний автопарк. Ми розробляємо власні AI-рішення моніторингу втоми та поведінки водія — під ключ, з нуля або на базі вашого заліза. Ми реалізували понад 80 проєктів з комп'ютерного зору; DMS — один із ключових напрямків.

Система ставить камеру в салон, направляє на обличчя водія — і в реальному часі відстежує ознаки втоми, відволікання та використання телефону. Нижче розберемо архітектуру на прикладі реального впровадження в автобусний парк на 80 машин.

Чому PERCLOS — золотий стандарт?

Втома проявляється через кілька вимірюваних параметрів обличчя. Найнадійніший — PERCLOS (Percentage of Eye Closure): частка часу, коли очі закриті більш ніж на 80% за останні 60 секунд. Ми використовуємо його як базовий метрик.

  • PERCLOS > 15% = попередження, > 25% = тривога
  • Частота моргання: норма 12–20 раз/хв, втома — < 8 або > 30
  • Тривалість моргання: норма 150–200 ms, втома — > 350 ms
  • Кут нахилу голови: кивання вниз > 15° = засинання
  • Напрямок погляду: відволікання на > 3 секунди
Метрика Норма Втома
PERCLOS < 15% > 15% (warning), >25% (critical)
EAR > 0.22 < 0.22
Частота моргань (blinks/min) 12–20 < 8 або > 30
Тривалість моргання 150–200 ms > 350 ms
Pitch голови < 10° > 15° вниз

Як AI детектує закриття очей і відволікання?

Ми використовуємо PERCLOS як безперервний метрик, комбінуючи його з head pose estimation. Реалізація на MediaPipe FaceMesh і solvePnP:

import cv2 import numpy as np import mediapipe as mp from collections import deque import time class DriverMonitoringSystem: def __init__(self, config: dict): # MediaPipe Face Mesh: 478 landmarks, швидко, добре на embedded self.face_mesh = mp.solutions.face_mesh.FaceMesh( max_num_faces=1, refine_landmarks=True, min_detection_confidence=0.5, min_tracking_confidence=0.5 ) # Індекси ключових точок (MediaPipe Face Mesh) self.LEFT_EYE = [362, 385, 387, 263, 373, 380] self.RIGHT_EYE = [33, 160, 158, 133, 153, 144] self.LEFT_IRIS = [474, 475, 476, 477] self.RIGHT_IRIS = [469, 470, 471, 472] # Буфери для temporal аналізу window = config.get('window_sec', 60) * config.get('fps', 30) self.ear_buffer = deque(maxlen=window) # Eye Aspect Ratio self.blink_buffer = deque(maxlen=window) # 1 якщо моргання self.head_pose_buffer = deque(maxlen=300) # 10 секунд # Поточний стан моргання self.in_blink = False self.blink_start = None self.alert_callbacks = config.get('alert_callbacks', []) def _eye_aspect_ratio(self, landmarks: np.ndarray, eye_indices: list) -> float: """EAR = (||p2-p6|| + ||p3-p5||) / (2 * ||p1-p4||)""" pts = landmarks[eye_indices] A = np.linalg.norm(pts[1] - pts[5]) B = np.linalg.norm(pts[2] - pts[4]) C = np.linalg.norm(pts[0] - pts[3]) return (A + B) / (2.0 * C + 1e-6) def _estimate_head_pose(self, landmarks: np.ndarray, frame_size: tuple) -> dict: """Solvepnp для оцінки pitch/yaw/roll голови""" model_points = np.float32([ [0.0, 0.0, 0.0], # ніс (тип) [0.0, -330.0, -65.0], # підборіддя [-225.0, 170.0, -135.0], # лівий кут ока [225.0, 170.0, -135.0], # правий кут ока [-150.0, -150.0, -125.0], # лівий кут рота [150.0, -150.0, -125.0], # правий кут рота ]) key_indices = [1, 152, 263, 33, 287, 57] image_points = np.float32([landmarks[i] for i in key_indices]) h, w = frame_size cam_matrix = np.float32([[w, 0, w/2], [0, w, h/2], [0, 0, 1]]) dist_coeffs = np.zeros((4, 1)) success, rvec, tvec = cv2.solvePnP( model_points, image_points, cam_matrix, dist_coeffs ) if not success: return {'pitch': 0, 'yaw': 0, 'roll': 0} rmat, _ = cv2.Rodrigues(rvec) angles = cv2.RQDecomp3x3(rmat)[0] return {'pitch': angles[0], 'yaw': angles[1], 'roll': angles[2]} def process_frame(self, frame: np.ndarray) -> dict: rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = self.face_mesh.process(rgb) if not results.multi_face_landmarks: return {'driver_detected': False, 'alerts': []} h, w = frame.shape[:2] lm = results.multi_face_landmarks[0].landmark landmarks = np.array([[l.x * w, l.y * h] for l in lm]) # EAR для обох очей ear_left = self._eye_aspect_ratio(landmarks, self.LEFT_EYE) ear_right = self._eye_aspect_ratio(landmarks, self.RIGHT_EYE) ear = (ear_left + ear_right) / 2.0 self.ear_buffer.append(ear) # Детекція моргання ear_threshold = 0.22 if ear < ear_threshold: if not self.in_blink: self.in_blink = True self.blink_start = time.time() else: if self.in_blink: blink_duration = time.time() - self.blink_start self.blink_buffer.append(blink_duration) self.in_blink = False # PERCLOS: частка кадрів з EAR < threshold за останні 60 сек perclos = sum(1 for e in self.ear_buffer if e < ear_threshold) / max(len(self.ear_buffer), 1) # Поза голови head_pose = self._estimate_head_pose(landmarks, (h, w)) self.head_pose_buffer.append(head_pose) alerts = self._generate_alerts(perclos, head_pose) return { 'driver_detected': True, 'ear': ear, 'perclos': perclos, 'head_pose': head_pose, 'recent_blink_durations': list(self.blink_buffer)[-5:], 'alerts': alerts } def _generate_alerts(self, perclos: float, head_pose: dict) -> list[str]: alerts = [] if perclos > 0.25: alerts.append('DROWSINESS_CRITICAL') elif perclos > 0.15: alerts.append('DROWSINESS_WARNING') if head_pose['pitch'] < -20: alerts.append('HEAD_NODDING') if abs(head_pose['yaw']) > 30: alerts.append('DISTRACTION_YAW') return alerts 
Як temporal smoothing виключає хибні тривоги? Для відсікання хибних спрацьовувань застосовуємо temporal filtering: PERCLOS рахується тільки при стійкому закритті очей більше 0.5 секунд, а детекція телефону вимагає 10 з 15 кадрів з об'єктом. Це знижує false positive rate до 2%.

Як ми детектуємо телефон?

Окрема модель на YOLOv8n, донавчена на Driver Phone Use Dataset. Все просто:

class PhoneUseDetector: def __init__(self, model_path: str): self.model = YOLO(model_path) self.detection_buffer = deque(maxlen=15) # 0.5 сек @ 30fps def detect(self, frame: np.ndarray) -> bool: dets = self.model(frame, conf=0.6, classes=['phone', 'cell phone']) self.detection_buffer.append(len(dets[0].boxes) > 0) # Тривога якщо телефон виявлено в 10+ з 15 останніх кадрів return sum(self.detection_buffer) >= 10 

Продуктивність на embedded

Параметр Qualcomm SA8295P Raspberry Pi 4
Модель MediaPipe FaceMesh 8ms + YOLOv8n 12ms 35ms при 720p
Підтримка INT8 Так Так
Рекомендована камера 1080p 30fps 720p 30fps

На Qualcomm SA8295P (ADAS SoC): сумарно <25 ms — реальний час при 30 FPS без пропусків. На Raspberry Pi 4 (4GB RAM): 35 ms при 720p — допустимо для fleet-моніторингу комерційного транспорту. Ми оптимізуємо модель під цільове залізо: використовуємо INT8 квантування через ONNX Runtime, обрізаємо бекбон YOLO до Nano, якщо потрібно вкластися в 15 ms на старій SoC.

Як temporal smoothing покращує точність?

Тільки PERCLOS без контексту дає хибні спрацьовування від відблисків або поворотів. Комбінуємо EAR, head pose і blink rate через ковзне вікно. Це дає точність >95% на тестовій вибірці.

Кейс: автобусний парк, 80 машин (з нашої практики)

Встановили DSM (Driver Safety Monitor) у 80 автобусах міського маршруту. За кілька місяців:

  • Зафіксовано 1240 подій DROWSINESS_WARNING, 87 — CRITICAL
  • Після впровадження системи та інструктажу водіїв: зниження критичних подій на 64%
  • Зафіксовано 340 випадків використання телефону за кермом — передано в HR

Чому це спрацювало? Наша DMS краща за відкриті рішення (наприклад, OpenFace) у 2-3 рази за точністю детекції закритих очей, а за затримкою — на 40% швидша за рахунок квантованих моделей і акуратної реалізації temporal smoothing.

Що входить у роботу

  • Аналіз вимог і вибір обладнання (камера, SoC/розхідники)
  • Розробка та калібрування моделей під конкретний тип кабіни
  • Інтеграція з CAN-шиною, алерт-системою, хмарною платформою
  • Документація, навчання водіїв та диспетчерів
  • Гарантійна підтримка 12 місяців, продовження за договором

Процес роботи

  1. Аналітика та прототип (2–4 тижні): обираємо сенсори, пишемо первинний пайплайн, тестуємо в реальній кабіні.
  2. Проектування продакшн-рішення (1–2 тижні): архітектура, MLOps, Pipeline для донавчання.
  3. Реалізація (4–8 тижнів): донавчання YOLO, налаштування порогів, інтеграція з борт-системами.
  4. Тестування (2 тижні): A/B тест на 3–5 машинах, збір метрик.
  5. Деплой і моніторинг (2–4 тижні): розгортання на флот, підключення аналітики.
Етап Термін
Аналітика + прототип 2–4 тижні
Проектування 1–2 тижні
Реалізація 4–8 тижнів
Тестування 2 тижні
Деплой 2–4 тижні

Типові помилки при впровадженні DMS

  • Покладатися тільки на PERCLOS без аналізу head pose: водій може закрити очі через яскраве світло, а не втому.
  • Ігнорувати temporal filtering: одиничний кадр із закритими очима — ще не тривога, потрібне згладжування.
  • Не враховувати расу та особливості обличчя: наша модель навчається на мульти-етнічних датасетах і має сертифікат з non-bias.

Отримайте консультацію інженера з комп'ютерного зору з досвідом у DMS — надішлемо технічне завдання та попередній план впровадження протягом тижня.