13
You only look once, или YOLO, — эффективный алгоритм, который позволяет выделять объекты на изображении. В этой статье мы используем его, чтобы написать на Python программу для определения числа людей в помещении, а по дороге опробуем его в разгадывании капчи.
Если ты смотрел «Терминатор», то помнишь кадры из глаз T-800: он смотрел по сторонам и определял разные объекты. Тогда о такой машине можно было только мечтать, а сегодня ее можно смастерить самому из готовых частей.
[
Кадр из фильма «Терминатор»
Распознавание объектов сегодня пригождается для решения самых разных задач: классификации видов растений и животных, распознавания лиц, определения габаритов объектов — и это далеко не полный список.
Какие бывают алгоритмы¶
Существует несколько алгоритмов обнаружения объектов на изображениях и видео. Посмотрим, что они собой представляют.
R-CNN, Region-Based Convolutional Neural Network¶
Сперва на изображении с помощью алгоритма выборочного поиска выделяются регионы, которые предположительно содержат объект. Далее сверточная нейронная сеть (CNN) пытается выявить признаки объектов для каждого из этих регионов, после чего машина опорных векторов классифицирует полученные данные и сообщает класс обнаруженного объекта.
Обработка в режиме реального времени: не поддерживается.
Fast R-CNN, Fast Region-Based Convolutional Neural Network¶
Подход аналогичен алгоритму R-CNN. Но вместо того, чтобы предварительно выделять регионы, мы передаем входное изображение в CNN для создания сверточной карты признаков, где затем будет происходить выборочный поиск, а предсказание класса объектов выполняет специальный слой Softmax.
Обработка в режиме реального времени: не поддерживается.
Faster R-CNN, Faster Region-Based Convolutional Neural Network¶
Подобно Fast R-CNN, изображение передается в CNN создания сверточной карты признаков, но вместо алгоритма выборочного поиска для прогнозирования предложений по регионам используется отдельная сеть.
Обработка в режиме реального времени: поддерживается при высоких вычислительных мощностях.
YOLO, You Only Look Once¶
Изображение делится на квадратную сетку. Для каждой ячейки сети CNN выводит вероятности определяемого класса. Ячейки, имеющие вероятность класса выше порогового значения, выбираются и используются для определения местоположения объекта на изображении.
Обработка в режиме реального времени: поддерживается!
Как видишь, YOLO пока что лучший вариант для обнаружения и распознавания образов. Он отличается высокой скоростью и точностью обнаружения объектов, а еще этот алгоритм можно использовать в проектах на Android и Raspberry Pi с помощью нетребовательного tiny-варианта сети, с которым мы с тобой сегодня будем работать.
Tiny-вариант несколько проигрывает в точности полноценному варианту сети, но и требует меньшей вычислительной мощности, что позволит запустить проект, который мы сегодня будем делать, как на слабом компьютере, так и, при желании, на смартфоне.
Пишем код¶
Чтобы написать легковесное приложение для обнаружения объектов на изображении, нам с тобой понадобятся:
- названия классов датасета COCO, которые умеет определять сеть YOLO;
- конфигурации для tiny-варианта сети YOLO;
- веса для tiny-варианта сети YOLO.
Дополнительно установим библиотеки OpenCV и NumPy:
pip install opencv-python pip install numpy
Теперь напишем приложение, которое будет находить объекты на изображении при помощи YOLO и отмечать их.
Мы попробуем обойти CAPTCHA с изображениями грузовиков — класс truck в датасете COCO. Дополнительно мы посчитаем количество обнаруженных объектов нужного нам класса и выведем всю информацию на экран.
Начнем с написания функции для применения YOLO. С ее помощью определяются самые вероятные классы объектов на изображении, а также координаты их границ, которые позже мы будем использовать для отрисовки.
import cv2 import numpy as np def apply_yolo_object_detection(image_to_process): """ Распознавание и определение координат объектов на изображении :param image_to_process: исходное изображение :return: изображение с размеченными объектами и подписями к ним """ height, width, depth = image_to_process.shape blob = cv2.dnn.blobFromImage(image_to_process, 1 / 255, (608, 608), (0, 0, 0), swapRB=True, crop=False) net.setInput(blob) outs = net.forward(out_layers) class_indexes, class_scores, boxes = ([] for i in range(3)) objects_count = 0 # Запуск поиска объектов на изображении for out in outs: for obj in out: scores = obj[5:] class_index = np.argmax(scores) class_score = scores[class_index] if class_score > 0: center_x = int(obj[0] * width) center_y = int(obj[1] * height) obj_width = int(obj[2] * width) obj_height = int(obj[3] * height) box = [center_x - obj_width // 2, center_y - obj_height // 2, obj_width, obj_height] boxes.append(box) class_indexes.append(class_index) class_scores.append(float(class_score)) # Выборка chosen_boxes = cv2.dnn.NMSBoxes(boxes, class_scores, 0.0, 0.4) for box_index in chosen_boxes: box_index = box_index[0] box = boxes[box_index] class_index = class_indexes[box_index] # Для отладки рисуем объекты, входящие в искомые классы if classes[class_index] in classes_to_look_for: objects_count += 1 image_to_process = draw_object_bounding_box(image_to_process, class_index, box) final_image = draw_object_count(image_to_process, objects_count) return final_image
Далее добавим функцию, которая позволит нам обвести найденные на изображении объекты с помощью координат границ, которые мы получили в apply_yolo_object_detection
.
def draw_object_bounding_box(image_to_process, index, box): """ Рисование границ объекта с подписями :param image_to_process: исходное изображение :param index: индекс определенного с помощью YOLO класса объекта :param box: координаты области вокруг объекта :return: изображение с отмеченными объектами """ x, y, w, h = box start = (x, y) end = (x + w, y + h) color = (0, 255, 0) width = 2 final_image = cv2.rectangle(image_to_process, start, end, color, width) start = (x, y - 10) font_size = 1 font = cv2.FONT_HERSHEY_SIMPLEX width = 2 text = classes[index] final_image = cv2.putText(final_image, text, start, font, font_size, color, width, cv2.LINE_AA) return final_image
Добавим функцию, которая выведет количество распознанных объектов на изображении.
def draw_object_count(image_to_process, objects_count): """ Подпись количества найденных объектов на изображении :param image_to_process: исходное изображение :param objects_count: количество объектов искомого класса :return: изображение с подписанным количеством найденных объектов """ start = (45, 150) font_size = 1.5 font = cv2.FONT_HERSHEY_SIMPLEX width = 3 text = "Objects found: " + str(objects_count) # Вывод текста с обводкой (чтобы было видно при разном освещении картинки) white_color = (255, 255, 255) black_outline_color = (0, 0, 0) final_image = cv2.putText(image_to_process, text, start, font, font_size, black_outline_color, width * 3, cv2.LINE_AA) final_image = cv2.putText(final_image, text, start, font, font_size, white_color, width, cv2.LINE_AA) return final_image
Напишем функцию, которая будет анализировать изображение и выводить на экран результат работы написанных нами алгоритмов.
def start_image_object_detection(): """ Анализ изображения """ try: # Применение методов распознавания объектов на изображении от YOLO image = cv2.imread("assets/truck_captcha.png") image = apply_yolo_object_detection(image) # Вывод обработанного изображения на экран cv2.imshow("Image", image) if cv2.waitKey(0): cv2.destroyAllWindows() except KeyboardInterrupt: pass
А теперь мы создадим функцию main
, в которой настроим нашу сеть и попробуем запустить ее.
if __name__ == '__main__': # Загрузка весов YOLO из файлов и настройка сети net = cv2.dnn.readNetFromDarknet("yolov4-tiny.cfg", "yolov4-tiny.weights") layer_names = net.getLayerNames() out_layers_indexes = net.getUnconnectedOutLayers() out_layers = [layer_names[index[0] - 1] for index in out_layers_indexes] # Загрузка из файла классов объектов, которые умеет обнаруживать YOLO with open("coco.names.txt") as file: classes = file.read().split("\n") # Определение классов, которые будут приоритетными для поиска на изображении # Названия находятся в файле coco.names.txt # В данном случае определяется грузовик для прохождения CAPTCHA classes_to_look_for = ["truck"] start_image_object_detection()
Давай посмотрим, как алгоритм YOLO справился с тестом простой CAPTCHA.
[
Исходная CAPTCHA
[
Результат применения YOLO
Некоторая погрешность все же есть, но два из трех грузовиков алгоритм выбрал правильно.
Кажется, пока что восстание машин нам не грозит!
Модифицируем приложение¶
Теперь мы с тобой приступим к решению практической задачи, в которой нам будет важно контролировать количество человек в помещении. Тем более что во время ограничительных мер, связанных с COVID-19, это не просто интересно, но еще и актуально.
Чтобы задача была на «живом примере», мы воспользуемся публичной камерой, установленной в одном из барбершопов Лондона. Из‑за его скромной площади находиться внутри может не больше десяти человек.
Чтобы решить эту задачу, достаточно добавить функцию, которая будет обрабатывать видео по кадрам и выводить результат обработки на экран. Чтобы не нагружать устройство обработкой каждого кадра, обновление экрана будет происходить по нажатию любой клавиши. На мощных компьютерах это необязательно.
def start_video_object_detection(): """ Захват и анализ видео в режиме реального времени """ while True: try: # Захват картинки с видео video_camera_capture = cv2.VideoCapture("http://81.130.136.82:82/mjpg/video.mjpg") while video_camera_capture.isOpened(): ret, frame = video_camera_capture.read() if not ret: break # Применение методов распознавания объектов на кадре видео от YOLO frame = apply_yolo_object_detection(frame) # Вывод обработанного изображения на экран с уменьшением размера окна frame = cv2.resize(frame, (1920 // 2, 1080 // 2)) cv2.imshow("Video Capture", frame) if cv2.waitKey(0): break video_camera_capture.release() cv2.destroyAllWindows() except KeyboardInterrupt: pass
Также нам потребуется немного модифицировать функцию main
, чтобы теперь запускать обработку видео вместо обработки изображения.
if __name__ == '__main__': # Загрузка весов YOLO из файлов и настройка сети net = cv2.dnn.readNetFromDarknet("yolov4-tiny.cfg", "yolov4-tiny.weights") layer_names = net.getLayerNames() out_layers_indexes = net.getUnconnectedOutLayers() out_layers = [layer_names[index[0] - 1] for index in out_layers_indexes] # Загрузка из файла классов объектов, которые умеет обнаруживать YOLO with open("coco.names.txt") as file: classes = file.read().split("\n") # Определение классов, которые будут приоритетными для поиска на изображении # Названия находятся в файле coco.names.txt # В данном случае определяется грузовик для прохождения CAPTCHA и человек для анализа видео classes_to_look_for = ["truck", "person"] start_video_object_detection()
Получаем результат: шесть из семи человек были распознаны.
[
Результат обработки видео
Можно добавить и другие полезные функции: например, отправлять на почту или в Telegram сообщение о том, что в барбершоп набилось многовато людей.
Итоги¶
Алгоритмы обнаружения объектов не дают стопроцентной точности, но они все равно эффективны и способны работать гораздо быстрее любого из нас.
Возможно, ты спросишь, почему мы не следим, чтобы соблюдалось расстояние в полтора метра. В реальной жизни его будет сложно проверять: например, когда перед нами пара друзей, семья с ребенком или происходит действие, невозможное без близкого контакта, а в парикмахерской это случается постоянно. Кроме того, если камера будет стоять под неудачным углом, сложность измерения расстояния возрастает.
Здесь потребуются алгоритмы, определяющие в перспективе габариты объектов и работающие с трехмерным пространством. Такие используются для определения транспортных средств в самоуправляемых автомобилях — Aggregate View Object Detection или YOLO 3D Oriented Object Bounding Box Detection.
Исходники проекта, с которыми ты сможешь поработать над решением подобных задач, смотри в моем репозитории на GitHub.