Вариант детекции обьектов с помощью CodeProject.AI работал хорошо, но пришлось отдать под него отдельный, хоть и старый, ноутбук, который требовал отдельного питания, заметно грелся, жужжал вентилятором.
Поэтому, с появлением компактного девайса с arm64 и 4 Гб ОЗУ, захотелось перенести всё на него.

К счастью, оказалось что есть готовый Docker и для arm64, достаточно только при создании указать codeproject/ai-server:arm64.

Всё установилось и заработало, причем даже чуть побыстрее чем на старом ноутбуке, и всё в маленькой бесшумной коробочке с питанием от USB.
Но хотелось что-то улучшить...

Стандартно, там внутри для распознавания обьектов используется нечто под названием YOLO 6.5. (да, я понятия не имел что это такое. Работает и хорошо, а что?)
При этом через пользовательский интерфейс система сообщает, что можно обновить до более свежей версии, но при попытке обновить штатным образом, нажатием кнопочек на экране, всё только портится и работать перестает: что-то не удалось найти, что-то не удалось загрузить и так далее.

При этом сама система - это как бы универсальный AI-сервер, на котором можно запускать разные модули, из которых по сути нужен только один.
А что если найти и запустить его отдельно?

Так выяснилось, что YOLO - это довольно известная штука от Ultralytics https://github.com/ultralytics/ultralytics, и актуальная версия там уже 11, а не 6.5.
И что всё это можно запустить под python.
Правда, есть нюанс: я не знаю python, но когда это кого останавливало?

Собирать всё это решено было также в докере, хотя бы для того чтобы ничего не поломать в работающей системе своими экспериментами.

docker run -ti --name t1 -v /tmp:/tmp -p 2222:22 -p 5000:5000 debian

За основу берем Дебиан, подключая туда системный /tmp, пробрасывая порт ssh 22 как 2222 и 5000 для веб-доступа.
В данном случае /tmp проброшен был ради упрощения обмена файлами, но как оказалось это была отличная идея, которая затем пригодится.

apt update
apt install vim openssh-server

vim /etc/startup

#!/bin/sh

mkdir -p /var/run/sshd
/usr/sbin/sshd -D

chmod 755 /etc/startup

Устанавливаем пароль - заходить и работать удаленно:
passwd

Разрешаем логин рутом (всё равно это тест и сюда посторонние не ходят)
vim /etc/ssh/ssdh_config

...
PermitRootLogin yes
...

Теперь можно спокойно ставить всё что хочется:
apt install python3-pip
apt install python3.11-venv

Найденные в интернетах инструкции советуют установить окружение:

python3 -m venv yolo
cd yolo
. bin/activate

Устанавливаю пакет от Ultralytics и отладочный вебсервер Flask
pip install ultralytics
pip install Flask

И пишу свою первую программу на python:

from flask import Flask, request, jsonify, make_response

app = Flask(__name__)

import ultralytics
import os

model = ultralytics.YOLO('yolo11n.pt')

@app.route('/detect', methods=['POST'])
def predict():

    file = request.files['file']
    filename = file.filename
    file.save(os.path.join('/tmp/', file.filename))

    results = model.predict(os.path.join('/tmp/', file.filename))

    os.remove(os.path.join('/tmp/', file.filename))
    for result in results:
        #result.show()  # display to screen
        #result.save(os.path.join('/tmp/', "result_"+file.filename))
        # Return response
        response = make_response(result.to_json())
        response.headers["Access-Control-Allow-Origin"] = "*"
        return response

В данном случае создаем примитивный веб-сервер, умеющий принимать файл по URL /detect, запускающий обработку на модели yolo11n.pt.
Файл в процессе сохраняется в каталоге /tmp, который отображен из основной ОС и по факту является ОЗУ (tmpfs), то есть работает максимально быстро.
Результат обработки вроде как мог бы быть выведен на экран - но у нас сервер, поэтому эта функция просто закомментирована.
Также, результатом мог бы быть файл с отрисованными рамками - но я уже их рисую отдельно, и так как мне нужно было - поэтому эта функция тоже закомментирована.

