Захват изображения с USB камеры при помощи STM32

image
Для собственного самообразования решил подключить USB камеру (вебкамеру) к STM32. У меня уже была отладочная плата на базе STM324F429, способная выводить изображение на VGA монитор, так что для проверки работы камеры я использовал именно ее.


Что выбрать: HAL или SPL?
Понятно, что USB-контроллер должен работать в режиме Host. До этого с USB Host я толком не работал, а в данном случае нужно было обязательно использовать режим Isochronous Transfers, для которого традиционно очень мало примеров. При этом для HAL STM32 Cube может генерировать код для USB Host Audio Class, так что в качестве основы я использовал именно сгенерированный пример. Этот пример предназначен для работы с USB аудиокартой.
Чтобы код драйвера USB начал выдавать отладочные сообщения, нужно установить константу:
#define USBH_DEBUG_LEVEL      3

После подключения аудиокарты микроконтроллер ее действительно обнаружил — драйвер USB и код аудиокласса выдали различные отладочные сообщения в окно вывода Semihosting, что означало, что железо работает нормально.
Дальше я занялся переделкой кода аудиокласса в класс UVC.

Стоит отметить, что большинство USB видеокамер работают с использованием специального USB-класса UVC (USB Video Class).
Ранее я уже сталкивался с ним.

Попробую кратко рассказать, что происходит у меня в программе.

1. Анализ дескрипторов
Первым делом нужно получить от камеры дескрипторы и проанализировать их.
Получением дескрипторов от камеры занимается драйвер USB от STM, так что пользователю остается лишь их анализ. При этом важно, чтобы значение «USBH_MAX_SIZE_CONFIGURATION» было достаточно большим (у меня оно равно 1024), иначе получаемые дескрипторы просто не уместятся в памяти контроллера.

При анализе дескрипторов нужно проверить, относится ли подключенное устройство к классу UVC.
Также в составе дескрипторов нас интересуют различные варианты настройки интерфейса, в частности — его номер и размер конечной точки (endpoint). Камера передает несколько вариантов размера конечных точек, которые она поддерживает, и нам нужно выбрать наиболее подходящий.

Допустимый размер конечной точки ограничивается аппаратным буфером FIFO контроллера. То, как этот FIFO будет распределен для использования под разные задачи USB, настраивается в файле «stm32f4xx_ll_usb.с» (искать по слову «GRXFSIZ»). К сожалению, необходимые константы там забиты прямо в коде, так что мне пришлось поправить этот файл, чтобы максимально увеличить область RX FIFO.

Кроме того, в дескрипторах передаются все возможные режимы передачи изображения камерой. Среди них можно выделить дескрипторы типа «Format Type Descriptor» — они описывают возможные форматы передачи изображения. Нас интересуют наиболее распространенные YUY2 (Uncompressed Format) и MJPEG (MJPEG Format). Также после каждого из таких дескрипторов идут несколько дескрипторов типа «Frame Type», каждый из которых соответствует определенному размеру изображения, передаваемого камерой.
Каждый из этих дескрипторов соответственно содержит поля «bFormatIndex» и «bFrameIndex», значения которых можно использовать для выбора режима работы камеры.

Таким образом, для пользователя задача анализа дескрипторов сводится к тому, чтобы найти нужные дескрипторы в массиве данных, полученном от камеры, и определить подходящие значения адреса конечной точки, по которой будут передаваться данные от камеры, номера интерфейса, bFormatIndex, bFrameIndex.

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

Здесь стоит заметить, что пример кода от STM выглядит очень путано. В нем вроде бы и сделана поддержка режимов OUT (передача данных от хоста) и IN (передача данных к хосту — нужный нам вариант), но в реальности режим IN реализован только частично. Зато разработчики напихали туда работу с HID (для регулировки громкости), и огромную кучу разнородного кода поместили в один единственный файл. Мне пришлось значительно переработать весь этот файл, к примеру, я выделил анализ дескрипторов и работу с принятыми данными в отдельные файлы.

