Детектор говорит «человек в кадре», но роботу нужно знать «человек в 2,4 метрах». Связка YOLOv8 и камеры Orbbec решает это дёшево и надёжно: детекция идёт по цветному кадру, а расстояние до каждого бокса читается из карты глубины. Схема называется «detect on RGB, measure on depth» — она выигрывает у детекции по глубине и по точности боксов, и по наличию готовых весов. В этом гайде соберём полный скрипт на pyorbbecsdk и ultralytics: включим выравнивание глубины на цветной кадр, возьмём дистанцию по валидным пикселям бокса через перцентили (медиана по всему bbox — ненадёжно, объясню почему), отрисуем результат в OpenCV. Камера — Femto Bolt: iToF с диапазоном 0,1–5,5 м и RGB 4K, то есть и дистанции меряются уверенно, и мелкие объекты детектор находит даже на дальнем плане.
Где эта связка приносит пользу сразу: навигация AMR (пешеход и его дистанция для планировщика), контроль опасной зоны у манипулятора, инспекция заполненности зон, интерактивные инсталляции. Важное свойство схемы — она агностична к детектору: завтра вы замените YOLOv8 на специализированную модель под свою предметную область, а слой измерения дистанции останется прежним. Поэтому и код ниже разделён так: детекция — один блок, глубина — другой, склеивает их только функция расстояния.
Что понадобится
- камера Orbbec (пример на Femto Bolt);
- Python 3.8+ с пакетами
pyorbbecsdk,ultralytics,opencv-python,numpy; - средний GPU не обязателен:
yolov8nна CPU даёт несколько FPS, на GPU — видеореальное время; - 10 минут.
Пошаговая настройка
Шаг 1. Ставим окружение
python3 -m venv venv && source venv/bin/activate
pip install ultralytics opencv-python numpy
# pyorbbecsdk ставится из исходников или готовым wheel под вашу платформу —
# см. инструкции в репозитории проекта
Первый запуск ultralytics сам скачает веса yolov8n.pt — лёгкой модели, которой для этой задачи достаточно. Если пайплайн пойдёт на встроенную плату, возьмите yolov8n с экспортом в ONNX/TensorRT — но начните с обычного запуска.
Про производительность инференса заранее: ultralytics сам выберет доступное устройство — CUDA при наличии GPU, иначе CPU. На десктопной NVIDIA кадр обрабатывается за единицы миллисекунд; на CPU для yolov8n закладывайте 100–200 мс на кадр. Проверить, где реально крутится модель, можно логом первого запуска — там написано устройство. Если пайплайн уедет на встроенную плату без GPU, экспорт в ONNX или OpenVINO снимет лишние накладные расходы, но это отдельная итерация — не смешивайте её с первой отладкой.
Шаг 2. Открываем камеру и включаем выравнивание
Ключ ко всей задаче — кадры depth и color в одном пиксельном базисе. Выравнивание делаем на стороне FrameSet:
import numpy as np
from pyorbbecsdk import Pipeline, Config, OBFrameType, OBStreamType
pipeline = Pipeline()
config = Config()
config.enable_stream(OBStreamType.OB_STREAM_COLOR) # профили потоков —
config.enable_stream(OBStreamType.OB_STREAM_DEPTH) # сверьте с examples вашей версии SDK
pipeline.start(config)
frames = pipeline.wait_for_frames(100)
aligned = frames.align_to(OBFrameType.OB_FRAME_COLOR) # сверьте с examples
color_frame = aligned.get_color_frame()
depth_frame = aligned.get_depth_frame()
h, w = color_frame.get_height(), color_frame.get_width()
color = np.asarray(color_frame.get_data()).reshape(h, w, 3)
dh, dw = depth_frame.get_height(), depth_frame.get_width()
depth = np.asarray(depth_frame.get_data(), dtype=np.uint16).reshape(dh, dw)
Конкретные имена конструкторов и метод выравнивания в pyorbbecsdk менялись между версиями — берите ближайший пример из репозитория SDK и вставляйте в этот каркас; логика ниже от имён не зависит. Без выравнивания пиксели depth и color показывают разные точки сцены (камеры разнесены на корпусе), и «глубина в центре бокса» была бы глубиной чужого места.
Шаг 3. Детекция и дистанция
import cv2
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
def distance_for_bbox(depth: np.ndarray, box, lo=300, hi=6000) -> float:
x1, y1, x2, y2 = map(int, box)
patch = depth[y1:y2, x1:x2]
valid = patch[(patch > lo) & (patch < hi)] # мм
if valid.size < 20:
return float("nan")
return float(np.percentile(valid, 25)) / 1000.0 # метры
while True:
frames = pipeline.wait_for_frames(100)
if frames is None:
continue
aligned = frames.align_to(OBFrameType.OB_FRAME_COLOR)
color_frame = aligned.get_color_frame()
depth_frame = aligned.get_depth_frame()
if color_frame is None or depth_frame is None:
continue
h, w = color_frame.get_height(), color_frame.get_width()
color = np.asarray(color_frame.get_data()).reshape(h, w, 3)
dh, dw = depth_frame.get_height(), depth_frame.get_width()
depth = np.asarray(depth_frame.get_data(), dtype=np.uint16).reshape(dh, dw)
results = model.predict(color, verbose=False)[0]
for r in results.boxes:
cls = model.names[int(r.cls)]
dist = distance_for_bbox(depth, r.xyxy[0].tolist())
label = f"{cls} {dist:.2f} m" if dist == dist else f"{cls} ?"
x1, y1, x2, y2 = map(int, r.xyxy[0].tolist())
cv2.rectangle(color, (x1, y1), (x2, y2), (0, 200, 0), 2)
cv2.putText(color, label, (x1, max(y1 - 8, 16)),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 200, 0), 2)
cv2.imshow("yolov8 + depth", color)
if cv2.waitKey(1) == 27:
break
pipeline.stop()
cv2.destroyAllWindows()
Шаг 3-бис. Маски вместо боксов: YOLOv8-seg
Когда объекты стоят плотно, бокс захватывает фон: расстояние через перцентиль всё равно чаще всего спасает, но аккуратнее взять сегментационную модель — yolov8n-seg.pt из того же пакета ultralytics. Она отдаёт маски классов, и дистанцию можно считать строго по пикселям объекта: замена в коде минимальная — вместо results.boxes идёте по results.masks и применяете ту же логику перцентиля к пикселям маски. Плата — инференс дороже на 30–50%; для задач, где объекты разрежены, боксы остаются правильным выбором. Держите оба варианта в арсенале: переключение — это веса и десять строк.
Шаг 4. Проверяем точность
Поставьте мишень на известном расстоянии (рулетка, разметка на полу), посмотрите показания. ToF-камера типа Femto Bolt в диапазоне до 5,5 м даёт ошибку в сантиметры на ровных поверхностях; основной вклад в погрешность вносят края объектов и дырки данных. Проверьте три материала: матовый картон (идеальный случай), тёмная ткань и стеклянная поверхность — на последней любое оптическое измерение честно вернёт мусор, и это надо закладывать в логику приложения.
Проверку сделайте протоколом, а не разовым взглядом: таблица «эталонное расстояние — показание — ошибка» на пяти дистанциях, снятая в два разных дня. Такой протокол за десять минут отвечает на главный вопрос внедрения — доверять ли цифре — и сразу показывает систематическое смещение, если оно есть. Смещение в сантиметры на постоянной основе лечится калибровочной константой в коде, а случайный разброс — только усреднением по кадрам.
Разбор кода
Самое важное — функция distance_for_bbox. Наивный подход «глубина в центре бокса» ломается на первом же объекте с дыркой в данных: центр может попасть на неотражающий участок, и вы получите ноль или расстояние до стены позади. Мы берём все валидные пиксели бокса и считаем 25-й перцентиль: он устойчиво указывает на переднюю поверхность объекта, а не на фон через дырки. Почему не медиана? В боксе «человек у стены» пикселей стены через дырки может быть больше, чем пикселей человека — медиана тогда уедет на стену. Нижняя четверть распределения — компромисс: часть шума отсечена, к передней плоскости объекта перцентиль чувствителен.
Границы lo/hi — это ваша рабочая зона в миллиметрах: они отсекают мусор рядом с камерой и «пролетающие» дальние значения. Для Femto Bolt естественная зона 300–5500 мм, для других камер смотрите паспортный диапазон. Порог valid.size < 20 отсекает боксы, где глубины почти нет: лучше честный «неизвестно», чем ложная цифра, особенно если дистанция идёт в логику безопасности робота.
Про производительность: детектор запускается на каждом кадре RGB, глубина только читается — тяжёлая часть одна. Если поток не нужен видеореальный, обрабатывайте каждый n-й кадр: для замера расстояний 5–10 Гц обычно достаточно, а CPU освобождается в разы.
Отдельная тема — согласованность кадра и детекции. Мы берём color и depth из одного FrameSet — они сняты в один момент, и это правильно. Детекция при этом выполняется позже, и на быстро движущемся объекте бокс уже «в прошлом». Для робототехники это лечится не гонкой за FPS, а честной меткой времени: тащите timestamp кадра в результат детекции, и планировщик сам оценит возраст измерения. Замер «сколько метров до человека прямо сейчас» и «сколько метров было 80 миллисекунд назад» — разные величины, и разница критична при скоростях выше метра в секунду.
| Камера | Технология | Кому подходит в связке с YOLOv8 |
|---|---|---|
| Femto Bolt | iToF, 0,1–5,5 м, RGB 4K | универсальный выбор: дистанции + мелкие объекты на 4K |
| Gemini 335L | активная стерео, база 100 мм | недорогой контур до 8–10 м, робототехника и AMR |
| Astra 2 | структурированный свет | короткая зона, офис и склад настольного масштаба |
Характеристики и актуальные цены — в карточках моделей; сравнение камер под конкретную сцену лучше делать на живом тесте.
Частые проблемы
Расстояние будто от другой сцены
Не включено выравнивание depth на color, либо выравнивание применено, но размерности кадров перепутаны местами. Проверка за минуту: наведите на рамку бокса угол карты глубины, наложенной поверх RGB полупрозрачно, — контуры сцены должны совпасть. Если не совпадают, вернитесь к шагу выравнивания и сверьте, к какому кадру вы приводите depth, и в каком порядке достаёте кадры из выровненного FrameSet. Имена методов и константы сверьте с examples вашей версии SDK.
Дистанция скачет от кадра к кадру
Обычно виноваты дырки и края. Во-первых, убедитесь, что считаете по валидным пикселям с фильтром диапазона, а не по сырому bbox. Во-вторых, добавьте временное сглаживание: экспоненциальное скользящее среднее дистанции по кадрам (dist = 0.7 * dist + 0.3 * new) убирает дрожание на статичных объектах. Если объект движется быстро, сглаживание внесёт отставание — тогда лучше увеличить bbox-отступ внутрь и считать по более плотному ядру.
Дистанция систематически занижена или завышена
Если ошибка постоянная и по знаку, ищите систему, а не случай: типовые причины — считаете до задней поверхности объекта (перцентиль стоит поднять), в бокс попадает стол под объектом (бокс шире силуэта), либо вы сравниваете расстояние до камеры с рулеткой до пола под объектом. Проверьте базу сравнения: почти все камеры отдают дистанцию по оптическому центру объектива, а рулетку вы прикладываете к полу. Случайный разброс — это другое лечение: усреднение по кадрам, плотнее ядро перцентиля, проверка вибрации монтажа камеры.
Низкий FPS на встроенной плате
Разложите бюджет: детекция на yolov8n, входное изображение для модели можно уменьшать (модель всё равно приводит кадр к своим 640), глубину не конвертировать в float без нужды. Уберите cv2.imshow в headless-режиме — отрисовка на слабых платах дорогая. Если этого мало, детектируйте через кадр, но измеряйте дистанцию реже, чем детектируете: бокс на пару кадров живёт, глубину в нём можно перечитывать чаще.
Железо под эту задачу есть в каталоге Orbbec на luwam.ru. Чаще всего берут Femto Bolt (iToF 0,1–5,5 м, RGB 4K) и Gemini 335L; начало работы с Python и OpenCV мы разбирали в отдельном квикстарте. Поможем подобрать камеру, дадим консультацию по SDK, выставим счёт для юрлица, гарантия 1 год.
Камеры Orbbec в наличии на LUWAM
- Orbbec Femto Bolt — iToF 4K, замена Azure Kinect — 99 900 ₽
- Orbbec Gemini 335L — стерео для роботов и SLAM — 99 900 ₽
- Orbbec Astra 2 — структурированный свет, сканирование помещений — 99 900 ₽
Весь каталог Orbbec · консультация по SDK · счёт для юрлица в день обращения · гарантия 1 год.

