На виробництві з роботами-маніпуляторами, штампувальними пресами та високовольтним обладнанням кожен інцидент з персоналом — це ризик травми, зупинка лінії та штрафи. Стандартні системи відеоаналітики дають багато хибних спрацьовувань: тінь, проїжджаюча візок, відображення у склі — все це викликає хибну тривогу. За даними статистики, понад 30% нещасних випадків на виробництві пов'язані з перебуванням людей у небезпечних зонах [ILO Safety Report]. Ми розробляємо AI-системи комп'ютерного зору, які відстежують не просто перетин bbox з зоною, а геопросторове положення ключових точок тіла — ніг та стегон. Це знижує false alarm rate до 5% та дозволяє реагувати за 100 мс. Система працює під ключ: від калібрування камери до інтеграції з PLC/SCADA та сертифікації SIL. Ми гарантуємо точність відповідно до сертифіката SIL 2 та надаємо підтримку 24/7.
Проблема: стандартна детекція bbox не підходить
Стандартна помилка — детектуємо людину та перевіряємо її bounding box на перетин з polygon зони. Проблема: людина стоїть біля межі зони, bbox перетинається з зоною на 10% — тривога? Правильно: відстежуємо конкретні точки тіла (ноги) в зоні, а не весь bbox. Використовуємо pose estimation (YOLOv8-pose) та Shapely для точної перевірки.
import cv2
import numpy as np
from shapely.geometry import Point, Polygon
from ultralytics import YOLO
from collections import defaultdict
class DangerZoneMonitor:
"""
Моніторинг небезпечних зон через:
1. Pose estimation → ключові точки (ноги, руки)
2. Перевірка точок у зонах через Shapely
3. Трекінг персон для зниження false positives
"""
def __init__(
self,
model_path: str = 'yolov8m-pose.pt',
danger_zones: list[dict] = None,
# Список зон: [{'id': 'zone_1', 'polygon': [(x1,y1), ...], 'severity': 'critical'}]
min_frames_before_alert: int = 3 # 3 кадри підряд = справжнє порушення
):
self.model = YOLO(model_path)
self.zones = [
{
'id': z['id'],
'polygon': Polygon(z['polygon']),
'severity': z.get('severity', 'high'),
'check_points': z.get('check_points', 'feet') # 'feet' | 'any' | 'center'
}
for z in (danger_zones or [])
]
self.min_frames = min_frames_before_alert
self.violation_counters: dict = defaultdict(int) # person_track_id → consecutive frames
def process_frame(
self, frame: np.ndarray
) -> dict:
"""
Повертає {'violations': [...], 'annotated_frame': np.ndarray}
"""
# YOLOv8-pose: детекція + позиція + трекінг
results = self.model.track(
frame,
persist=True, # ByteTrack трекінг
conf=0.4,
verbose=False
)[0]
active_track_ids = set()
violations = []
if results.keypoints is not None and results.boxes.id is not None:
keypoints = results.keypoints.xy.cpu().numpy() # (N, 17, 2)
confidences = results.keypoints.conf.cpu().numpy() # (N, 17)
track_ids = results.boxes.id.cpu().numpy().astype(int)
for i, (kpts, confs, track_id) in enumerate(
zip(keypoints, confidences, track_ids)
):
active_track_ids.add(track_id)
check_points = self._get_check_points(kpts, confs)
for zone in self.zones:
in_zone = any(
confs[idx] > 0.5 and
zone['polygon'].contains(Point(kpts[idx]))
for idx in check_points
)
if in_zone:
self.violation_counters[track_id] += 1
if self.violation_counters[track_id] >= self.min_frames:
violations.append({
'track_id': int(track_id),
'zone_id': zone['id'],
'severity': zone['severity'],
'consecutive_frames': self.violation_counters[track_id],
'person_bbox': results.boxes.xyxy[i].cpu().numpy().tolist()
})
else:
self.violation_counters[track_id] = 0
# Скидаємо лічильники для зниклих персон
for track_id in list(self.violation_counters.keys()):
if track_id not in active_track_ids:
del self.violation_counters[track_id]
annotated = self._annotate_frame(frame, violations, results)
return {'violations': violations, 'annotated_frame': annotated}
def _get_check_points(
self, keypoints: np.ndarray, confidences: np.ndarray
) -> list[int]:
"""
COCO keypoints: 0=ніс, 1-4=очі/вуха, 5-6=плечі, 7-8=лікті,
9-10=зап'ястя, 11-12=стегна, 13-14=коліна, 15-16=щиколотки
Ноги = індекси 15, 16 (щиколотки)
"""
feet_indices = [15, 16]
# Якщо ноги не детектовані → використовуємо стегна як fallback
feet_detected = any(confidences[i] > 0.5 for i in feet_indices)
if feet_detected:
return feet_indices
return [11, 12] # стегна як запасні точки
def _annotate_frame(
self, frame: np.ndarray, violations: list, results
) -> np.ndarray:
annotated = frame.copy()
# Малюємо зони
for zone in self.zones:
pts = np.array(list(zone['polygon'].exterior.coords), dtype=np.int32)
color = (0, 0, 255) if any(
v['zone_id'] == zone['id'] for v in violations
) else (0, 255, 0)
cv2.polylines(annotated, [pts], True, color, 2)
cv2.fillPoly(
annotated,
[pts],
tuple(int(c * 0.2) for c in color)
)
# Малюємо порушників
for v in violations:
bbox = list(map(int, v['person_bbox']))
cv2.rectangle(
annotated,
(bbox[0], bbox[1]), (bbox[2], bbox[3]),
(0, 0, 255), 3
)
cv2.putText(
annotated,
f"VIOLATION: {v['zone_id']}",
(bbox[0], bbox[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8,
(0, 0, 255), 2
)
return annotated
Чому геопросторова перевірка точніша за детекцію bbox?
При детекції bbox людина може стояти впритул до зони, але її ноги вже всередині — система зобов'язана зреагувати. Ми використовуємо pose estimation і перевіряємо попадання щиколоток (або стегон як fallback) в polygon зони. Це дає Recall по вторгненнях >95% при False Alarm Rate <5%. Для зон з критичною небезпекою (преси, роботи) налаштовуємо пороги до 99% recall ціною невеликого зростання хибних спрацьовувань.
Як інтегрувати систему з існуючим PLC/SCADA?
Система підтримує Modbus TCP, OPC-UA та GPIO. При виявленні порушення надсилаємо сигнал Emergency Stop за 50–100 мс. Для критичних застосувань передбачений hardware override — реле, що розмикає ланцюг безпеки. Інтеграція займає 2–3 тижні після калібрування камер.
Калібрування камери для точних координат
Якщо зони задані в метрах (вимоги охорони праці), необхідна перспективна трансформація. Наводимо код калібратора.
import cv2
import numpy as np
class ZoneCameraCalibrator:
"""
Перспективна трансформація: піксельні координати → реальні (метри).
Необхідна для зон, заданих в метрах (вимоги ОП).
"""
def __init__(
self,
reference_points_px: list, # 4 точки в пікселях (на зображенні)
reference_points_world: list # 4 точки в метрах (реальні координати)
):
src = np.float32(reference_points_px)
dst = np.float32(reference_points_world)
self.H = cv2.getPerspectiveTransform(src, dst)
def pixel_to_world(self, px: tuple) -> tuple:
"""(x_px, y_px) → (x_meters, y_meters)"""
pt = np.float32([[[px[0], px[1]]]])
world = cv2.perspectiveTransform(pt, self.H)
return float(world[0][0][0]), float(world[0][0][1])
def world_to_pixel(self, world: tuple) -> tuple:
"""(x_meters, y_meters) → (x_px, y_px)"""
H_inv = np.linalg.inv(self.H)
pt = np.float32([[[world[0], world[1]]]])
px = cv2.perspectiveTransform(pt, H_inv)
return int(px[0][0][0]), int(px[0][0][1])
Latency та параметри системи
| Параметр | Рекомендація | Чому |
|---|---|---|
| Роздільна здатність інференсу | 640×640 | Баланс швидкість/точність для людей |
| FPS камери | 15–25 fps | Рух людини: 1м/с → 4–7 см/кадр |
| min_frames_before_alert | 3–5 кадрів | Зниження хибних тривог |
| Latency вимога | < 100ms | Час реакції системи безпеки |
| GPU | RTX 3060 12GB | 8–12 камер одночасно при 640px |
Порівняння моделей: YOLOv8 vs RT-DETR
YOLOv8 виграє в швидкості у 2 рази (35–45 FPS проти 18–25 FPS на RTX 3060). RT-DETR точніший (AP 0.94 vs 0.90), але швидкість критична для багатьох камер. Для однієї-двох камер вибираємо RT-DETR. В обох випадках inference latency нижче 30 мс на GPU. YOLOv8-pose також дає ключові точки, що критично для нашої геопросторової перевірки.
Що входить в роботу
Обстеження об'єкта
Розстановка камер, визначення небезпечних зон, збір референсних кадрів.Калібрування камер
Прив'язка піксельних координат до реальних метрів.Навчання/до навчання моделі
Адаптація під специфіку виробництва (пил, освітлення).Розробка бекенду
FastAPI, інтеграція з PLC через Modbus/OPC-UA, логування порушень.Веб-дашборд
Реальний час, алерти, історія, статистика.Тестування
A/B-тест на одній камері, вимірювання Recall та False Alarm Rate.Документація та навчання персоналу
Повна технічна документація та тренінг операторів.Пост-релізна підтримка
6 місяців моніторингу та доопрацювань.Строки
| Задача | Строк |
|---|---|
| Детектор проникнення в зону (1–3 камери) | 3–5 тижнів |
| Масштабована система (10+ камер, дашборд) | 7–12 тижнів |
| Сертифікація як система безпеки (SIL) | 20+ тижнів |
Наш досвід
Більше 7 років розробляємо системи комп'ютерного зору для промисловості. Реалізували 50+ проєктів з безпеки персоналу: від детекції в зоні преса до моніторингу периметра складу. Середнє зниження хибних тривог — 80% після впровадження нашої геопросторової логіки. Ми маємо досвід сертифікації SIL 2 та гарантію на систему 12 місяців. Вартість пілотного проєкту стартує від 15 000$. Зв'яжіться з нами для безкоштовної оцінки вашого проєкту. Замовте пілотне впровадження на одній камері — результат за 2 тижні.







