Зачем все это
На создание камеры меня подтолкнуло желание поработать с относительного новыми FPGA от компании Gowin, т.к. эти чипы недороги, имеют корпуса пригодные для ручного монтажа, и что меня особенно заинтересовало - наличие в некоторых моделях встроенной SDRAM памяти, что еще больше упрощает создание дешевых печатных плат с этими чипами для DIY. Для проектов, связанных с видео обработкой, наличие SDRAM имеет большое значение, поскольку позволяет буферизировать несколько кадров. В сети мне попался интересный проект OpenMV - это библиотека машинного зрения для микроконтроллеров, и я захотел совместить изучение FPGA Gowin, мой интерес к работе с видео на ПЛИС, и работу с OpenMV. Проект не должен быть сложным, чтобы его можно было реализовать одному человеку в факультативном режиме, и при этом нести хотя бы минимальную новизну. Стерео-камера подошла для этих целей идеально.
Что получилось в итоге
Сенсоры: MT9V034C12STM
Максимальное выходное разрешение: 1504x480
Глубина цвета: до 10бит, монохромный
Частота кадров на максимальном разрешении: 60FPS
FPGA : GW2AR-LV18QN88C8/I7
Logic units/Block SRAM/18x18 Multiplier : 20736/828K/48
32bits SDR SDRAM 64M bits
Слот SD карты
Этапы реализации проекта
Прототип
Прототип был собран на базе проекта Robot Navigation using Stereo Vision, отладочной платы Arrow Deca MAX10, и Arduino PRO Portenta H7 Carrier Board
На первом этапе прототипирования были отработаны способы взаимодействия с Portenta H7 и OpenMV. Нужно было чтобы IDE смогла при подключении идентифицировать плату Deca как сенсор изображения, в противном случае невозможно запустить скрипт MicroPython. OpenMV из коробки поддерживает Arduino Portenta H7, к которой подключается шилд с сенсором HM-01B0, следовательно необходимо было использовать I2C slave IP, который бы по запросу от Arduino имитировал ответ от моей платы аналогичный ответу HM-01B0. Поскольку проект OpenMV открытый, не составило труда найти адрес и номера регистров, к которым обращается процессор для идентификации сенсора, оказалось достаточно лишь ответить на запрос ID чипа по нулевому адресу.
I2C Slave To Avalon-MM Master Bridge Intel FPGA IP конвертирует запрос от шины I2C в протокол шины Avalon MM, а простейший самописный модуль HM-01B0_I2C_Stub подставляет нужные данные по запросу от Avalon Master.
Некоторая сложность возникла с выяснением того в какой момент производится опрос сенсоров. Оказалось, что он делается сразу при включении Arduino а не по нажатию кнопки «Connect» в IDE. При одновременной подаче питания на мой прототип и на Arduino прошивка OpenMV стартовала быстрее чем проект на плате Deca MAX10 и не успевала обнаружить сенсор, таким образом скрипт запустить не удавалось. В качестве временного решения я просто нажимал кнопку сброса Arduino после загрузки проекта FPGA на плате Deca MAX10.
После того как все эти моменты были учтены, удалось запустить получить картинку в среде OpenMV IDE.
Передаваемое изображение в виде монохромных вертикальных полос разной яркости было сгенерировано внутри FPGA с помощью Test Pattern Generator II Intel FPGA IP и Clocked Video Output II Intel FPGA IP
Следующим этапом нужно было получить изображение с сенсоров, для этого была заказана плата из проекта Robot Navigation using Stereo Vision, на которую было смонтировано два видео сенсора MT9V034.
Кроме этого, необходимо было добавить в OpenMV драйвер для разрабатываемого модуля, так как драйвер для HM-01B0 не поддерживал нужного разрешения. Для этого был создан форк прошивки OpenMV, в который я добавил минимально необходимый драйвер, и указал его как поддерживаемый для платы Arduino Portenta H7. Инструкция по сборке проекта и прошивки платы есть в оригинальном репозитории, поэтому я опущу описание этих шагов. Версия библиотеки с моим драйвером доступна на гитхабе.
Видео с сенсоров было обработано с помощью модулей из пакета «Intel Video and Image Processing Suite» (VIP), который содержит в себе необходимые модули для захвата, обработки, и вывода видеопотока. На данном этапе прототипирования сенсоры были инициализированы по умолчанию - в режиме master mode с автоматическим контролем экспозиции. Это позволило не разрабатывать алгоритм синхронного управления сенсорами на данном этапе, а использование пакета VIP Suite облегчило задачу синхронизации и склейки изображений двух источников. В конце концов картинка со стереопары была получена. Из-за навесного монтажа и огромного количества проводов картинка получилась шумной, работа модуля была нестабильна из-за помех или обрыва контакта одного из проводов. Самой большой проблемой была несинхронная работа сенсоров, что напрочь убивало идею получения идеально синхронизированной стереопары с помощью FPGA.
Разработка собственной платы
После выявления всех недостатков разработанного прототипа было принято решение проектировать собственный модуль. Кроме пары сенсоров и FPGA на модуле заложены слот SD карты, который подключается к SDIO интерфейсу Portenta H7, конфигурационная память для FPGA, и разъем программирования JTAG. Для отладки на плате предусмотрено два светодиода и пара контактных площадок, которые я использовал для подключения UART-USB преобразователя для вывода отладочных сообщений. Разработка схемы и платы была заказана у опытного специалиста, и после нескольких недель обсуждения, месяца разработки и месяца изготовления был получен собственный модуль стереокамеры. Готовый модуль представлен на изображении
Разработка прошивки FPGA
Структурная схема проекта представлена на рисунке:
Основные блоки и их назначение:
Cофт-процессор RISC-V PicoRV32. Процессор осуществляет начальную инициализацию сенсоров и модулей камеры, принимает команды по I2C от платы Arduino и перенастраивает параметры в зависимости от принятых команд. В настройках процессора включены модули I2C Master, OpenWB(мастер шины Wishbone) и UART. Процессор использует ончип память для кода и данных. Порт OpenWB подключен к блокам регистров а так же к модулю I2C Slave, который принимает запросы от Arduino. Адресное пространство со стороны Arduino поделено на 3 части, первая часть – это область общих параметров камеры. Втрое и третье адресное пространство напрямую отображается на оба сенсора соответственно, при записи по этим адресам I2C Master IP осуществляет запись по соответствующему адресу сенсора. Таким образом сохраняется возможность конфигурации любых параметров сенсора напрямую из Arduino. Кроме того, процессор содержит модуль UART, который используется для отправки отладочных данных через внешний UART-USB преобразователь.
Registers Array Block IP. Это простой модуль регистров, в который по шине Wishbone процессор записывает параметры системы. Содержит 4 регистра – разрешение по вертикали, разрешение по горизонтали, время экспозиции, и контрольный регистр. Контрольный регистр управляет включением генерации управляющих сигналов для сенсоров в блоке Control Sequence Generator IP.
Control Sequence Generator IP(CSG). Формирует сигналы для управления сенсорами в режиме Slave Sequential Mode. На рисунках ниже представлена схема соединения управлявшего контроллера и сенсора(6), а также диаграмма управляющих сигналов в этом режиме(7).
Генератор формирует сигналы EXPOSURE, STFRM_OUT, и STLN_OUT одновременно для обоих сенсоров. По импульсу EXPOSURE начинается экспозиция кадра, по сигналу STFRM_OUT экспозиция останавливается, заряд из светочувствительных элементов переносится в область памяти для считывания и начинается процесс считывания кадра. Далее генератор начинает выдавать импульсы на выход STLN_OUT для старта считывания очередной строки изображения. В начале кадра считываются так называемся “область гашения” или vertical blanking, в это время выходы FRAME_VALID и LINE_VALID не активны, однако необходимо формировать STLN_OUT так чтобы вся строка изображения могла быть полностью вычитана из сенсора, т.е. учитывать заданную ширину строки. Далее генератор продолжает выдавать импульсы STLN_OUT, на выходах FRAME_VALID и LINE_VALID появляются активные сигналы, маркирующие активное изображение на кадре. После полного считывания кадра необходимо выдать еще два импульса 2 затем не ранее чем через 4 такта системной частоты сенсора можно вновь выставлять сигнал старта экспозиции. Несоблюдение последовательности или времени выдачи управляющих сигналов приводит к тому, что сенсор начинает выдавать данные в хаотичном порядке, пришлось потратить значительное время, чтобы привести автомат генератора к стабильной работе при любом заданном разрешении сенсоров. При нормальной работе сигналы FRAME_VALID и LINE_VALID с обоих сенсоров идентичны, поэтому на Control Sequence Generator заведены сигналы лишь с одного сенсора.
Video Input IP. Захватывает данные активного изображения от одного сенсора на его выходной тактовой частоте 27 МГц, записывает данные в dual clock FIFO и выдает наружу на системной частоте 100 МГц, дополняя видео поток одним дополнительным сигналом – маркером первого пикселя в кадре. Два модуля Video Input принимают на вход данные по независимым частотам от сенсоров и формируют выход в одном тактовом домене.
Stitcher IP. Формирует кадр удвоенной ширины из двух параллельных входных потоков. Состоит из управляющего автомата, мультиплексора, и трех FIFO. Два входных FIFO собирают данные с двух входов, третье FIFO через мультиплексор поочередно принимает данные от входных буферов линия за линией, таким образом чтобы выходной поток в первой линии содержал сначала данные от первого источника затем от второго источника и т.д.
Video Output IP. Генерирует поток видеоданных и сигналы синхронизации для Arduino. Записывает данные в dual clock FIFO на системной частоте 100 МГц и выдает наружу на частоте 54 МГц. Так же поток дополняется «пустыми» областями – vertical blank и horizontal blank. Размеры этих областей заданы минимальными, т.к. опыты показали, что DCMI приёмник Arduino не чувствителен к динамическому изменению размеру этих областей, таким образом выдача новой строки начинается после того, как пройдет время, указанное в параметре horizontal blank и в FIFO накопится достаточно данных для выдачи, что приводит к небольшим изменением размера пустых областей на кадре.
Тестирование камеры
Ниже представлено первое изображение, полученное с камеры в OpenMV IDE и исходный код phyton скрипта:
Еще один вариант картинки, где на сенсоры установлены разные объективы, на правом изображении фокусное расстояние объектива 2,8мм, а на левом – супертелефото объектив 25мм. На изображении нанесен оверлей для удобства поиска увеличенного изображения на обзорной картинке.
Попробуем теперь воспользоваться широкими возможностями OpenMV и определить относительное расстояние до объекта. Простейший способ – так называемый blob detection, когда на равномерном фоне выделяется контрастный объект, и находятся его координаты. Подразумевается, что объект перед камерой один, таким образом мы получим два blob’а находящихся приблизительно на одной вертикальной координате и имеющих смещение по горизонтальной оси, соответствующее расстоянию от камеры до объекта. Истинное расстояние зависит от оптических параметров камеры, но для упрощения мы можем использовать условное «псевдорасстояние», чтобы определять, что камера приблизилась к объекту или удалилась от него:
Такой метод не требует больших вычислительных ресурсов и на разрешении 1280x480 алгоритм обрабатывал около 14 кадров в секунду (в зависимости от времени экспозиции)
Протестируем возможности работы с нейросетями. Для детектирования и классификации объектов предлагается использовать фреймворк TensorFlow-Lite. Фреймворк позволяет очень просто произвести обучение нейросети по набору изображений и сгенерировать файл для развёртывания сети на микроконтроллере. Я использовал открытый проект с уже размеченным датасетом с изображениями человека, и произвел переобучение с параметрами для Arduino Portentа H7 и монохромного сенсора. Идея заключалась в комбинировании метода обнаружения человека с помощью нейросети на одной половине изображения и поиска соответствующего образа с помощью корреляции на второй половине. Такая комбинация позволит определить на снимке несколько персон, и точно сопоставить их на обоих изображениях. Поскольку используется сенсоры с глобальным затвором, а момент захвата кадра синхронизирован до наносекунды, различий в положении человека на левом и правом кадре не будет даже при быстром движении человека или камеры, а значит метод корреляции должен дать удовлетворительный результат.
Размер удвоенного кадра в этом тесте 960x240. Поиск объекта осуществляется на левом полукадре, причем в его середине, т.к. поиск может осуществляться лишь в квадратной области. Таком образом я ищу изображение человека в середине левом полукадра в области 240 на 240 пикселей. Ниже представлен фрагмент кода скрипта, отвечающий за поиск объектов, соответствующих распознаваемым классам. Класс 0 соответствует фону, при его обнаружении просто пропускаем этот объект.
for i, detection_list in enumerate(net.detect(img, roi=NET_ROI,thresholds=[(math.ceil(min_confidence * 255), 255)]) ):
if i == 0:
continue # background class
if len(detection_list) == 0:
continue # no detections for this class?
Если изображение человека найдено, я копирую эту часть изображение с небольшим запасом по краям и произвожу поиск на всем втором полукадре методом корреляции:
img_template = img.copy(1.0, 1.0, ([x-10, y-10, w+20, h+20]), copy=True)
r = img.find_template(img_template, 0.7, step=4 , roi=[WINDOW_FRAME_WIDTH, 0, WINDOW_FRAME_WIDTH, WINDOW_FRAME_HEIGH])
Тесты производись на напечатанных на 3D принтере фигурках людей, общий вид эксперимента представлен на фотографии:
Три различных фигуры расположены на разном расстоянии от камеры. В результате удалось получить одновременно расстояние до трех различных фигур. Стабильность определения человека средняя, не во всех ракурсах камера стабильно определяла все три фигуры, но точность алгоритма корреляции оказалась удовлетворительной, если фигура была найдена на левой части кадра, она всегда находилась и на правой части. Дистанция, как и в эксперименте с blob detection, отображается условно, без пересчета в реальное расстояния.
В данном эксперименте при нахождении трех объектов на правом кадре и, соответственно, трех расчетов корреляции, скорость обработки падала до 4 кадров в секунду.
Планы по доработке устройства
При тестировании вне помещения была выявлена острая необходимость реализации алгоритма автоматической экспозиции и автоматического контроля усиления. В некоторых случаях полезно было бы иметь модуль гамма-коррекции внутри FPGA прошивки. Все эти функции могут быть успешно реализованы на Arduino, однако это повлияет на производительность основного алгоритма пользователя. Так же заложенный чип FPGA обладает запасом ресурсов для реализации предварительной обработки изображения, такого, как например фильтрация или морфологические преобразования. GW2AR имеет встроенную SDRAM память, это позволяет буферизировать кадры, а значит есть возможность реализовать алгоритмы расчета попиксельной разности кадров для определения движения(аналог событийной камеры). Как раз сейчас я работаю над реализацией такого алгоритма.
Выводы
Разработанная камера уже успешно справляется с некоторыми задачами технического зрения, однако она может быть существенно доработана для конкретных задач путем реализации части алгоритмов обработки исходного изображения внутри FPGA
longtolik
На такой камере когда -то делал сканер Datamatrix.
https://youtu.be/MEAieQftYEo?si=2WOF2VgwtiiSWDMh
Изображение очень качественное, даже при 8 битах.