Разработка AI-системы детекции отсутствия СИЗ у строителей

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
Разработка AI-системы детекции отсутствия СИЗ у строителей
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

AI-детекция отсутствия средств индивидуальной защиты (СИЗ)

Узкая, но критически важная задача: автоматически определять, надета ли на рабочего каска, жилет, очки, перчатки. Звучит просто, пока не сталкиваешься с реальными условиями: рабочий в тени, боком, на высоте 20м, в клубах пыли.

Датасеты и модели для PPE detection

Открытые датасеты: Safety Helmet Detection Dataset (7000+ изображений), PPE-Detection Dataset (Roboflow, 8000+ изображений), CHV (Construction Helmet and Vest).

Для production всегда нужно дообучение на собственных данных. Разные стройплощадки — разные типы касок, жилетов, освещения.

from ultralytics import YOLO
import cv2
import numpy as np
from collections import defaultdict

class PPEDetector:
    """
    Модель обнаруживает как наличие, так и отсутствие СИЗ напрямую.
    Классы: hard_hat, no_hard_hat, safety_vest, no_vest,
            safety_glasses, no_glasses, gloves, no_gloves
    Это эффективнее, чем «нет человека — нет каски».
    """
    def __init__(self, model_path: str, site_config: dict):
        self.model = YOLO(model_path)
        self.required_ppe = site_config.get('required_ppe', ['hard_hat', 'safety_vest'])
        self.violation_threshold = site_config.get('violation_threshold', 0.5)

        # Для подавления дублирующих тревог
        self.active_violations: dict[int, dict] = {}
        self.cooldown_frames = 30  # 1 сек @ 30fps

    def detect(self, frame: np.ndarray) -> dict:
        results = self.model.track(frame, persist=True, conf=0.4)

        workers_status = {}
        all_detections = []

        for box in results[0].boxes:
            cls = self.model.names[int(box.cls)]
            conf = float(box.conf)
            bbox = list(map(int, box.xyxy[0]))
            track_id = int(box.id) if box.id is not None else -1

            all_detections.append({
                'class': cls, 'conf': conf,
                'bbox': bbox, 'track_id': track_id
            })

        # Группируем по рабочим (person = anchor)
        persons = [d for d in all_detections if d['class'] == 'person']

        for person in persons:
            pid = person['track_id']
            violations = []

            for req_ppe in self.required_ppe:
                no_ppe_class = f'no_{req_ppe}'

                # Есть явный класс "без СИЗ" рядом с рабочим?
                for det in all_detections:
                    if det['class'] == no_ppe_class:
                        if self._near_person(det['bbox'], person['bbox']):
                            if det['conf'] > self.violation_threshold:
                                violations.append({
                                    'type': no_ppe_class,
                                    'confidence': det['conf']
                                })

            workers_status[pid] = {
                'bbox': person['bbox'],
                'violations': violations,
                'compliant': len(violations) == 0
            }

        return {
            'workers': workers_status,
            'total_workers': len(persons),
            'violations_count': sum(
                len(w['violations']) for w in workers_status.values()
            ),
            'compliance_rate': (
                sum(1 for w in workers_status.values() if w['compliant'])
                / max(len(persons), 1)
            )
        }

    def _near_person(self, ppe_bbox: list, person_bbox: list,
                      expand: float = 0.3) -> bool:
        """СИЗ считается относящимся к рабочему, если его bbox близко"""
        px1, py1, px2, py2 = person_bbox
        pw = px2 - px1
        ph = py2 - py1

        # Расширяем bbox рабочего
        ex1 = px1 - pw * expand
        ey1 = py1 - ph * expand
        ex2 = px2 + pw * expand
        ey2 = py2 + ph * expand

        cx = (ppe_bbox[0] + ppe_bbox[2]) / 2
        cy = (ppe_bbox[1] + ppe_bbox[3]) / 2

        return ex1 <= cx <= ex2 and ey1 <= cy <= ey2

Трудные случаи и как с ними работать

1. Частичное перекрытие: рабочий виден наполовину за конструкцией. Голова в кадре — каска проверяется. Если голова не видна — не штрафуем.

def is_head_visible(self, person_bbox: list,
                     frame_height: int) -> bool:
    """Оцениваем, видна ли голова рабочего"""
    h = person_bbox[3] - person_bbox[1]
    # Голова занимает верхние ~15% тела
    head_region_y = person_bbox[1] + h * 0.15
    return head_region_y < frame_height * 0.95  # не у нижнего края

2. Мелкие объекты на дальнем плане: рабочий на 40 метрах, bbox 30×90 px. Каска 8×8 px. YOLOv8l на таких разрешениях даёт recall ~70%. Решение: PTZ-камеры с автозумом или дополнительные камеры на дальних секциях.

3. Похожие объекты: строительный мусор, ткань на строительных лесах похожи на каску при плохом освещении. Hard negative mining при дообучении — собираем 200–300 таких примеров и добавляем в тренировочный набор.

Статистика и дашборд

class PPEComplianceDashboard:
    def daily_summary(self, detection_log: list) -> dict:
        total_detections = len(detection_log)
        violations_by_type = defaultdict(int)
        violations_by_hour = defaultdict(int)
        violators = set()

        for record in detection_log:
            for violation in record.get('violations', []):
                violations_by_type[violation['type']] += 1
                hour = record['timestamp'].hour
                violations_by_hour[hour] += 1
                violators.add(record['worker_id'])

        return {
            'total_inspections': total_detections,
            'unique_violators': len(violators),
            'violations_by_type': dict(violations_by_type),
            'peak_violation_hour': max(violations_by_hour,
                                        key=violations_by_hour.get,
                                        default=None),
            'compliance_rate': 1 - (len(violators) / max(total_detections, 1))
        }
Масштаб Срок
Детектор каски + жилета (2–4 камеры) 2–4 недели
Полный PPE (6+ типов, 10+ камер) 5–9 недель
С дашбордом и автоматическими актами 7–12 недель