В данной статье мы попробуем на своей локальной машине дообучить уже готовую модель в Tensorflow 1.13 и Object Detection API на нашем собственном наборе изображений, а потом используем её для распознавания ягод и контроллеров, в видеопотоке веб-камеры с помощью OpenCV.
Хотите к лету улучшить навык распознавания ягод? Тогда милости прошу под кат.
Содержание:
Часть I: введение Часть II: обучаем модель в TenosrFlow Часть III: применяем модель в OpenCV Часть IV: заключение
Часть I: введение
Те кто ознакомился с прошлой статьей про PyTorch, уже знают, что я дилетант в вопросах нейронных сетей. Поэтому не стоит воспринимать, эту статью, как истину в последней инстанции. Но так или иначе я надеюсь, что смогу кому-то помочь разобраться с азами распознавания видео с помощью Tensorflow Object Detection API.
В этот раз я не стремился сделать туториал, поэтому статья выйдет короче чем обычно.
Начну с того, что официальный туториал по применению Object Detection API на локальной машине, мягко говоря сложно назвать исчерпывающим. Мне, как новичку информации было совершенно недостаточно и пришлось ориентироваться на статьи в блогах. Если честно мне бы хотелось попробовать TensorFlow 2.0, но в большинстве публикаций, на момент написания этой статьи, были не полностью решены вопросы миграции. Поэтому в итоге я остановился на TF 1.13.2.
Часть II: обучаем модель в TensorFlow
Инструкции по обучению модели я черпал из этой статьи , а точнее из её первой половины, до момента применения JavaScript (Если вы не владеете английским, можно посмотреть статью на эту же тему на Хабре).
Правда в моем случае есть несколько отличий:
- Я использовал Linux, потому что в Anaconda для Linux уже есть собранные protobuf и pycocoapi, поэтому мне не пришлось их собирать самому.
- Я использовал версию TensorFlow 1.13.2, а также ветку Object Detection API 1.13 потому, что актуальная ветка выдавала ошибку с TensorFlow 1.13.2. Текущую ветку master мне вроде бы удавалось запустить с версией TF 1.15, но для этой статьи я остановился на версии 1.13.
- Необходимо будет установить более старую версию numpy — 1.17.5, версия 1.18 вызывала у меня ошибку.
- Вместо модели faster_rcnn_inception_v2_coco мы будем использовать ssd_mobilenet_v2_coco, поэтому будут небольшие изменения, но всё делается по аналогии.
Так же на всякий случай, скажу, что я не использовал графический ускоритель. Обучение производилось только на мощностях процессора.
Набор изображений, конфигурационный файл, сохраненный граф, а также скрипт для распознавания изображения с помощью OpenCV, как всегда можно скачать с GitHub. Прошли долгие 23 часа обучения модели, весь чай в доме уже выпит, “Что? Где? Когда?” досмотрено и вот мое терпение окончательно подошло к концу.
Останавливаем обучение и сохраняем модель.
Устанавливаем в тоже самое окружение “Анаконды” OpenCV, следующей командой:
conda install -c conda-forge opencv
У меня в итоге установлена версия 4.2
Дальше инструкции из этой статьи нам уже будут не нужны. После сохранения модели я сделал одну неочевидную для меня ошибку, а именно сразу попытался подставить использованный ранее в папке training/ файл graph.pbtxt в функцию:
cv2.dnn.readNetFromTensorflow()
К сожалению, это так не работает и нам придется проделать еще одну манипуляцию, чтобы получить graph.pbtxt для OpenCV.
Скорее всего, то что я сейчас посоветую не очень хороший способ, но для меня он работает.
Скачаем tf_text_graph_ssd.py, а также tf_text_graph_common.py положим их в папку, где лежит наш сохраненный граф (у меня это папка inference_graph). Затем перейдем в консоли в эту папку и выполним из неё команду примерно следующего содержания:
python tf_text_graph_ssd.py --input frozen_inference_graph.pb --config pipeline.config --output frozen/graph.pbtxt
Вот и всё осталось только загрузить нашу модель в OpenCV.
Часть III: применяем модель в OpenCV
Как и в статье про PyTorch в части работы с OpenCV я брал за основу программный код из этой публикации. Я внес небольшие изменения, чтобы его еще немного упростить, но поскольку я не полностью понимаю код, то и комментировать его не буду. Работает и славно.Понятно, что код мог бы быть и лучше, но у меня пока нет времени, чтобы засесть за учебники по OpenCV .
# USAGE # based on this code https://proglib.io/p/real-time-object-detection/ # import the necessary packages from imutils.video import VideoStream from imutils.video import FPS import numpy as np import imutils import time import cv2 prototxt="graph.pbtxt" model="frozen_inference_graph.pb" min_confidence = 0.5 # initialize the list of class labels MobileNet SSD was trained to # detect, then generate a set of bounding box colors for each class CLASSES = ["background", "duino","raspb"] COLORS = [(40,50,60),((140,55,130)),(240,150,25)] # load our serialized model from disk print("[INFO] loading model...") net =cv2.dnn.readNetFromTensorflow(model,prototxt) # initialize the video stream, allow the cammera sensor to warmup, # and initialize the FPS counter print("[INFO] starting video stream...") vs = VideoStream(src=0).start() time.sleep(0.5) fps = FPS().start() # loop over the frames from the video stream while True: # grab the frame from the threaded video stream and resize it # to have a maximum width of 400 pixels frame = vs.read() frame = imutils.resize(frame, width=300) # grab the frame dimensions and convert it to a blob (h, w) = frame.shape[:2] blob = cv2.dnn.blobFromImage(frame, size=(300, 300), swapRB=True) # pass the blob through the network and obtain the detections and # predictions net.setInput(blob) detections = net.forward() # loop over the detections for i in np.arange(0, detections.shape[2]): # extract the confidence (i.e., probability) associated with # the prediction print (detections) confidence = detections[0, 0, i, 2] if confidence > min_confidence: # extract the index of the class label from the # `detections`, then compute the (x, y)-coordinates of # the bounding box for the object idx = int(detections[0, 0, i, 1]) box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) (startX, startY, endX, endY) = box.astype("int") # draw the prediction on the frame label = "{}: {:.2f}%".format(CLASSES[idx], confidence * 100) cv2.rectangle(frame, (startX, startY), (endX, endY), COLORS[idx], 2) y = startY - 15 if startY - 15 > 15 else startY + 15 cv2.putText(frame, label, (startX, y+3), cv2.FONT_HERSHEY_SIMPLEX, 0.5, COLORS[idx], 1) # show the output frame cv2.imshow("Frame", frame) key = cv2.waitKey(1) & 0xFF # if the `q` key was pressed, break from the loop if key == ord("q"): break # update the FPS counter fps.update() # stop the timer and display FPS information fps.stop() print("[INFO] elapsed time: {:.2f}".format(fps.elapsed())) print("[INFO] approx. FPS: {:.2f}".format(fps.fps())) # do a bit of cleanup cv2.destroyAllWindows() vs.stop()
На первый взгляд совсем не дурно, но это только на первый взгляд. Похоже за 23 часа, модель переобучилась, поэтому выдает серьезные ошибки при определении объектов.
Вот наглядная демонстрация:
Как видно, не только ножик, но даже просто черный фон, эта модель определяет как ардуино-подобный контроллер. Возможно, всё потому, что в тренировочных данных были темные картинки с Arduino и его аналогами, на которые модель за 23 часа успела натаскаться.
В итоге пришлось загружать свой компьютер еще на 8 часов и обучать новую модель.
С ней дела обстоят намного лучше.
Вот пример с CraftDuino:
Живой малины у меня под рукой нет. Пришлось печатать картинки. С экрана телефона или монитора тоже можно распознать, но с бумаги было удобней.
Проверим, как модель распознает Arduino nano, которую в свое время DrZugrik для меня впаял в свое мега устройство с датчиками: Как видите распознает вполне хорошо, однако при очень неудачном ракурсе и при теплом освещении, может некоторые фрагменты распознать, как малину. Но на самом деле кадр с ошибкой было тяжело поймать в объектив.
Теперь проверим, как она классифицирует те объекты на которых не обучалась.
Снова пример с ножом и черным фоном:
В этот раз все работает, как надо.
Предложим нашей модели распознать контроллер Canny 3 tiny, о котором я писал в прошлой статье.
Поскольку наша модель не знает ни о чем, кроме ягоды малины и ардуино-подобных контроллеров, то можно сказать, что модель распознала контроллер Canny вполне успешно.
Правда, как и в случае с Arduino nano, многое зависит от ракурса и освещения.
При теплом свете лампы накаливания и при неудачном ракурсе, контроллер может не только не распознаваться, но даже определиться как малина. Правда, как и в прошлом случае, эти ракурсы еще надо было постараться поймать в объектив.
Ну и последний случай — своеобразный реверанс статье про классификацию изображений в PyTorch. Как и в прошлый раз совместим в одном кадре одноплатный компьютер Raspberry Pi 2 и его логотип. В отличие от прошлой статьи, в которой мы решали задачу классификации и выбирали один наиболее вероятный объект для изображения, в данном случае распознается и логотип и сама «Малина».
Часть IV: заключение
В заключение хочется сказать, что не смотря на то, что по неопытности этот небольшой пример работы с Tensorflow Object Detection API отнял у меня оба выходных и часть понедельника, я ни о чем не жалею. Когда хоть немного разберешся, как этим всем пользоваться становится безумно любопытно. В процессе обучения начинаешь относится к модели, как к живой, следишь за её успехами и неудачами.
Поэтому, рекомендую всем кто с этим не знаком однажды попробовать, распознать что-нибудь свое.
Тем более, как выснилось в процессе, вам даже не нужно покупать настоящую веб-камеру. Дело в том, что во время подготовки статьи я умудрился доломать свою веб-камеру (сломал механизм фокусировки) и уже думал, что придется всё забросить. Но оказалось, что с помощью Droidcam можно использовать смартфон вместо веб-камеры (не сочтите за рекламу). Причем качество съемки получилось сильно лучше, чем у сломавшейся камеры, а это весьма влияло на качество распознавания объектов на изображении.
Кстати поскольку в Anaconda нормальную сборку pycocotools я нашел только для Linux, а переключаться в процессе работы между операционными системами мне было лень, то всю эту статью я подготовил только с использованием oпрограмного обеспечения с открытым исходным кодом. Нашлись аналоги и Word и Photoshop и даже драйвер для принтера. Первый раз в моей жизни случилось такое. Оказалось, что современные версии ОС Linux и прикладных программ могут быть очень удобными, даже для человека более 25 лет использующего ОС от Microsoft.
P.S. Если кто-то знает, как нормально запустить Object Detection API
для Tensorflow версии 2 и выше, отпишитесь пожалуйста в личку или в комментарии.
Всем хорошего дня и крепкого здоровья!