Знакът на КАГАМИ КАГАМИ
kagami.bg/academy · lesson · machine-readable viewUPDATED 2026-10-03
IDENTITY
module
GX10-04-118 · Real-time object detection with YOLO11 and TensorRT
series
GX10 (local AI server class: NVIDIA GB10, e.g. ASUS Ascent GX10 / DGX Spark)
level
Intermediate
duration
about 2 h
prerequisites
A GB10-class machine with DGX OS (Ubuntu, Arm64), shell access, Python 3, one or more RTSP-capable cameras (or a test video file), a PostgreSQL database
trust_label
UPDATED 2026-10-03 (rewritten against the Ultralytics documentation for DGX Spark, TensorRT export and YOLO11 as of 2026-10-03) · NOT TESTED (no GB10 machine available during the update; no command in this lesson was run by the authors)
versions
Ultralytics package: current release from PyPI (not pinned; the documented DGX Spark benchmark used 8.3.249) · YOLO11 weights yolo11n/s/m/l/x.pt · Ultralytics docs now lead with YOLO26, YOLO11 remains supported · Ultralytics NVIDIA ARM64 image: PyTorch 26.08, CUDA 13.4, TensorRT 11.2 (per docs)
license
Ultralytics code and YOLO11 models: AGPL-3.0 or a paid Enterprise license (check before commercial use)
language
human view: bg · english edition: /en/academy/gx10/ (same file name)
previous / next
GX10 series index / GX10 series index
PURPOSE

Build a multi-camera detection pipeline on a GB10-class server: YOLO11 restricted to security-relevant COCO classes, exported to a TensorRT engine, one worker process per RTSP stream, polygon zones per camera (forbidden or time-restricted) tested with the bounding-box foot point, and batched logging of detection events to PostgreSQL. Only box coordinates, class, confidence, time and zone are stored: no frames, no faces. Recording people has legal requirements (GDPR): a lawyer must be consulted before production use.

KEY CONCEPTS
COMMANDS / PATHS
CHECKLIST
NEXT MODULE

GX10 series index: kagami.bg/academy/gx10/ · offer: Quick experiment (kagami.bg/stalbata/)

SOURCES
TAGS
gx10nvidia-gb10arm64yolo11tensorrtobject-detectionrtsppostgresqlvideo-analytics
ОБНОВЕНО · 03.10.2026

Откриване на обекти с YOLO11 на GX10

Учим локалния AI сървър да гледа камери: да забелязва хора и превозни средства, да разбира дали са влезли в забранена зона и да записва събитията в база. Видеото се обработва на самата машина — не отива при облачен доставчик.

⏱ 2 ч Средно GX10 NVIDIA GB10 · 128 GB обща памет YOLO11 · TensorRT · RTSP · PostgreSQL
Ultralytics YOLO11 + TensorRT🔒 локално PostgreSQL🔒 локално
🔄
ОБНОВЕНО · 03.10.2026 — какво
Урокът е преработен по текущата документация на Ultralytics. Махнахме: закачената стара версия на пакета, остарелия аргумент half=True (сега FP16 е quantize=16), таблицата с брой потоци, кадри в секунда и натоварване, обещанията „3 пъти по-бързо“ и „8+ потока“ — това бяха числа без източник и без измерване, .engine файла със „защитен“ път в системна папка, паролата на камерата в конфигурацията и в кода, разделянето на таблицата по месеци (в стария вид SQL-ът не работеше), партньорските и клиентски имена. Добавихме: двата начина за инсталация, описани от Ultralytics за DGX Spark (готов образ и ръчна инсталация), бележка за лиценза (AGPL-3.0 или платен), адресите на камерите в променливи на средата, поправена логика на зоните (клас на обекта и прозорец през полунощ), запис на пакети по време и по брой, а в „Измери сам“ — как да получиш свои числа. Правна рамка: разделът за съответствие е заменен с кратка кутия и съвет за проверка с юрист. Новост: Ultralytics вече водят документацията си с по-нов модел, YOLO26; YOLO11 остава поддържан, а кодът тук работи със същите команди — сменя се само името на файла.
⚠️
Какво не сме пускали сами
При проверката нямахме машина от класа GB10. Нито една команда от този урок не е пусната от нас — сверени са с документацията на Ultralytics и NVIDIA, но няма етикет „ТЕСТВАНО“. Непроверени са още: стартирането на няколко потока едновременно, скоростите на твоя модел и камери, и инсталирането на psycopg2-binary и shapely на ARM64.

01Какво ще научиш

02Преди да започнеш

💡
Защо тази машина
Процесорът ѝ е Arm с 20 ядра, видеокартата е Blackwell, а 128 GB памет са обща за двете — моделът се зарежда веднъж и не се копира между памети. „1 PFLOP“ е мощност при FP4 и не е скоростта на този урок. Ultralytics препоръчват именно TensorRT за най-добра производителност на DGX Spark.

