Построение сортировщика Lego Technic с использованием распознавания объектов в реальном времени

Создание сортировочной машины Lego Technic с помощью технологии распознавания объектов в реальном времени

Во время моей стажировки в Nullspace Robotics мне выпала честь погрузиться в проект, который улучшит возможности компании. Мы интегрировали распознавание объектов и машинное обучение для разработки устройства, которое в реальном времени классифицирует детали Lego Technic.

В этой статье блога я расскажу вам об испытаниях, с которыми мы столкнулись, и о том, как мы успешно осуществили этот проект.

Весь летний период 2022 года я и Амос Ко вела обучение программированию и робототехнике для студентов, одновременно работая над проектом для Nullspace. Вы можете найти нас по ссылкам ниже статьи.

Nullspace Robotics – ведущий провайдер образования в области робототехники и программирования для учеников начальной и средней школы в Сингапуре. Большая часть их работы связана с созданием роботов из деталей Lego Technic, которые сортируются по специфическим лоткам. Вы можете себе представить, что это настоящий кошмар попросить 8-летнего ребенка с безграничной энергией помочь положить детали обратно в лоток, когда все, чего он хочет, это строить еще больше вещей.

Nullspace дали нам задачу создать устройство, которое сможет сортировать детали Lego Technic в специфические категории с минимальным вмешательством человека для решения одной из ключевых проблем эффективности при проведении занятий по робототехнике.

Определение задачи

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

Одной из основных проблем было распознавание движущихся частей и их идентификация в пределах одного кадра. Мы обсуждали два подхода: интеграцию машинного обучения для распознавания изображений в камеру распознавания объектов или отдельное выполнение процессов.

В конечном итоге мы решили разделить распознавание объектов и их классификацию. Для этого мы сначала фиксировали подходящее изображение после обнаружения объекта, а затем запускали модель для классификации изображения. Если бы мы объединили эти процессы, нам пришлось бы запускать модель практически на каждом кадре для классификации каждого обнаруженного объекта. Разделение этих процессов позволило избежать необходимости постоянной обработки модели, обеспечивая более плавную и вычислительно эффективную работу.

Обнаружение объектов

Мы использовали идеи из проектов, указанных ниже статьи, для реализации нашей программы распознавания объектов/движения и настраивали ее для работы с деталями Lego

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

Мы применяли гауссово размытие и другие техники обработки изображений ко всем кадрам и сравнивали их с предыдущими кадрами. Дополнительная обработка позволяла выделить (нарисовать пограничные рамки вокруг) элементы, вызывающие движение, как показано ниже:

for f in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):        frame = f.array # grab the raw NumPy array representing the image    text = "No piece" # initialize the occupied/unoccupied text    # resize the frame, convert it to grayscale, and blur it    frame = imutils.resize(frame, width=500)    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)    gray = cv2.GaussianBlur(gray, (21, 21), 0)    # if the average frame is None, initialize it    if avg is None:        print("[INFO] starting background model...")        avg = gray.copy().astype("float")        rawCapture.truncate(0)        continue    # accumulate the weighted average between the current frame and    # previous frames, then compute the difference between the current    # frame and running average    cv2.accumulateWeighted(gray, avg, 0.5)    frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg))        # threshold the delta image, dilate the thresholded image to fill    # in holes, then find contours on thresholded image    thresh = cv2.threshold(frameDelta, conf["delta_thresh"], 255,        cv2.THRESH_BINARY)[1]    thresh = cv2.dilate(thresh, None, iterations=2)    cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,        cv2.CHAIN_APPROX_SIMPLE)    cnts = imutils.grab_contours(cnts)    # loop over the contours        for c in cnts:        # if the contour is too small, ignore it        if cv2.contourArea(c) < conf["min_area"]:            continue        # compute the bounding box for the contour, draw it on the frame,        # and update the text        (x, y, w, h) = cv2.boundingRect(c)        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)        piece_image = frame[y:y+h,x:x+w]        text = "Piece found"        # cv2.imshow("Image", image)

Для того чтобы убедиться, что движение действительно вызвано куском Лего, стабильность обнаружения движения была оценена с помощью счетчика движения, который проверял, что движение обнаруживается в течение определенного количества кадров, прежде чем сделать вывод о том, что движение действительно вызвано куском Лего, а не случайным шумом. Затем окончательное изображение сохраняется и подается в нашу сверточную нейронную сеть для классификации.

