Закройте глаза и представьте себя в цехах большого завода. Пусть это будет производство вакцин в ампулах. А вы, как и еще 70 человек, заняты тем, что целыми днями просматриваете ампулы, чтобы отобрать дефектные. И так весь день… Сколько ампул с малейшими отклонениями от нормы вы бы не заметили? Задачу усложняет то, что дефектом считается не только неправильная запайка, но и едва заметная точка на дне ампулы. Можете ли вы быть на 100% уверены, что не пропустили ни одного дефекта? А ведь вас еще будут выборочно перепроверять.
Устают глаза, притупляется внимание.
А что если сортировать ампулы будет рука робота? Эксперты компании НОРБИТ (входит в группу ЛАНИТ) на примере кейса “Разработка системы Computer Vision моделей для фармацевтического завода” расскажут, как им удалось автоматизировать типовой рутинный процесс, и к чему это привело.
Сегодня существенное количество производственных компаний уже имеют автоматизированные участки контроля качества или задумываются об автоматизации процессов, связанных с монотонной ручной проверкой объектов. Это позволяет ускорить выполнение работы, сократить расходы на оплату труда. Например, если качество продукции контролируют около 70 человек со средней зарплатой в 30 тысяч рублей, то с налогами и прочими затратами, расходы на ФОТ составят больше 30 миллионов рублей.
А что если эту работу доверить роботу? Разработка PoC (Proof of Concept - проверка концепции) решения занимает около 2-3 месяцев для команды из нескольких человек, еще шесть месяцев потребуется на продуктивизацию. Однако даже с учетом этого времени, а также затрат на закупку и переоборудование необходимых манипуляторов и камер, лент конвейеров и поддержку внедрение Computer Vision окупится в течение первых двух лет.
Система автоматического контроля может самостоятельно классифицировать контролируемые объекты на качественные/бракованные и сделает это быстрее и дешевле.
Собираем функциональные требования заказчика и описываем прототип
На этапе проектирования одна из задач - формулирование списка требований для процесса контроля качества и перечня дефектов. К примеру, дефектами могут быть уровень жидкости в пробирке или наличие посторонних включений. В нашем проекте было выделено 11. Большинство этих дефектов можно находить с помощью камеры (хотя бы с разрешением в 720p, находящейся в 10-15 см от объекта) и модели компьютерного зрения.
На этапе проработки проекта было несколько предложений по автоматизации:
двигающаяся лента конвейера;
вращающийся диск;
рука-манипулятор.
Почему мы выбрали именно манипулятор, можно будет почитать дальше. А пока сосредоточимся на самой разработке автоматизированной системы контроля. Правильнее начинать решение глобальной задачи с создания прототипа решения (PoC). Безусловно, он будет сильно отличаться от финального продукта, но должен демонстрировать основные принципы его работы. Основная последовательность действий при разработке обычно выглядит так:
собрать требования и составить ТЗ на разработку решения;
спроектировать и собрать прототип решения для пилотного проекта, достаточный для демонстрации и сбора первых метрик;
портировать разработанные модели машинного обучения на промышленное оборудование и начать этап опытно-промышленной эксплуатации.
На основе собранных от заказчика данных мы сформулировали требования к прототипу.
Железо:
камера высокого разрешения для определения дефектов из списка;
камера для позиционирования манипулятора;
рука-манипулятор;
два источника света (один - для определения позиции, другой - для определения качества ампул);
специальный однородный фон с подсветкой.
Программное обеспечение:
модель для позиционирования ампул в пространстве;
математическая модель манипулятора;
несколько моделей для поиска дефектов из списка.
Чтобы было понятнее, приведем описание разработки одного элемента контроля - теста на наличие нерастворимых фракций в жидкости внутри ампулы.
Алгоритм выполнения анализа одного семпла:
получить и обработать картинку с камеры, стоящей прямо над рабочей поверхностью - определить, где находятся ампулы;
аккуратно подхватить ампулу, встряхнуть и поднести ее к камере для определения качества контролируемого объекта;
получить картинку со второй камеры и оценить ампулу с помощью моделей определения качества;
на основе этой информации положить ампулу в один из лотков;
GOTO 1.
Уже готовый прототип показан на фото ниже.
Hardware часть
В качестве основного механизма, сортирующего контролируемые объекты, мы остановились на манипуляторе по следующим причинам.
В данный момент сотрудники достают (и складывают) ампулы из специальных лотков, поэтому нужно использовать что-то более-менее похожее на руку человека.
Возможность быстрой адаптации манипулятора для других задач контроля, которые появляются на предприятии.
Ампулы, не прошедшие проверку, должны быть рассортированы по имеющемуся на них дефекту. Манипулятор позволяет быстро и точно перемещать отбракованную ампулу в один из лотков.
При проведении осмотра ампулы, ее необходимо встряхивать и вращать, чтобы поднять все нерастворимые примеси. Манипулятор достаточно легко может это осуществить с помощью одного из своих сервоприводов.
С помощью небольшого количества модификаций (например, добавления датчика усилия захвата, накладок из более мягкого/жесткого материала и т.д.) ее можно применить для решения большого числа задач.
Конечно, у манипулятора есть и очевидные минусы:
скорость работы уступает другим решениям;
относительно маленькая рабочая область, необходимо компактное размещение рабочих элементов;
точность работы зависит от сервоприводов и габаритов манипулятора, может не соответствовать требованиям для высокоточных работ.
В нашем случае перечень требований к манипулятору был примерно таким:
грузоподъемность - 10-15 г (почти все манипуляторы в этом классе отвечают данной характеристике);
достаточное усилие на захвате для того, чтобы не выронить ампулу;
точность установки захватывающей лапы - разброс должен составлять менее 4 мм;
минимальное количество степеней свободы - 4;
суммарная длина колен - не менее 40 см;
управление через USB (необходимо для удобного управления через Jupyter Notebook).
Всем требованиям как раз соответствовала модель Hiwonder LeArm. Это рука-манипулятор на базе Arduino Nano с шестью степенями свободы (т.е. в ней 6 сервоприводов). У нее открытый исходный код и довольно хорошая документация (естественно, на китайском).
Манипулятор поставляется в различных вариантах комплектации, можно заказать в собранном или разобранном виде. С нашей же стороны потребовалась небольшая доработка. Мы добавили резиновые накладки на клешню, повернули сервопривод клешни на 180 градусов, чтобы он не стукался об стол при маленьких углах наклона.
Управляющий контроллер | Nano (Arduino-совместимый контроллер) |
Управление | с помощью джойстика, через USB |
Функциональные возможности | возможность захвата предметов, 6 степеней свободы |
Материал | алюминий |
Питание | блок питания 7,5 В DC |
Габариты робота | 285 мм x 120 мм x 465 мм |
Вес | 1,24 кг |
Манипулятором можно управлять несколькими способами: через USB-провод с компьютера, через Bluetooth (но у нас была заказана комплектация без Bluetooth-модуля) и с помощью беспроводного геймпада. Удобнее всего было пользоваться USB-подключением, а с помощью китайского описания протокола общения и питоновской библиотеки `pyserial` стало возможным управлять рукой прямо из Jupyter ноутбука.
Пример команды, которую нужно подать на USB-порт, чтобы первый сервопривод пришел в положение 2000 за 1 секунду:
0x55 0x55 0x08 0x03 0x01 0xE8 0x03 0x01 0xD0 0x07
import serial
import struct
cmd_servo_move = b'\x03'
frame_header = b'\x55'
COM = 'COM4'
baudrate = 9600
ser = serial.Serial(COM, baudrate)
def moveServo(servoid, position, time=1000):
buf = frame_header + frame_header # Header of command
buf += b'\x08' # Length of parameters
buf += cmd_servo_move # Command type
buf += b'\x01' # Number of servos moved
buf += struct.pack('<h', time) # Time of command
buf += struct.pack('<b', servoid) # ID of servo to move
buf += struct.pack('<h', position)# Position of servo to be moved to
ser.write(buf)
Управление движением манипулятора
Обратная (инверсная) кинематика - это процесс определения параметров связанных объектов для достижения ими заданной позиции (подробнее - добро пожаловать на Википедию). В нашем случае требовалось по положению объекта в пространстве найти углы поворота пяти сервоприводов (шестой отвечает за сжатие-разжатие клешни).
Сначала мы пробовали решить задачу просто. Чтобы определить положение ампулы в пространстве, использовали лист А4, закрепленный неподвижно относительно основания манипулятора. Ввели двумерную систему координат: оси совпадают с перпендикулярными сторонами листа, пересекаются в его нижнем углу. Дополнительно замерили x0 и y0 - проекции расстояния от начала координат на наши оси. В коде записали, что координаты центра манипулятора - (-x0; -y0).
Все расстояния и координаты определяются в миллиметрах. Третье измерение не нужно, поскольку определяемые объекты всегда находятся на одной высоте, которую также можно измерить.
Теперь, если нанести на лист бумаги сетку (нарисовать сетку 50х50 мм обычной шариковой ручкой), можно будет довольно легко найти координаты ампулы на этом листе, а затем и координаты вектора, соединяющего центр манипулятора с ампулой.
Ещё раз картинка
Определимся с тем, что будет делать каждый из сервоприводов манипулятора в решаемой задаче с ампулами:
1 - берет ампулу в зоне своей видимости;
2 - работает шейкером (взбалтывает ампулу, чтобы поднять все взвеси со дна);
3, 4, 5 - пытаются вместе подвести клешню манипулятора точно к ампуле;
6 - вращает манипулятор вокруг оси, перпендикулярной земле.
Разделим задачу взятия ампулы на несколько частей.
1. Определим угол поворота основного сервопривода.
Это делается по координатам ампулы и центра манипулятора. Пояснения, думаю, тут будут лишними. Останется только повернуть шестой сервопривод в соответствии с этим углом.
2. Подводим клешню манипулятора к ампуле.
Поскольку мы повернули манипулятор так, чтобы он смотрел прямо на ампулу, можем перенести эту задачу в 2D-пространство. Поместим начало координат в начало первого колена (см. рисунок ниже). С помощью трех колен нам нужно точно попасть в точку P(x, y), где x - это расстояние от центра манипулятора до ампулы по рисунку из первого пункта , а y определяется из высоты подставки манипулятора и высоты самой ампулы. Для программы значение “y” всегда было равно 70, поскольку самый первый сервопривод находился ровно на семь сантиметров выше того места, за которое можно было ухватить ампулу.
Длины всех колен манипулятора известны (а если нет, то берем линейку и замеряем) - назовем их l1, l2 и l3. По данным условиям нужно найти углы θ1, θ2 и θ3.
В пакете с манипуляторам шла программа для решения точно такой же задачи, но при заданном угле α, что фактически сводило задачу к нахождению только двух углов. За основу был взят алгоритм работы этой программы, а угол α находился методом подбора, что сильно упростило жизнь разработчикам, но при этом немного подкосило точность работы манипулятора. В финальной версии продукта будет использоваться другой алгоритм.
3. Сжимаем клешню.
Для этого на первый сервопривод просто подаем значение, нужное для аккуратного обхвата ампулы, которое измерим заранее. Чтобы освободить ампулу, возвращаем сервопривод в начальное положение.
После тестов и более точной подгонки параметров, можно демонстрировать первые результаты работы: манипулятор может сам поднимать и опускать ампулы, расположенные в определенных координатах на листе. Ниже видео, как это выглядит.
Что делать с фотографией рабочей поверхности
При указании координаты на листе бумаги манипулятор может схватить тестируемый объект и поставить его на место. Это очень круто, но нас ждет новая задача - научить манипулятор отслеживать положение ампул автоматически.
Очевидно, что можно искать контур белого пятна на фотографии и пытаться вписать в него прямоугольник, но в нашем случае это нецелесообразно, поскольку мы сами создаем условия работы прототипа. Мы в таких задачах обычно используем ArUco маркеры.
ArUco маркер - это специальный (фидуциальный - для знающих) маркер в виде квадрата с однозначно идентифицируемым узором. На Хабре про него тоже есть пара интересных статей (например, вот). Эти маркеры широко распространены для определения положения объектов в пространстве (если вы видели, как издеваются над роботами в Boston Dynamics, то можете вспомнить, что на каждом объекте, с которым взаимодействует робот, есть похожая наклейка). Мы же используем эти маркеры для нахождения углов рабочей поверхности.
Модуль aruco в openCV включает в себя предопределенные словари маркеров. Мы будем использовать словарь из 50 маркеров размером 6х6. Как генерировать и искать маркеры, хорошо расписано вот здесь.
Вырезав и наклеив на бумагу маркеры, мы написали небольшой скрипт, который ищет положение листа на фото с камеры.
import cv2
from cv2 import aruco
aruco_dict = aruco.Dictionary_get(aruco.DICT_6X6_50)
def findMarkers(img):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
parameters = aruco.DetectorParameters_create()
corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, aruco_dict, parameters=parameters)
return(corners, ids)
ArUco маркеры лежат в библиотеке opencv-contrib-python, поэтому если у вас стоит обычная версия cv2, то выполните следующие команды:
pip uninstall opencv-python
pip install opencv-contrib-python
Для работы с перспективой у openCV есть отличная функция - getPerspectiveTransform. С ее помощью можно найти 3x3 матрицу перспективного преобразования. Перемножив наш вектор координат на матрицу преобразования, получаем координаты. Обратите внимание на то, что для преобразования используется однородная система координат. Поскольку мы решаем задачу двумерного случая, то для определяемой точки задаем третью координату, равную единице; после преобразования нужно промасштабировать результаты относительно полученной третьей координаты.
def position_on_paper(paper_pos, x_pos, y_pos):
pap_x = settings.paper_x_length
pap_y = settings.paper_y_length
paper_coords = np.float32([[0, pap_y],
[pap_x, pap_y],
[0, 0],
[pap_x, 0]])
paper_pos = np.float32(paper_pos)
matrix = cv2.getPerspectiveTransform(paper_pos, paper_coords)
point = np.float32([x_pos, y_pos, 1.0])
transf_point = matrix.dot(point)
transf_point[0] /= transf_point[2]
transf_point[1] /= transf_point[2]
return(int(transf_point[0]), int(transf_point[1]))
ML-модель для определения ампулы в кадре
Для определения положения объектов в пространстве мы взяли YOLOv3 - сверточную нейросеть, способную работать в режиме (ну почти) реального времени. Основная информация по ней лежит вот тут, а вот здесь лежит darknet - фреймворк для работы с ней (оставляю ссылку на гит AlexeyAB, потому что с этой версией удобнее работать + там есть подробные инструкции).
Для работы нужно рассказать обучаемой модели как можно больше о предметах, которые в дальнейшем она будет искать на фото. Если вам нужно искать на картинке лошадь, собаку или бокал с вином, то можно скачать предобученные на MS COCO датасете веса (тоже здесь). Но если обобщать на случай поиска любого предмета, нужно собирать собственный датасет.
Для датасета было сделано 150 фотографий, на каждой из которых находилось от 1 до 5 ампул. Было важно подобрать фотографии с разными фонами и освещением, чтобы модель увереннее чувствовала себя на этапе инференса. Разметка производилась с помощью LabelImg, а весь процесс занял полтора-два часа. Оставалось обучить модель с помощью упомянутого выше darknet (подробно можно почитать в readme репозитория darknet или посмотреть тут) и получить веса модели, готовые к работе.
net = cv2.dnn.readNet(yolo_weights, yolo_cfg)
size = (608, 608)
with open (yolo_classes) as f:
labels = f.read().strip().split('\n')
layer_names = net.getLayerNames()
out_layer_indexes = [index[0] - 1 for index in net.getUnconnectedOutLayers()]
out_layer_names = [layer_names[index] for index in out_layer_indexes]
def find_ampoules(img):
height, width, _ = img.shape
blob = cv2.dnn.blobFromImage(img, 1/255, size, swapRB=True)
net.setInput(blob)
out_layers = net.forward(out_layer_names)
object_boxes = []
object_probas = []
object_labels = []
for layer in out_layers:
for result in layer:
x, y, w, h = result[:4]
x = int(x * width)
y = int(y * height)
w = int(w * width)
h = int(h * height)
#Находим наиболее вероятный класс объекта
probas = result[5:]
max_proba_index = np.argmax(probas)
max_proba = probas[max_proba_index]
#Если есть вероятность, что это какой-то объект
if max_proba > 0:
#Записываем информацию о нем в массивы с данными
object_boxes.append([x, y, w, h])
object_probas.append(float(max_proba))
object_labels.append(labels[max_proba_index])
#Применяем NMS-преобразование, чтобы объединить рамки одного и того же объкта
filtered_boxes = cv2.dnn.NMSBoxes(object_boxes, object_probas, 0.0, 0.3)
#Запишем все данные о наших объектах в массив
objects = []
for index_arr in filtered_boxes:
index = index_arr[0]
x, y, w, h = object_boxes[index]
label = object_labels[index]
proba = object_probas[index]
objects.append((label, (x, y, w, h), proba))
return objects
Что происходит на дне ампулы
Однако и у данного решения есть проблема. Оно не позволяет точно определить центр ампулы на бумаге. Сейчас это решается с помощью небольшого смещения к точке, определяемой вручную при запуске программы.
Для того, чтобы не опрокидывать лишние ампулы при работе, отсортируем ампулы по расстоянию до манипулятора и будем брать всегда самую ближнюю, при этом сразу поднимая ее над остальными.
Сборка прототипа и видео
В условиях теста манипулятор проваливал переноску всего 1 ампулы из 100, а после небольших доработок в коде и уточнении координат положения стал работать еще точнее.
Вывод
Для заказчика был создан рабочий прототип, удовлетворяющий всем требованиям. На производстве начнут использовать уже промышленные решения с доработанными моделями. Но именно применение прототипирования позволило заранее убедиться, что задача решаема, а также узнать, какие дефекты модель не распознает, с какой скоростью работает и точнее определить потребности заказчика.
В статье мы рассказали, как разрабатывали прототип визуального контроля качества ампул, однако применение ML моделей достаточно универсально и они могут быть использованы для решения многих проблем практически на любом типе производства. А это значительно удешевит себестоимость продукции.
PS: Если у вас есть желание попробовать поуправлять манипулятором самостоятельно, то есть идея дать доступ к его управлению и данным с камер, например, через Telegram бота. Пишите в комментариях, так сказать.
P.S.S: В статье намеренно не затрагиваются процессы продуктивизации решения, так как для фармацевтической промышленности они весьма специфичны. А также не проводится сравнение уже существующего на рынке оборудования, так как в процессе его изучения выяснилось, что оно не позволяет выявлять все виды дефектов.