Привет! Меня зовут Влад Губайдулин, я full-stack разработчик в департаменте Логистика КОРУС Консалтинг. Весной на Хабре я рассказывал о том, как для дипломного проекта создавал приложение для отслеживания объектов спортивного мероприятия. Почитать об этом можно вот здесь.
Из моего пет-проекта вырос полноценный реальный проект. Знания и навыки в области нейронных сетей, трекинговых библиотек и компьютерного зрения, которые я приобрел, были использованы для разработки системы отслеживания транспортных средств на производственных территориях. Эта система основана на применении сверточной нейронной сети — технологии, позволяющей компьютерам «видеть» и интерпретировать визуальную информацию
Наш департамент разрабатывает и внедряет системы и ПО для логистики, поэтому развитие проекта было вполне закономерным. Но оно не случилось бы без участия коллег, которые вдохновили меня на эту идею, за что им спасибо.
Сегодня расскажу, в чем суть этой системы, остановлюсь на принципах работы, инструментах и архитектуре.
Я рассматриваю наше приложение как дополнение к существующим системам управления двором (YMS) и как способ более интеллектуального отслеживания транспортных средств на территории промышленных зон.
В современных системах трекинг этапов перемещения транспортных средств (въезд на территорию/разгрузка/погрузка и так далее) в основном осуществляется с помощью вторичных инструментов: камер для чтения номеров, датчиков парковки и карточек. Меня же интересует реализация подобной функции исключительно за счет обыкновенных камер наблюдения без дополнительных «железок» — это упростит и автоматизирует процесс.
Представьте систему видеонаблюдения, в которой отслеживание осуществляется с нескольких камер, каждая из которых имеет собственную трекинг-метку. Когда транспортное средство попадает в поле зрения камеры, она присваивает ему уникальный идентификатор. ТС перемещается и попадает в зону видимости другой камеры — новая камера также присваивает ID. Еще есть адаптер, который сопоставляет эти идентификационные номера при выезде ТС из одного кадра и появлении его в следующем по логике кадре. На основе этой логики формируются импровизированные контрольные точки.
В качестве преимуществ такого приложения — помимо собственно автоматизации — выделю несколько функций:
1. Анализ фактического нахождения транспортного средства на каждом этапе разгрузки. Поможет поддерживать и оптимизировать производительность загрузочно-разгрузочных пунктов.
2. Мгновенная реакция в случае, если транспортное средство оказалось в запретной зоне.
3. Учет срока пребывания ТС вне парковочного места или дока разгрузки — если машина долго стоит неподвижно, система уведомит о возможной поломке.
4. Идентификация свободного парковочного места или дока разгрузки — следующее транспортное средство можно вызвать на место автоматически..
5. Расчет скорости движения транспортного средства: при превышении допустимой скорости можно провести беседу с водителем при выезде.
Подобных готовых решений на рынке я пока не нашел ни у российских, ни у зарубежных поставщиков — встречаются лишь исследовательские работы по этой теме. Но если я что-то упустил и у вас есть информация о подобных разработках, пожалуйста, делитесь в комментариях.
Тут мы делаем DataScience, поэтому, разумеется, Python — наш лучший друг.
Для хранения данных используем SQLite, простая и легкая база данных.
Чтобы не заморачиваться с запросами к базе, а сразу работать с нужными объектами, возьмем SQLAlchemy.
Теперь перейдем к более интересной части — отслеживанию объектов в видео. Здесь на помощь приходят две мощные библиотеки: ByteTrack и YOLOv8.
ByteTrack — это инструмент, который помогает следить за несколькими объектами в видео. Делает это легко и эффективно, даже если объекты частично скрыты или находятся далеко от камеры.
YOLOv8 — это одна из самых современных моделей для обнаружения объектов, известна своей скоростью и точностью, что делает её идеальной для задач, выполняемых в режиме реального времени, таких как видеонаблюдение или автономное вождение.
И, конечно, библиотека OpenCV — это отличный инструмент для работы с изображениями и видео. Она позволяет делать практически всё: от простого чтения и записи видео до сложных операций, таких как распознавание лиц или фильтрация изображений.
С ее помощью вы можете изменять размер, поворачивать, улучшать качество изображения, определять контуры объектов. И все это даже иногда работает.
Конвейер обработки видеоизображения представлен в листинге 1. В нем можно найти пример подключения YOLO и ByteTrack, а также отрисовку с помощью OpenCV.
Листинг 1
async def main(self, is_show_frames):
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'Using device: {device}')
model = YOLO('yolov8l.pt').to(device)
cap = cv2.VideoCapture(self.VIDEO_PATH)
ret, frame = cap.read()
byte_tracker = BYTETracker(BYTETrackerArgs.BYTETrackerArgs())
while ret:
results = model(frame)[0]
detections = Detection.Detection.from_results(
pred=results.boxes.data.cpu().numpy(),
names=model.names)
car_detections = filter_detections_by_class(detections=detections, class_name="car")
truck_detections = filter_detections_by_class(detections=detections, class_name="truck")
detections = car_detections + truck_detections
tracks = byte_tracker.update(
output_results=detections2boxes(detections=detections),
img_info=frame.shape,
img_size=frame.shape
)
tracked_detections = match_detections_with_tracks(detections=detections, tracks=tracks)
for detection in tracked_detections:
tracked_objects_aud_id = await Recording().record_tracks_async(detection, self.CAMERA, self.AREA_EXIT)
if is_show_frames:
cv2.rectangle(frame,
(int(detection.rect.x), int(detection.rect.y)),
(int(detection.rect.x + detection.rect.width), int(detection.rect.y + detection.rect.height)),
(255, 255, 255), 2)
cv2.putText(frame,
f'{detection.class_name}_id#{tracked_objects_aud_id}',
(int(detection.rect.x), int(detection.rect.y) - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 255), 2)
if is_show_frames:
if self.IS_PARKING_EXIST:
parking_color = self.COLOR.get_current_parking_color(tracked_detections, self.AREA_PARKING_PLACE)
# add bounding box for parking
cv2.rectangle(frame,
self.AREA_PARKING_PLACE[0],
self.AREA_PARKING_PLACE[1],
parking_color, 2)
cv2.putText(frame,
f'parking_place',
self.TEXT_PARKING_PLACE,
cv2.FONT_HERSHEY_SIMPLEX, 0.6, parking_color, 2)
# add bounding box for exit
cv2.rectangle(frame,
self.AREA_EXIT[0],
self.AREA_EXIT[1],
(0, 0, 150), 2)
cv2.putText(frame,
f'exit_place',
self.TEXT_EXIT,
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 128), 2)
cv2.namedWindow('frame')
cv2.imshow('frame', frame)
k = cv2.waitKey(33)
if k == 27:
break
ret, frame = cap.read()
cap.release()
cv2.destroyAllWindows()
Архитектура нашего приложения состоит из двух ключевых компонентов: клиентской и серверной части. Клиентская часть занимается отправкой видеофреймов на удалённый сервер, для чего использует брокер сообщений для передачи данных в виде байтов. Серверная часть берет на себя обработку этих фреймов, извлекает необходимую информацию и сохраняет ее в базу данных. После этого агрегированные данные могут быть отображены в пользовательском интерфейсе.
На производственной площадке установлены IP-камеры, которые контролируют парковочные зоны и зоны загрузки. Каждая из камер передаёт фреймы на сервер для последующей обработки. На сервере применяется алгоритм, использующий нейронные сети для выделения и анализа интересующих объектов.
• Новый объект: Если система обнаруживает объект с новым идентификатором (ID), алгоритм пытается сопоставить его с транспортным средством, которое имеет схожее предыдущее местоположение и последнюю запись в пределах допустимого временного интервала.
• Старый объект: Если объект уже имеет существующий ID, система просто обновляет его местоположение.
Для переиндексации объектов используется таблица exit_zones, содержащая информацию о присутствии объектов в ассоциативных границах камер. Эта таблица включает ID последнего транспортного средства в границе, ID камеры с общей границей и статус наличия транспортного средства в данной зоне.
Например, если объект покидает зону видимости Камеры 1 и попадает в зону видимости Камеры 2, данные с Камеры 2 обрабатываются для определения, является ли этот объект новым для неё. Если да, система проверяет, отслеживался ли он другими камерами. В случае положительного результата происходит переиндексация. Если нет — объекту присваивается новый индекс. Для старых объектов просто обновляется информация о местоположении.
Все данные передаются на интерфейс и доступны для просмотра в хранилище. К базе данных можно подключать другие системы через API или брокер сообщений.
Функционально система делится на две части:
1. Ведение журнала о перемещении объектов на видео;
2.Комплексное отслеживание объектов на территории по нескольким камерам с учетом перехода объектов из области видимости одной камеры в другую.
Первая часть на данный момент реализована целиком. Спроектирована UML диаграмма классов, которая легла в основу схемы базы данных. Для минимальной работы системы необходимо три класса:
- камеры (cameras);
- отслеживаемые объекты (tracked_objects_aud);
- ассоциативные границы (exit_zones). Класс ассоциативных границ необходим для переиндексации объектов при переходе с одной камеры на другую.
Вторая часть — собственно отслеживание объектов — реализована частично. Уже готов алгоритм, который отвечает за переиндексацию отслеживаемых объектов при переходе объекта с одной камеры на другую. Он вызывается на каждом обрабатываемом кадре для любого обнаруженного объекта после записи нового транспортного средства или актуализации данных старого в базе данных.
Осталось доработать момент с расчетом идентичности двух обнаруженных объектов и принятием решения об индексации. То есть, сейчас алгоритм довольно-таки глуп: при пересечении границ камер он любой машине присвоит старый индекс, а суть конечной идеи, чтобы он точно мог определить, что в зону видимости заехало новое транспортное средство или, что мы продолжаем наблюдать за старым.
Сейчас думаю о том, чтобы сделать собственную нейросеть, которая будет сверять по свертке два объекта и выдавать % соотношения их идентичности. Пока все на уровне эксперимента.
Такая система может быть запущена на любом производстве. Достаточно только вручную настроить ассоциативные зоны-границы камер — остальное она определит сама. Ахиллесова пята решения — производительность, так как для нормальной работы системы из коробки нужны мощные ресурсы: цп, видеокарта, оперативка. В качестве компромисса можно рассмотреть ограничения: обрабатывать каждый n-ый кадр, вместо того, чтобы обрабатывать каждый. При таком подходе нужно будет рассчитать, сколько кадров можно пропустить.
Метод, ответственный за переиндексацию отслеживаемых объектов при переходе объекта с одной камеры на другую, вызывается на каждом обрабатываемом кадре для любого обнаруженного объекта после записи нового транспортного средства или актуализации данных старого в базе данных. Алгоритм переиндексации представлен псевдокодом А1.
Алгоритм А1
Procedure actualize_exit_zones_data
Ввод exit_area_rect – координаты выделяющей рамки ассоциативной границы
Ввод transport_rect – координаты выделяющей рамки транспортного средства
Ввод camera_id – идентификационный номер камеры, с которой пришёл кадр
If exit_area_rect и transport_rect пересекаются then
Получение из БД текущее состояние модели ассоциативной границы из класса exit_zone
If значение tracked_objects_aud_id НЕ равно текущему транспортному средству then
Присвоение tracked_objects_aud_id значение id текущего транспортного средства
Присвоение флагу is_empty значение 0
End
End
Else if значение tracked_objects_aud_id для ассоциативной границы из класса exit_zone равен текущему транспортному средству then
Присвоение флагу is_empty значение 1
Получение пар id объектов и id ассоциативной границ, которые связаны, при условии, что связанная граница также имеет флаг is_empty равный 1
If такая пара ассоциативных границ существует then
Перезапись координат и камеры в более старой модели для tracked_objects_aud на актуальные, новый id удаляем из таблицы
Присвоение парам ассоциативной границ last_tracked_objects_aud NULL и флагу is_empty 1.
End
End
End actualize_exit_zones_data
Разработка шла по таким этапам:
Перенос имеющихся методов из пет-проекта.
Проектирование ER-модели приложения.
Реализация алгоритма переиндексации.
Реализация и внедрение нейронной с расчётом идентичности двух обнаруженных объектов и принятием решения об индексации (в процессе).
На этапе реализации механизма переиндексации столкнулся со сложностями. Во-первых, не был плотно знаком с ORM SQLAlchemy, из-за чего при разработке допускал ошибки. Во-вторых, изначально не придумал «триггеры», которые должны инициализировать переиндексацию. Сейчас они есть и занесены в отдельную таблицу, которая фиксирует входы и выходы из границ камер. Когда описывал принципы работы системы, рассказывал про нее.
Из планов:
реализовать и внедрить нейросеть для расчета идентичности двух обнаруженных объектов и принятием решения об индексации (когда удастся это сделать, обязательно поделюсь результатами);
реализация API приложения и разделения монолитного приложения на клиент и сервер.
Подчеркну мой личный итог: из пет-проекта, разработанного для защиты дипломной работы, вырос (почти) полноценный, применимый в реальном бизнесе инструмент. Надеюсь, что быстро справлюсь со всеми перечисленными сложностями и доработаю его. Будет здорово, если мой пример подтолкнет кого-то из читателей к развитию их пет-проектов или к созданию таковых.
Буду рад вашим комментариям и вопросам. Если у кого-то есть идеи и соображения, как можно улучшить или оптимизировать работу системы и решить задачу по расчету идентичности — делитесь в комментариях.