Откриване на обекти с YOLO11 на GX10
Учим локалния AI сървър да гледа камери: да забелязва хора и превозни средства, да разбира дали са влезли в забранена зона и да записва събитията в база. Видеото се обработва на самата машина — не отива при облачен доставчик.
half=True (сега FP16 е quantize=16), таблицата с брой потоци, кадри в секунда и натоварване, обещанията „3 пъти по-бързо“ и „8+ потока“ — това бяха числа без източник и без измерване, .engine файла със „защитен“ път в системна папка, паролата на камерата в конфигурацията и в кода, разделянето на таблицата по месеци (в стария вид SQL-ът не работеше), партньорските и клиентски имена. Добавихме: двата начина за инсталация, описани от Ultralytics за DGX Spark (готов образ и ръчна инсталация), бележка за лиценза (AGPL-3.0 или платен), адресите на камерите в променливи на средата, поправена логика на зоните (клас на обекта и прозорец през полунощ), запис на пакети по време и по брой, а в „Измери сам“ — как да получиш свои числа. Правна рамка: разделът за съответствие е заменен с кратка кутия и съвет за проверка с юрист. Новост: Ultralytics вече водят документацията си с по-нов модел, YOLO26; YOLO11 остава поддържан, а кодът тук работи със същите команди — сменя се само името на файла.
psycopg2-binary и shapely на ARM64.01Какво ще научиш
- Как да пуснеш YOLO11 на машина от класа GB10 и да го ограничиш до нужните обекти.
- Как да превърнеш модела в TensorRT „двигател“ (
.engine) и защо той е вързан за конкретната машина. - Как да опишеш охранявани зони с многоъгълници и да познаеш кога обект влиза в тях.
- Как да обработваш няколко камери паралелно, без да пазиш паролите им в файлове.
- Как да записваш събитията в PostgreSQL, без да пазиш кадри и лица.
- Какво да уредиш с юрист, преди да насочиш камера към хора.
02Преди да започнеш
- Машина от класа NVIDIA GB10 (например ASUS Ascent GX10 или DGX Spark) с DGX OS и достъп до терминала ѝ.
- Python 3 и интернет за първото изтегляне на пакетите и теглата на модела.
- Камера с RTSP поток или обикновен видеофайл за проба. За първите стъпки файлът е напълно достатъчен.
- PostgreSQL база. Ако нямаш, вдигни я по урока за n8n (там Postgres е в контейнер).
- Лиценз: Ultralytics публикуват кода и моделите YOLO11 под AGPL-3.0 и под платен Enterprise лиценз (по документацията им). За вътрешна проба това не пречи; преди да вградиш системата в продукт или услуга за клиент, прочети условията.
- Правна проверка: виж кутията в стъпка 9. Преди да обработваш поток с реални хора, говори с юрист.
03Стъпки
-
Какво ще стои къде
Защо на части? Така можеш да проверяваш всяка част отделно и да търсиш грешка там, където е.
Част За какво е cameras/cam01.yaml Описание на една камера и нейните зони (без пароли) detect.py Работници за камерите, проверка на зоните, записване в базата schema.sql Таблицата със събитията yolo11m.pt / .engine Моделът и неговият TensorRT вариант -
Среда: готов образ или ръчна инсталация
Ultralytics описват два пътя за DGX Spark. Вариант А е готовият образ за Arm64 — всичко нужно е вътре. Образът е с PyTorch 26.08, CUDA 13.4 и TensorRT 11.2 (по документацията към 03.10.2026) и иска достатъчно нов драйвер на машината.
bash · вариант А (образ)t=ultralytics/ultralytics:latest-nvidia-arm64 sudo docker pull $t sudo docker run -it --ipc=host --device nvidia.com/gpu=all $tВариант Б е ръчна инсталация. Ultralytics препоръчват версиите на PyTorch за CUDA 13, защото обикновените може да не са оптимизирани за тази машина. Пусни го във виртуална среда:
bash · вариант Б (ръчно)python3 -m venv ~/detekcia source ~/detekcia/bin/activate pip install -U pip pip install ultralytics psycopg2-binary shapely pyyaml pip install torch torchvision --index-url https://download.pytorch.org/whl/cu130Версията на
ultralyticsне е закачена: документацията на Ultralytics се променя бързо. След инсталацията запиши коя е сpip show ultralytics— така ще можеш да повториш резултата. Проверка, че видеокартата се вижда:bashnvidia-smi💡Предупреждение за версията на CUDAПри PyTorch 2.9.1 Ultralytics описват безобидно предупреждение, че „GB10 има възможности 12.1, а версията поддържа до 12.0“. Според документацията то може да се игнорира. „Memory-Usage: Not Supported“ вnvidia-smiсъщо е нормално — паметта е обща. -
Първа проба: модел и само нужните класове
YOLO11 се предлага в пет размера (
n,s,m,l,x). По-големият е по-точен и по-бавен. Тук избираме среден (yolo11m.pt) — разумен баланс за начало; кой е най-добър за теб, ще покаже собственото ти измерване. Теглата се теглят автоматично при първото пускане.Защо ограничаваме класовете? Моделът познава 80 вида обекти. Нам трябват само няколко — така има по-малко фалшиви тревоги и по-малко работа за обработка. Номерата са от набора COCO.
pythonfrom ultralytics import YOLO model = YOLO("yolo11m.pt") # 0 човек · 2 кола · 3 мотор · 24 раница · 26 дамска чанта · 28 куфар CLASSES = [0, 2, 3, 24, 26, 28] results = model("test_frame.jpg", classes=CLASSES, conf=0.45) for r in results: for box in r.boxes: cls = int(box.cls) print(model.names[cls], round(float(box.conf), 2), box.xyxy[0].tolist())Вместо
test_frame.jpgсложи свой кадър. Очакваш редове само за избраните класове. Ако искаш най-новия модел на Ultralytics, замениyolo11m.ptс името от тяхната документация (сега водят с YOLO26) — командите остават същите. -
TensorRT: компилирай модела за тази машина
Защо? TensorRT пренарежда мрежата така, че да работи най-бързо на конкретната видеокарта. Резултатът е файл
.engine. Ultralytics препоръчват този формат за DGX Spark.pythonfrom ultralytics import YOLO model = YOLO("yolo11m.pt") model.export( format="engine", imgsz=640, # размерът, който ще ползваш при работа quantize=16, # FP16; остарялото half=True вече не е препоръчително device=0, ) # създава yolo11m.engine⚠️Двигателят е вързан за машинатаПо документацията.engineфайлът е настроен за видеокартата и за версията на TensorRT/CUDA, с които е изграден. Не го разглеждай като преносим файл: след смяна на машина, образ или драйвер го изгради отново. Запази оригиналния.pt.Първите кадри след зареждане на двигателя могат да са по-бавни — това е нормално. Колко печелиш, виж в стъпка 8.
-
Зони: многоъгълници за всяка камера
Всяка камера има файл с нейните зони. Координатите са в пиксели на кадъра. Адресът на потока не е във файла — в него пише само името на променливата, където е той (стъпка 6).
yaml · cameras/cam01.yamlcamera_id: "CAM-01" rtsp_env: "CAM01_RTSP_URL" site_id: "SITE-01" zones: - id: "ZONE-A" name: "Склад — вход" type: "forbidden_zone" polygon: [[120, 200], [380, 200], [380, 650], [120, 650]] severity: "critical" classes: [0] # само хора - id: "ZONE-B" name: "Паркинг" type: "time_restricted" polygon: [[800, 400], [1200, 400], [1200, 900], [800, 900]] restricted_hours: {start: 22, end: 6} # от 22:00 до 06:00 severity: "medium" classes: [0, 2, 3] # хора, коли, моториЗащо точката е в долния край на рамката? Стъпалата на човека показват къде стои на земята; центърът на рамката може да е „във въздуха“ над зоната. Затова проверяваме средата на долния ръб.
-
Достъпът до камерите — само в променливи на средата
Адресът на RTSP потока обикновено съдържа потребител и парола. Не го пишем във файл, в код или в дневник — който види файла, вижда камерата.
bashexport CAM01_RTSP_URL='rtsp://<потребител>:<парола>@<адрес-на-камерата>:554/<път-на-потока>' export DATABASE_URL='postgresql://<потребител>:<парола>@localhost:5432/<база>'За проба със видеофайл сложи пътя му вместо адреса. Реалните стойности вземи от настройките на камерата и на базата; за по-постоянна работа ги пази в защитен файл с права само за теб, а не в историята на терминала.
-
Основният скрипт
Идеята: един процес за всяка камера гледа потока и пуска събития в обща опашка, а отделен процес ги пише в базата на пакети. Така бавна база не спира камерите. Опашката е с ограничен размер: ако се напълни, събитието се изпуска, а не блокира потока. Използваме метода
spawnза стартиране на процесите — с видеокарта в играта „fork“ е опасен.python · detect.pyimport multiprocessing as mp import os import queue import sys import time from datetime import datetime import cv2 import psycopg2 import yaml from psycopg2.extras import execute_batch from shapely.geometry import Point, Polygon from ultralytics import YOLO CLASSES = [0, 2, 3, 24, 26, 28] INSERT_SQL = """ INSERT INTO detections ( camera_id, site_id, detected_at, class_id, class_name, confidence, bbox_x1, bbox_y1, bbox_x2, bbox_y2, zone_id, zone_name, is_violation, severity ) VALUES ( %(camera_id)s, %(site_id)s, %(detected_at)s, %(class_id)s, %(class_name)s, %(confidence)s, %(x1)s, %(y1)s, %(x2)s, %(y2)s, %(zone_id)s, %(zone_name)s, %(is_violation)s, %(severity)s ) """ def check_zone_violation(bbox, cls, zones, now): """Връща зоните, в които е точката на стъпалата на обекта.""" x1, y1, x2, y2 = bbox foot = Point((x1 + x2) / 2, y2) hits = [] for z in zones: if cls not in z.get("classes", CLASSES): continue if not Polygon(z["polygon"]).contains(foot): continue if z["type"] == "forbidden_zone": hits.append(z) elif z["type"] == "time_restricted": start = z["restricted_hours"]["start"] end = z["restricted_hours"]["end"] h = now.hour # прозорец през полунощ (22 -> 6) или в рамките на деня inside = (h >= start or h < end) if start > end else (start <= h < end) if inside: hits.append(z) return hits def camera_worker(cfg_path, events): with open(cfg_path, encoding="utf-8") as f: cfg = yaml.safe_load(f) url = os.environ[cfg["rtsp_env"]] # адресът идва от средата model = YOLO("yolo11m.engine", task="detect") cap = cv2.VideoCapture(url) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # само най-пресният кадър while True: ok, frame = cap.read() if not ok: # прекъсната връзка: опитай пак time.sleep(1) cap.release() cap = cv2.VideoCapture(url) continue now = datetime.now().astimezone() # местно време с часова зона for r in model(frame, classes=CLASSES, conf=0.45, verbose=False): for box in r.boxes: cls = int(box.cls) bbox = box.xyxy[0].tolist() hits = check_zone_violation(bbox, cls, cfg["zones"], now) z = hits[0] if hits else None event = { "camera_id": cfg["camera_id"], "site_id": cfg["site_id"], "detected_at": now.isoformat(), "class_id": cls, "class_name": model.names[cls], "confidence": round(float(box.conf), 3), "x1": bbox[0], "y1": bbox[1], "x2": bbox[2], "y2": bbox[3], "zone_id": z["id"] if z else None, "zone_name": z["name"] if z else None, "is_violation": z is not None, "severity": z["severity"] if z else None, } try: events.put_nowait(event) except queue.Full: pass # по-добре изпусната проба, отколкото спрян поток def db_writer(events): conn = psycopg2.connect(os.environ["DATABASE_URL"]) batch, last = [], time.monotonic() while True: try: batch.append(events.get(timeout=0.5)) except queue.Empty: pass if batch and (len(batch) >= 50 or time.monotonic() - last >= 1.0): with conn, conn.cursor() as cur: # "with conn" потвърждава записа execute_batch(cur, INSERT_SQL, batch) batch, last = [], time.monotonic() if __name__ == "__main__": ctx = mp.get_context("spawn") events = ctx.Queue(maxsize=1000) procs = [ctx.Process(target=db_writer, args=(events,), daemon=True)] for path in sys.argv[1:]: # python detect.py cameras/cam01.yaml ... procs.append(ctx.Process(target=camera_worker, args=(path, events), daemon=True)) for p in procs: p.start() for p in procs: p.join()Първо пусни с една камера (
python detect.py cameras/cam01.yaml), после добавяй. Всеки работник зарежда свой екземпляр на модела — следи паметта сnvidia-smiиdocker stats, докато добавяш потоци. -
Таблицата със събитията
Какво пазим: камера, обект, клас, увереност, рамка, зона и време. Какво не пазим: кадри и лица. Така базата отговаря на „кога и къде“, без да събира излишно.
sql · schema.sqlCREATE TABLE IF NOT EXISTS detections ( id BIGSERIAL PRIMARY KEY, camera_id VARCHAR(64) NOT NULL, site_id VARCHAR(64) NOT NULL, detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), class_id SMALLINT NOT NULL, class_name VARCHAR(32) NOT NULL, confidence NUMERIC(4,3) NOT NULL, bbox_x1 NUMERIC(8,2), bbox_y1 NUMERIC(8,2), bbox_x2 NUMERIC(8,2), bbox_y2 NUMERIC(8,2), zone_id VARCHAR(64), zone_name VARCHAR(128), is_violation BOOLEAN NOT NULL DEFAULT FALSE, severity VARCHAR(16) ); CREATE INDEX IF NOT EXISTS idx_det_camera_time ON detections (camera_id, detected_at DESC); CREATE INDEX IF NOT EXISTS idx_det_site_time ON detections (site_id, detected_at DESC); CREATE INDEX IF NOT EXISTS idx_det_violations ON detections (detected_at DESC) WHERE is_violation;bashpsql "$DATABASE_URL" -f schema.sql psql "$DATABASE_URL" -c "SELECT camera_id, class_name, is_violation, detected_at FROM detections ORDER BY detected_at DESC LIMIT 10"При много събития таблицата расте бързо. Разделяне по месеци (партициониране) и автоматично изтриване на стари записи са отделна тема — тук не са показани; срокът за пазене на данните се определя заедно с юриста (виж по-долу).
-
Измери сам
Скоростта зависи от модела, размера на кадъра, броя потоци и кодека на камерите. Затова в урока няма наши числа — измери своите върху твой кадър:
pythonimport time from ultralytics import YOLO def ms_per_frame(model, img, n=100): model(img, verbose=False) # загрявка t0 = time.perf_counter() for _ in range(n): model(img, verbose=False) return (time.perf_counter() - t0) * 1000 / n img = "test_frame.jpg" pt = ms_per_frame(YOLO("yolo11m.pt"), img) trt = ms_per_frame(YOLO("yolo11m.engine", task="detect"), img) print(f"PyTorch: {pt:.1f} ms/кадър") print(f"TensorRT: {trt:.1f} ms/кадър")За ориентир: в публикуваното от Ultralytics сравнение на DGX Spark (модел YOLO11n, 640 px, версия на пакета 8.3.249) PyTorch отнема 2,67 ms на изображение, TensorRT с FP32 — 1,95 ms, с FP16 — 1,01 ms. Това са техни числа за най-малкия модел, не наши и не за среден модел или няколко потока.
-
Хората в кадъра: правна рамка
⚖️Записът и анализът на хора имат правни изискванияВидеонаблюдението, включително автоматичното откриване на хора, е обработване на лични данни и попада под правилата за защита на личните данни (GDPR) и българското право. Основание, информиране на хората, срок на съхранение, достъп до записите и права на засегнатите са въпроси, които зависят от обекта и целта. Препоръка: преди да пуснеш система с реални камери, провери с юрист и запиши решенията му. Този урок е технически и не е правна консултация.- Технически добра практика, която улеснява юриста: пази само рамки, клас и време — не кадри, не лица, не разпознаване на конкретни хора.
- Пази данните колкото е нужно и не повече, и ограничи кой има достъп до таблицата.
- Информирай хората на обекта по начина, който юристът ти препоръча.
04Проверка
- Избрал си вариант на среда и
nvidia-smiпоказва видеокартата. - Пробният кадър дава рамки само за избраните класове.
- Имаш
yolo11m.engine, изграден на тази машина, и си сравнил скоростта му с.ptвърху свой кадър. - Зоната реагира, когато човек стои вътре в многоъгълника, и мълчи, когато е отвън.
- Адресът на камерата е в променлива на средата, не във файл.
- Таблицата е създадена и в нея се появяват събития от една камера. Чак тогава добавяш втора.
- Преди да насочиш реална камера към хора — говорил си с юрист.
Тест
1. Защо подаваме на модела classes=[0, 2, 3, 24, 26, 28]?
2. Можеш ли да копираш yolo11m.engine на друга машина с друга видеокарта и да го ползваш?
3. Къде е правилно да стои адресът на камерата с паролата?
4. Какво е най-разумното преди пускането на система с реални камери?
05Какво следва
06Източници
- Ultralytics: YOLO на NVIDIA DGX Spark 🔒 локално — образ, ръчна инсталация, TensorRT, сравнението със скоростите.
- Ultralytics: износ към TensorRT — аргументи (
quantize,imgsz), двигателят е вързан за видеокартата. - Ultralytics: YOLO11 — размери на моделите, лицензи AGPL-3.0 и Enterprise.
- Набор COCO — номерата на класовете.
- NVIDIA DGX Spark: хардуер · ASUS Ascent GX10: технически данни — процесор, обща памет, 1 PFLOP при FP4.