Остается собственно список обьектов, который выводится в формате JSON. А заголовок "Access-Control-Allow-Origin=*" позволит обойти ограничения на кросс-доменные ссылки.

Пробный запуск:
flask --app detect run --host 0.0.0.0

По 5000 порту удается подключить и передать картинку, а в ответ получить список обьектов - всё работает.
Но в документации пишут, что Flask - это отладочный сервер, а нужно запустить что-то более серьезное.

pip install uwsgi
uwsgi --http :5000 --wsgi-file detect.py --callable app --processes 4 --threads 4 --stats :9191

Вот теперь то же самое, но несколько процессов сразу, для возможных одновременных подключений.
Остается сделать скрипт запуска:

#!/bin/sh
PATH=/root/yolo8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH

cd /root/yolo

. bin/activate

setsid uwsgi --http :5000 --wsgi-file detect.py \
        --callable app --processes 4 --threads 4 \
        --stats :9191 > /dev/null 2>/var/log/yolo_error.log &

И вызывать его из файла /etc/startup

Перезапускаем контейнер:
docker commit t1 yolo
docker stop t1
docker rm t1
docker run -d --name yolo -v /tmp:/tmp -p 2222:22 -p 5000:5000 yolo /etc/startup

Теперь по порту 5000 доступен детектор изображений, а по порту 2222 можно подключиться по ssh и поправить что-то, если потребуется.

Немного изменился формат JSON-ответа, поэтому вносим изменения в модуль MyImgAI.pm, примерно так:

my $d = from_json($1);
-- if($d->{count}){
++ if(defined $d){

my $cnt = 0;
-- foreach my $x (@{$d->{predictions}}){
++ foreach my $x (@$d){

-- my $px_max   = $x->{x_max};
-- my $px_min   = $x->{x_min};
-- my $py_max   = $x->{y_max};
-- my $py_min   = $x->{y_min};
++ my $px_max   = $x->{box}->{x1};
++ my $px_min   = $x->{box}->{x2};
++ my $py_max   = $x->{box}->{y1};
++ my $py_min   = $x->{box}->{y2};

-- my $key = $ch.'_'.$x->{label}.'_'.$px_max.'_'.$px_min.'_'.$py_max.'_'.$py_min;
++ my $key = $ch.'_'.$x->{name}.'_'.$px_max.'_'.$px_min.'_'.$py_max.'_'.$py_min;

-- if($x->{label} eq 'person'){
-- $im->rectangle($x->{x_min},$x->{y_min},$x->{x_max},$x->{y_max},$red);
++ if($x->{name} eq 'person'){
++ $im->rectangle($x->{box}->{x1},$x->{box}->{y1},$x->{box}->{x2},$x->{box}->{y2},$red);

-- elsif($x->{label} eq 'car'){
-- $im->rectangle($x->{x_min},$x->{y_min},$x->{x_max},$x->{y_max},$blue);
++ elsif($x->{name} eq 'car'){
++ $im->rectangle($x->{box}->{x1},$x->{box}->{y1},$x->{box}->{x2},$x->{box}->{y2},$blue);

-- $im->rectangle($x->{x_min},$x->{y_min},$x->{x_max},$x->{y_max},$green);
++ $im->rectangle($x->{box}->{x1},$x->{box}->{y1},$x->{box}->{x2},$x->{box}->{y2},$green);

Теперь все работает почти как раньше, только без оболочки в виде дополнительного сервера с не очень понятными ошибками обновления.
И чуть быстрее.

Комментарии (16)


  1. Dynasaur
    08.12.2024 13:11

    с появлением компактного девайса с arm64 и 4 Гб ОЗУ

    что за девайс?


    1. JBFW Автор
      08.12.2024 13:11

      Опыты с TV-box, позже напишу


  1. aborouhin
    08.12.2024 13:11

    Автору за отвагу и достигнутый результат, несомненно, плюсик, но зачем sshd внутри контейнера?.. Ну и делать docker-контейнер на debian (даже не -slim версии), с установкой внутрь него и vim, который был нужен один раз, и flask, который в итоге оказался не нужен... (в питоновской части тоже наверняка можно более минимальным вариантом ограничиться, но тут я не специалист) - это вот вообще не best practice. Контейнер должен быть минимальным. А потом ещё про его обновление подумать бы...


    1. JBFW Автор
      08.12.2024 13:11

      Контейнер можно использовать для масштабирования сервисов, а можно - как аналог виртуалки.
      Вот в данном случае это и есть виртуалка - быстро создается, и сравнительно безопасно - можно понаустанавливать всяких программ, не рискуя снести рабочую систему. Накосячил - удалить образ и создать заново.

      Поэтому и ssh, чтобы подключаться напрямую, и vim, чтобы был нормальный редактор.
      Насчет flack вообще не в курсе, но так показалось что uwsgi именно его использует внутри себя. Но я python не знаю ))


      1. aborouhin
        08.12.2024 13:11

        Как аналог виртуалки обычно используют LXC, но хозяин - барин :) В любом случае поднимать внутри контейнера SSH несколько бессмысленно, т.к. можно просто вызвать "docker exec -it my_container /bin/sh". А так Вы пробросили SSH (да ещё и с рутом и парольным доступом) в контейнер, который собран руками и в нём даже критичные обновления безопасности устанавливаться не будут, пока Вы его вручную не пересоберёте.


        1. JBFW Автор
          08.12.2024 13:11

          Совершенно верно. Потому что стоит это физически в отдельном удаленном помещении, в отдельной сети, и для того чтобы можно было загрузить туда какой-нибудь файл теперь достаточно простого scp filename servername, а не плясок с бубном с заходами в ОС самого девайса.

          Поскольку это у нас контейнер - не вижу принципиальной разницы между рутом в контейнере и юзером в этом же контейнере - и тот и другой могут при проникновении злоумышленника что-нибудь напортить, но и того и другого можно так же снести вместе с зараженным контейнером из базовой ОС, и восстановить из сохраненной копии.

          А вот автообновления, даже критические - это зло, которое хуже чем вообще ничего не обновлять. Не должно оно САМО обновляться, иначе вас рано или поздно ждет сюрприз "у нас почему-то всё сломалось!!!111"


          1. aborouhin
            08.12.2024 13:11

            А вот автообновления, даже критические - это зло, которое хуже чем вообще ничего не обновлять.

            Смелое утверждение. Конечно, лучше иметь тестовый контур, на котором предварительно раскатываются обновления и тестируется, что ничего не сломалось. Но это такое дорогое удовольствие и по времени, и по деньгам, поэтому если пара часов простоя не критичны - Debian stable / Ubuntu LTS с unattended-upgrades вполне себе компромисс. У меня годами так две дюжины серверов автообновляются, инциденты были единичными и для меня приемлемыми. Да и даже апгрейд на новый релиз (это уже вручную, само собой) редко что-то ломает.


      1. NAI
        08.12.2024 13:11

         а можно - как аналог виртуалки.

        Нельзя. Потому, контейнеры для сервисов - это кусок архитектуры. Достаточно задаться вопросами: что произойдет если pid 1 (а его назначаете вы, при билде) в контейнере завершит работу (или умрет), что произойдет с вашим vim\ssh-соединением и прочим софтом


        1. JBFW Автор
          08.12.2024 13:11

          и что страшного? Оно вылетит. У вас никогда компьютер не выключался по питанию?

          Вот, практический эксперимент буквально онлайн:
          Захожу, PID 1 = /etc/startup, скрипт висит за счет sshd -D.
          Убиваю sshd - всё, скрипт завершился, контейнер вырубился.

          Что критичного произошло?
          Во-первых, его можно перезапустить снаружи.
          Во-вторых, можно завернуть sshd в бесконечный цикл while...

          Просто тут контейнер используется не для запуска сервиса, а для запуска рабочей среды для программирования и отладки, вот и вся разница.
          Правильнее назвать это "песочницей"


          1. NAI
            08.12.2024 13:11

            Убиваю sshd - всё, скрипт завершился, контейнер вырубился.

            Что критичного произошло? Во-первых, его можно перезапустить снаружи.

            Вы ответили на первую половину вопроса, но проигнорировали вторую:

            ...произойдет с вашим vim\ssh-соединением и прочим софтом

            Ответ банален - он умрет. Все еще не видите проблемы? Давайте чтобы было понятнее рассмотрим через призму "У вас никогда компьютер не выключался по питанию?" Итак у вас есть ПК который при старте запускает ну... скажем word и при закрытии окна выключается. Когда это будет удобно? Когда вы писатель и кроме ворда ничем не пользуетесь - включил ПК, набрал текст, закрыл окно - все. Даже больше, система мониторит процесс ворда, и если он завис, система сама перезапустит весь ПК - вот это вот из перовой линии поддержки "перегрузите ПК".

            Но вам лично мало ворда и накатываете внутрь Акцесс (БД), Эксель, почту и еще файловую шару делаете. И тут вы получаете оооогромный пучек проблем:

            Во-первых, если кто-то писал в БД\файловую шару, а вы закрыли ворд, то что произойдет с остальными процессами? kill -9? а данные? Восстанавливали когда-нибудь поломанные БД? Отличный опыт (не для прода). Что произойдет если процессы подняты, как нормальные сервисы (или отдельные контейнеры) - kill 15, т.е. процессы могут спокойно записать данные, закрыть транзакции, соединения и спокойно умереть. И даже больше, если у вас упал любой сервис, можно написать обработчик ошибок, ну там не знаю "БД упала, пишем в текстовые файлики" или nginx отвалился - вот вам заглушка.

            Во-вторых, мониторинг. У вас докер (или подман, или куб) мониторят этот самый pid 1, окей, допустим это сферический идеальный цикл. Но другие сервисы внутри контейнера берут и падают. Докер про них ничего не знает, по этому вы вкорячиваете туда агента мониторинга. Допустим monit - маленький, легкий, сам рестартит сервисы. И тут получается "отличная" история- ваши сервисами управляют аж две системы мониторинга. KISS нервно курит в сторонке

            Есть еще пучек проблем типа распределения ресурсов, доступов (это вроде победили, но осадочек остался). В общем, перечисленные проблемы должны сказать вам что забивать гвозди микроскопом конечно можно, но нах*я зачем если есть молоток?


            1. ProstoSerge
              08.12.2024 13:11

              Слушай чел, у него домашнее видеонаблюдение и он эксперементирует. Ты пришел сюда рассказывать как нужно випродакшене развернуть докер. Иди своему тимлиду рассказывай как ты умеешь.


      1. ProstoSerge
        08.12.2024 13:11

        Посмотрите в сторону Alpine Linux, для докера на Arm отлично подойдет. Маленький шустрый дистрибутив.


    1. NAI
      08.12.2024 13:11

      Контейнер должен быть минимальным.

      Как тебе такое Илон Маск?

      REPOSITORY                      TAG                        IMAGE ID       CREATED       SIZE
      build/trassir-daemon-cuda11.1   4.5.24.0-1232707-Release   695927c06b84   6 weeks ago   22.1GB

      Официальная сборка системы видеонаблюдения (все по трассировским инструкциям). Про compose они видимо не слышали


      1. JBFW Автор
        08.12.2024 13:11

        у меня 2.3GB получилось ...
        со всеми лишними пакетами. Есть куда расти ))


      1. JBFW Автор
        08.12.2024 13:11

        У меня 2.3GB получилось, со всеми лишними пакетами. Есть куда расти ))


      1. aborouhin
        08.12.2024 13:11

        Ааа!... :) Таких монстров не встречал в природе. Но вот полно любителей запихать в свой образ контейнера какой-нибудь nginx замшелой версии исключительно как reverse proxy, добавляющий HTTPS, и пофигу, что у меня свой nginx для этой задачи вообще на отдельной машине... бесят.