03Стъпки

  1. Какво ще стои къде

    Защо на части? Така можеш да проверяваш всяка част отделно и да търсиш грешка там, където е.

    ЧастЗа какво е
    cameras/cam01.yamlОписание на една камера и нейните зони (без пароли)
    detect.pyРаботници за камерите, проверка на зоните, записване в базата
    schema.sqlТаблицата със събитията
    yolo11m.pt / .engineМоделът и неговият TensorRT вариант
  2. Среда: готов образ или ръчна инсталация

    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 — така ще можеш да повториш резултата. Проверка, че видеокартата се вижда:

    bash
    nvidia-smi
    💡
    Предупреждение за версията на CUDA
    При PyTorch 2.9.1 Ultralytics описват безобидно предупреждение, че „GB10 има възможности 12.1, а версията поддържа до 12.0“. Според документацията то може да се игнорира. „Memory-Usage: Not Supported“ в nvidia-smi също е нормално — паметта е обща.
  3. Първа проба: модел и само нужните класове

    YOLO11 се предлага в пет размера (n, s, m, l, x). По-големият е по-точен и по-бавен. Тук избираме среден (yolo11m.pt) — разумен баланс за начало; кой е най-добър за теб, ще покаже собственото ти измерване. Теглата се теглят автоматично при първото пускане.

    Защо ограничаваме класовете? Моделът познава 80 вида обекти. Нам трябват само няколко — така има по-малко фалшиви тревоги и по-малко работа за обработка. Номерата са от набора COCO.

    python
    from 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) — командите остават същите.

  4. TensorRT: компилирай модела за тази машина

    Защо? TensorRT пренарежда мрежата така, че да работи най-бързо на конкретната видеокарта. Резултатът е файл .engine. Ultralytics препоръчват този формат за DGX Spark.

    python
    from 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.

  5. Зони: многоъгълници за всяка камера

    Всяка камера има файл с нейните зони. Координатите са в пиксели на кадъра. Адресът на потока не е във файла — в него пише само името на променливата, където е той (стъпка 6).

    yaml · cameras/cam01.yaml
    camera_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]      # хора, коли, мотори

    Защо точката е в долния край на рамката? Стъпалата на човека показват къде стои на земята; центърът на рамката може да е „във въздуха“ над зоната. Затова проверяваме средата на долния ръб.

  6. Достъпът до камерите — само в променливи на средата

    Адресът на RTSP потока обикновено съдържа потребител и парола. Не го пишем във файл, в код или в дневник — който види файла, вижда камерата.

    bash
    export CAM01_RTSP_URL='rtsp://<потребител>:<парола>@<адрес-на-камерата>:554/<път-на-потока>'
    export DATABASE_URL='postgresql://<потребител>:<парола>@localhost:5432/<база>'

    За проба със видеофайл сложи пътя му вместо адреса. Реалните стойности вземи от настройките на камерата и на базата; за по-постоянна работа ги пази в защитен файл с права само за теб, а не в историята на терминала.

  7. Основният скрипт

    Идеята: един процес за всяка камера гледа потока и пуска събития в обща опашка, а отделен процес ги пише в базата на пакети. Така бавна база не спира камерите. Опашката е с ограничен размер: ако се напълни, събитието се изпуска, а не блокира потока. Използваме метода spawn за стартиране на процесите — с видеокарта в играта „fork“ е опасен.

    python · detect.py
    import 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, докато добавяш потоци.

  8. Таблицата със събитията

    Какво пазим: камера, обект, клас, увереност, рамка, зона и време. Какво не пазим: кадри и лица. Така базата отговаря на „кога и къде“, без да събира излишно.

    sql · schema.sql
    CREATE 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;
    bash
    psql "$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"

    При много събития таблицата расте бързо. Разделяне по месеци (партициониране) и автоматично изтриване на стари записи са отделна тема — тук не са показани; срокът за пазене на данните се определя заедно с юриста (виж по-долу).

  9. Измери сам

    Скоростта зависи от модела, размера на кадъра, броя потоци и кодека на камерите. Затова в урока няма наши числа — измери своите върху твой кадър:

    python
    import 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. Това са техни числа за най-малкия модел, не наши и не за среден модел или няколко потока.

  10. Хората в кадъра: правна рамка

    ⚖️
    Записът и анализът на хора имат правни изисквания
    Видеонаблюдението, включително автоматичното откриване на хора, е обработване на лични данни и попада под правилата за защита на личните данни (GDPR) и българското право. Основание, информиране на хората, срок на съхранение, достъп до записите и права на засегнатите са въпроси, които зависят от обекта и целта. Препоръка: преди да пуснеш система с реални камери, провери с юрист и запиши решенията му. Този урок е технически и не е правна консултация.
    • Технически добра практика, която улеснява юриста: пази само рамки, клас и време — не кадри, не лица, не разпознаване на конкретни хора.
    • Пази данните колкото е нужно и не повече, и ограничи кой има достъп до таблицата.
    • Информирай хората на обекта по начина, който юристът ти препоръча.

04Проверка

Тест

1. Защо подаваме на модела classes=[0, 2, 3, 24, 26, 28]?

2. Можеш ли да копираш yolo11m.engine на друга машина с друга видеокарта и да го ползваш?

3. Къде е правилно да стои адресът на камерата с паролата?

4. Какво е най-разумното преди пускането на система с реални камери?

05Какво следва

06Източници

  1. Ultralytics: YOLO на NVIDIA DGX Spark 🔒 локално — образ, ръчна инсталация, TensorRT, сравнението със скоростите.
  2. Ultralytics: износ към TensorRT — аргументи (quantize, imgsz), двигателят е вързан за видеокартата.
  3. Ultralytics: YOLO11 — размери на моделите, лицензи AGPL-3.0 и Enterprise.
  4. Набор COCO — номерата на класовете.
  5. NVIDIA DGX Spark: хардуер · ASUS Ascent GX10: технически данни — процесор, обща памет, 1 PFLOP при FP4.