Сам драйвер USB содержит в себе баг, нарушающий работу конечных точек Isochronous IN.

2. Настройка параметров камеры
Следующий этап — передать камере определенные параметры — в частности «bFormatIndex» и «bFrameIndex», для того, чтобы указать ей, какого размера и в каком режиме она должна передавать изображение.
В HAL этот процесс производится в функции USBH_UserProcess().
В протоколе имеются специальные запросы (Request) GET_CUR — запрос текущих настроек камеры и SET_CUR — установка новых параметров камеры. Каждый из этих запросов должен быть определенного типа — PROBE_CONTROL (пробная установка параметров) и COMMIT_CONTROL (подтверждающая установка параметров).
Таким образом, Host вначале отправляет определенные настройки камере (SET_CUR + PROBE_CONTROL), затем считывает их из камеры (GET_CUR + PROBE_CONTROL). Уже на этом этапе камера должна вернуть адекватные значения, включая те, что были установлены. После этого нужно подтвердить установку значений, отправив запрос (SET_CUR + COMMIT_CONTROL).

3. Запуск передачи данных
На этом этапе нужно отправить на камеру запрос «Set Interface». Он уже реализован в драйвере USB — в виде функции «USBH_SetInterface()». В качестве параметров в нее нужно передать значения «bInterfaceNumber» и «bAlternateSetting», полученные ранее во время анализа дескрипторов.
Получив этот запрос, камера начинает передачу данных на микроконтроллер-Host.

4. Обработка принимаемых данных
После того, как камера начинает предавать данные, задачей контроллера становится их обработка.
Пользовательский код должен не реже, чем в 1 мс проверять, не появились ли новые данные от камеры; и если они появились, обрабатывать их.
В HAL предполагается, что пользовательский код обработки данных должен вызываться из callback функции «BgndProcess», которая, в свою очередь вызывается из функции MX_USB_HOST_Process() — единой функции HAL для управления USB-Host. В HAL опять же предполагается, что функция MX_USB_HOST_Process() располагается в главном цикле (суперцикле) программы. Понятно, что любая длительно выполняющаяся функция в суперцикле блокирует вызовы MX_USB_HOST_Process(), и часть USB данных от камеры теряется. Чтобы избежать этого, мне пришлось вынести обработку данных от камеры в прерывание от предварительно настроенного таймера.

Камера передает данные в Isochronous пакетах. По стандарту UVC, каждый пакет содержит заголовок (обычно 12 байт длинной) и полезные данные. Для нас в заголовке важным является только второй байт — он содержит в себе битовые флаги, несущие информацию о пакете.
Один из битов: EOF — показывает, что текущий пакет данных является последним в кадре. Другой бит: FID — переключатся при смене кадра. Используя эти биты, можно обнаруживать начало и конец кадра. При получении пакета с полезными данными (не все пакеты их содержат), программа копирует их в кадровый буфер. В своей программе я реализовал двойную буферизацию принятых данных (то есть используются два кадровых буфера).

После того, как весь кадр получен, его нужно отобразить на экране.
В случае, когда используется режим несжатых данных YUY2 это сделать достаточно просто — каждые 4 байта данных из кадрового буфера соответствуют двум пикселям изображения.
Отмечу, что YUY2 изображение размером 160x120 занимает 38400 байт в RAM. В принципе, если для работы с изображением не нужна цветовая информация, то половину передаваемых камерой данных можно отбрасывать, за счет чего размер кадрового буфера сокращается вдвое.