if text == "Кусок найден":        # для сохранения изображений ограничивающих рамок        motionCounter += 1        print("motionCounter= ", motionCounter)        print("image_number= ", image_number)        # сохранить изображение, если движение обнаруживается в течение 8 или более последовательных кадров        if motionCounter >= 8:            image_number +=1            image_name = str(image_number)+"image.jpg"            cv2.imwrite(os.path.join(path, image_name), piece_image)            motionCounter = 0 #сбросить счетчик движения # классифицировать сохраненное изображение с помощью нашей модели, см. ниже

Создание модели

Создание набора данных

Мы создали набор данных с изображениями сами, а не использовали изображения Лего Technic, найденные в Интернете, потому что мы хотели воспроизвести условия, в которых модель в конечном итоге будет обнаруживать и классифицировать куски Лего. Мы разработали простую систему конвейерной ленты, используя сами куски Лего Technic! Затем мы подключили к ней двигатель Лего Spike Prime, чтобы она продолжала двигаться.

Проектирование архитектуры модели

Для решения главной задачи, я адаптировал модель машинного обучения, которую нашел в репозитории GitHub AladdinPersson. Эта модель имела сверточные слои с последовательностью от 128 до 64 до 32 до 16, выбор архитектуры, разработанный для улучшения распознавания изображений.

Вместо использования предварительно обученной модели мы разработали собственную сверточную нейронную сеть, потому что:

  1. Нам не требовалась особенно глубокая фича-экстракция для наших изображений.
  2. Мы хотели сделать модель компактной, уменьшить ее сложность и уменьшить вычислительные затраты для ее выполнения. Это позволило бы эффективнее выполнять сверточную нейронную сеть в качестве модели tflite на Pi.

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

В этой модели различные слои, такие как ReLU, dense, softmax и flatten, играли ключевую роль. Активация ReLU, например, была важна для классификации изображений, поскольку она устраняла проблему исчезающих градиентов в распознавании изображений. Dense-слои, с другой стороны, являются стандартными в моделях Tensorflow и обеспечивают плотно связанные нейронные сети. Softmax-активация использовалась для расчета вероятностей для каждой категории в нашем наборе данных.

В качестве функций потерь мы использовали Sparse Categorical Cross Entropy из Keras, подходящий выбор для задач многоклассовой классификации. Кроме того, для донастройки модели использовался оптимизатор Adam из Keras, известный своей эффективностью.

Обучение и оптимизация

Эпохи были тщательно выбраны, чтобы найти баланс между обучением и переобучением, с предпочтением числа менее 200, чтобы обеспечить оптимальную производительность модели. Для ускорения тренировки модели мы использовали Google Colab, который предоставлял доступ к ресурсам GPU, обеспечивая значительно более быстрые скорости обучения по сравнению с нашими собственными ноутбуками.

Полная архитектура модели показана ниже:

data_augmentation = keras.Sequential([    layers.RandomFlip("horizontal",                        input_shape=(img_height,                                    img_width,                                    1)),    layers.RandomRotation(0.2),    layers.RandomZoom(0.1),    ])model = keras.Sequential(    [        data_augmentation,            layers.Rescaling(1./255, input_shape = (img_height,img_width,1)), #нормализуем данные ввода        layers.Conv2D(128, 3, padding="same", activation='relu'),        layers.MaxPooling2D(pool_size=(2,2)),        layers.Conv2D(64, 3, padding="same", activation='relu'), #должно быть 16 или 32? попробуйте с большим количеством данных        layers.MaxPooling2D(pool_size=(2,2)),        layers.Conv2D(32, 3, padding="same", activation='relu'),        layers.MaxPooling2D(pool_size=(2,2)),                layers.Conv2D(16, 3, padding="same", activation='relu'),        layers.MaxPooling2D(pool_size=(2,2)),                layers.Dropout(0.1),        layers.Flatten(),        layers.Dense(10,activation = 'relu'),        layers.Dense(7,activation='softmax'), # количество классов в категории            ])        model.compile(    optimizer=keras.optimizers.Adam(),    loss=[keras.losses.SparseCategoricalCrossentropy(from_logits=False),],    metrics=["accuracy"],)model_history = model.fit(x_train, y_train, epochs=200, verbose=2, validation_data=(x_test,y_test), batch_size=25)  #думаю, что 25/32 является оптимальным размером пакета

Результаты модели

Модель была обучена на 6000 изображениях из 7 категорий конструкторов Lego Technic. Она достигла конечной точности на валидации 93%. Ниже показаны диаграммы, отображающие прогресс обучения, а также матрица путаницы для оценки производительности:

Внедрение модели на Raspberry Pi

Самый эффективный способ запуска нейронной сети на Raspberry Pi – использовать модель tflite (TensorFlow Lite). Мы сохранили модель локально, а затем загрузили ее на Pi.

from tflite_runtime.interpreter import Interpreter# Загрузка модели TFLite и выделение памяти для тензоров.interpreter = Interpreter(model_path="lego_tflite_model/detect.tflite") # вставьте путь к модели TFLiteinterpreter.allocate_tensors()

Продолжая с цикла счетчика движения выше, подходящие изображения были поданы на вход нейронной сети для классификации:

 # продолжая с if text == "Piece found":            # Открыть изображение, изменить его размер и увеличить контрастность            input_image = Image.open('lego-pieces/'+ image_name)            input_image = ImageOps.grayscale(input_image)            input_image = input_image.resize((128,128))            input_data = img_to_array(input_image)            input_data = increase_contrast_more(input_data)            input_data.resize(1,128,128,1)                        # Разместить np.array изображения через модель tflite. Это приведет к выходному вектору вероятности            interpreter.set_tensor(input_details[0]['index'], input_data)            interpreter.invoke()            output_data = interpreter.get_tensor(output_details[0]['index'])                        # Получить индекс наибольшего значения в векторе вероятности.            # Это значение индекса будет соответствовать созданному выше вектору меток (т.е. значение индекса 1 означает, что объект скорее всего принадлежит метке labels[1])            category_number = np.argmax(output_data[0])            # Вернуть метку классификации изображения                classification_label = labels[category_number]                            print("Метка изображения для " + image_name + ":", classification_label)                                        else:        motionCounter = 0 # сбросить счетчик движения для поиска новых объектов

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

Демонстрация

Итогом наших усилий стало шоу, демонстрирующее общую точность системы, подтвержденное фотографиями и видеозаписями ее работы. Установка конвейера (выше) была неотъемлемой частью этой демонстрации:

Перспективы и области для улучшения

Программное обеспечение: Безусловно, модели непременно будет полезна более качественная камера для получения изображений высокого качества. Кроме того, в будущем можно также дополнить операцию моделью проверки качества, чтобы гарантировать, что изображения, используемые для классификации деталей, будут подходящими.

Оборудование: Система конвейерного транспорта, временно построенная для наших тестов и демонстрации, должна быть масштабирована для вмещения большего количества деталей. Также необходимо разработать и внедрить метод для разделения нескольких Lego деталей и обеспечения видимости только одной детали на кадре камеры. В онлайн-проектах можно найти подробные описания возможных методов.

Заключение

Мой опыт работы в Nullspace Robotics стал моим первым шагом в создании собственной нейронной сети для практических целей. Поскольку я ранее разрабатывал модели в рамках тренировочных курсов, создание модели для реального производства – это совершенно другой опыт, где мы должны учитывать различные факторы, такие как ресурсы, цели использования и настройку набора данных и модели под наши цели. Я с нетерпением жду продолжения своего пути в машинном обучении и использования последних технологий искусственного интеллекта для создания более инновационных решений.

Я хочу поблагодарить Nullspace за возможность работать над этим проектом и с нетерпением жду, что произойдет дальше с компанией, которая расширяет границы образования в области робототехники.

Проверьте полный репозиторий на Github или HuggingFace для кода, доступа к изображениям набора данных и получения дополнительной информации о проекте:

Github: https://github.com/magichampz/lego-sorting-machine-ag-ak/

HuggingFace: https://huggingface.co/magichampz

Познакомьтесь с разработчиками

Aveek: https://www.linkedin.com/in/aveekg00/

Amos: https://www.linkedin.com/in/ak726/

Ссылки:

Основное обнаружение движения и отслеживание с помощью Python и OpenCV — PyImageSearch

В этом руководстве я покажу вам, как использовать Python и OpenCV для основного обнаружения движения и отслеживания. Узнайте, как…

pyimagesearch.com

Обнаружение движения с помощью OpenCV — анализ изображений для начинающих

Как обнаружить и проанализировать движущиеся объекты с помощью OpenCV

towardsdatascience.com

Machine-Learning-Collection/ML/TensorFlow/Basics/tutorial15-customizing-modelfit.py at master ·…

Ресурс для изучения машинного обучения и глубокого обучения…

github.com

Неудачные снимки

Если не указано иное, все изображения принадлежат автору