В случае MJPEG обработка данных сложнее — каждый кадр представляет собой JPEG изображение. Для декодирования полученных изображений я использовал библиотеку TJpgDec от elm-chan. Однако и здесь все не так просто — тут я просто процитирую Википедию:
Заголовок каждого кодированного MJPEG обычно соответствует стандарту JPEG, однако, допустимыми являются некоторые несоответствия стандарту. Так, например, в нём может отсутствовать маркер DHT, определяющий таблицы для хаффмановского декодирования. В этом случае в процессе декодирования следует использовать таблицы, приведённые в разделе K.3 стандарта JPEG (CCITT Rec. T.81).
Таким образом, мне пришлось доработать декодер MJPEG, чтобы он брал предварительно подготовленные DHT данные из Flash памяти.

Получившаяся производительность в режиме YUY2 — с учетом отрисовки на экране:
160x120 — 15 FPS.

Производительность в режиме MJPEG — с учетом отрисовки на экране:
160x120 — 30 FPS.
320x240 — 12 FPS.
640x480 — 4 FPS.

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


Стоит отметить несколько нюансов использования USB видекамер, ограничивающих их использование вместе с микроконтроллером.
  • Не все камеры работают в UVC режиме. Мне несколько раз встречались подобные камеры.
  • Встроенный в STM32 USB Host работает только в Full Speed (FS) режиме. Мне попадались камеры, которые вообще не передавали никаких данных в этом режиме. Причем именно такую камеру я подключил к контроллеру первой, и долго пытался понять, почему ничего не работает. Позже я подключил ее к ПК, у которого в BIOS был отключен USB 2.0, и убедился, что с ним камера тоже не работает. В режиме HS камера с ПК работала без проблем.
  • Поддерживаемые разрешения для режимов FS и HS различаются (в зависимости от режима камера передает разные дескрипторы), в режиме FS их может быть меньше.
  • В режимах YUY2 и MJPEG варианты разрешения могут быть различными.


Также я написал пример подключения камеры с STM32F4-DISCOVERY. Поскольку у меня не было экрана, который можно было бы подключить к этой плате, то в этом примере я просто отправляю принятый кадр на ПК. Для этого используется специальный отладочный механизм IAR (используя yfuns.h).

И напоследок:
В случае использования режима YUY2 полученное изображение на ПК можно посматривать при помощи программы 7yuv (нужно выбрать в ней режим YUV422 YUYV).

Для анализа дескрипторов устройств, подключенных к ПК очень удобно использовать утилиту USBView от Microsoft.

На всякий случай — подробная статья про подключение USB камеры к ARM Cortext-M3 SAM3X.

Проект на Github: github.com/iliasam/STM32_HOST_UVC_Camera
  • +9
  • 29 сентября 2018, 22:18
  • citizen

Комментарии (5)

RSS свернуть / развернуть
Теперь осталось нейросеть подключить для распознавания лиц.
Спасибо за статью. Интересные результаты.
0
Мне попадались камеры, которые вообще не передавали никаких данных в этом режиме.
Поддерживаемые разрешения для режимов FS и HS различаются
Ну это логично. У FS для видео очень мала пропускная способность.
Получившаяся производительность в режиме YUY2 — с учетом отрисовки на экране:
160x120 — 15 FPS.
Что лимитирует? Полоса пропускания USB или декодирование на стороне МК? Помнится, у EyeToy тоже на редкость скромные показатели — из-за USB 1.1 на плойке.

У STM32F4 встроенного интерфейса камеры нет?
0
  • avatar
  • Vga
  • 30 сентября 2018, 04:39
Что лимитирует?
Насколько я понял, именно полоса USB.

У STM32F4 встроенного интерфейса камеры нет?
DCMI? Есть, у меня на отладочной плате есть для подключения камеры, и я ее запускал: github.com/iliasam/stm32f429_vga_examples

Но здесь мне захотелось именно USB камеру запустить.
К Discovery вроде бы к DCMI камеру просто так не подключить — нужно снимать с нее некоторые компоненты.
0
На DCMI производительность лучше?
0
  • avatar
  • Vga
  • 30 сентября 2018, 17:10
Я получал 23 FPS на 640x480 с DCMI:
vk.com/video24888157_456239026
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.