InquirerWhy time rice imports with local harvest season? Pangilinan smells ‘modus’ESPN DeportesAsí vivimos remontada de Rays en J4 de Serie Mundial para seguir con vidaESPNBen Williamson walks it off in the 12th inning to extend Rays' seasonDaily MaverickWHAT WE’RE WATCHING: Al Wat Ek Weet — a local film’s heartfelt message about adolescent mental healthThe Jerusalem PostPolice, emergency services searching for people, vehicles trapped in southern Israel floodsThe Hollywood Reporter‘Snowfall’ Spinoff Boss Malcolm Spellman Lays Out Multi-Season Plan for ‘The Drop,’ Why Season One Was Just a “Dash of What’s Coming”ZDF heuteEntdecken Sie das ZDF-NachrichtenstudioCNN TürkEmekli zammı ne kadar olacak?Impress Watch“指示待ちAI”からの脱却 Metaが語るパーソナルAI「Muse」The Japan TimesDown by the River Thames, a failure to build holds back Britain’s growthVanguardAgric ministry puts post-harvest loss at $10bn yearlyEl ComercioEfemérides del 28 de octubre: ¿Qué pasó en el mundo un día como hoy?
The Daily Newsstand · Free, Always
Wednesday, October 28, 2026

[Перевод] Компьютерное зрение на микроконтроллере за $2: SIMD на ESP32-S3 ускорил FAST в 2,2 раза

Translate

Я написал ускоренный детектор особых точек FAST для ESP32-S3 на его 128-битных SIMD-инструкциях. Он работает примерно вдвое быстрее эталонной реализации и обрабатывает кадр QVGA (320×240) примерно за 6 мс. Исходный код лежит здесь.

За свои деньги ESP32-S3 — очень мощный микроконтроллер. В невзрачном пластиковом корпусе прячется двухъядерный процессор с частотой до 240 МГц и куча периферии, включая радиомодули Wi-Fi и Bluetooth Low Energy. Копаясь в его техническом справочнике, я обнаружил, что чип поддерживает ограниченный набор SIMD-инструкций. Для кремния, который стоит дешевле чашки кофе, это здорово. Посмотрим, что с ним можно сделать!

Пока я писал детектор углов FAST на SIMD, я освоил основы ассемблера ESP32-S3, научился обходить его странные ограничения и в итоге написал собственный простой распределитель регистров basm.

Что уже было

Поискав ещё, я понял, что эти инструкции вовсе не секретные, просто малоизвестные. Мне хотелось поэкспериментировать с ними самому и написать что-то сложнее того, что можно найти в сети. Компьютерное зрение меня в последнее время очень увлекает, поэтому я решил сделать SIMD-версию детектора особых точек FAST для ESP32-S3.

Начал я с поиска примеров кода с SIMD-инструкциями. На момент написания статьи я нашёл всего три источника:

Просмотрев их, я с облегчением понял, что ассемблер на ESP32-S3 не так уж страшен. В итоге я остановился на такой общей структуре:

    .section .rodata
	# read-only data here
    .section .text
    .align 4
    .global add
    .type   add,@function
# a simple function that adds two numbers
# int32_t add(int32_t a, int32_t b)
add:
    entry           sp, 48 # sp (stack pointer) is an alias for a1
    #define r_a a2
    #define r_b a3
	# code here
	add a2, r_a, r_b # a2 = a + b
    retw.n # return, with a2 containing the return value.

Первое ограничение: невыровненное чтение

Дальше я взялся за SIMD-версию предварительной проверки угла в FAST. По сути, она берёт по одному пикселю в каждой из четырёх сторон света и проверяет, есть ли среди них хотя бы три «экстремальных». Экстремальным здесь считается пиксель, который отличается от центрального по модулю больше заданного порога.

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

Но тут я упёрся в первое ограничение: набор инструкций не позволяет напрямую читать невыровненные данные. Если считать, что центральный блок выровнен, то восточный и западный окажутся невыровненными:

               [ north ]
    [ west ]   [ center ]   [ east ]
               [ south ]

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

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

Второе ограничение: сравнение только со знаком

Так, данные пикселей есть. Пора сравнивать, да? А вот и нет. Оказалось, что ESP32-S3 умеет сравнивать только знаковые восьмибитные числа. Это усложнило дело, ведь пиксели у меня хранились как беззнаковые восьмибитные значения. Если просто переинтерпретировать беззнаковые данные как знаковые, получится странное:

>>> np.int8(np.uint8(range(0, 256, 8)))
array([   0,    8,   16,   24,   32,   40,   48,   56,   64,   72,   80,
         88,   96,  104,  112,  120, -128, -120, -112, -104,  -96,  -88,
        -80,  -72,  -64,  -56,  -48,  -40,  -32,  -24,  -16,   -8],
      dtype=int8)

Диапазон [128, 255] нелинейно отображается в [-128, -1], и все сравнения ломаются. Чтобы это заработало, нужно было сдвинуть значения из [0, 255] в [-128, 127]. Казалось бы, обычное вычитание, но и тут помешал набор инструкций: на ESP32-S3 вся SIMD-арифметика с насыщением. Значит, все значения пикселей выше 127 вместо того, чтобы перейти через границу и получить правильный результат, упёрлись бы в -128. Пришлось как-то вычесть 128 из каждого числа параллельно, не пользуясь инструкцией вычитания. Сначала задача показалась мне неподъёмной, но в итоге решение нашлось.

Вычитание через XOR

Пусть x — уменьшаемое. Вот веса его разрядов:

-128 64 32 16 8 4 2 1

Замечаете что-нибудь интересное? Вычесть 128 — то же самое, что прибавить -128. Поэтому, чтобы вычислить x - 128, достаточно «прибавить единицу» к старшему разряду x, вес которого равен -128. А это просто инверсия старшего бита x. XOR любого бита с единицей равносилен побитовому НЕ, значит, старший бит x можно точечно инвертировать, сделав XOR с маской 0b10000000, то есть 0x80. Следовательно, x - 128 == x ^ 0x80.

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

С этими двумя приёмами мне удалось обойти ограничения ESP32-S3 и написать SIMD-версию предварительной проверки угла в FAST. Ещё я написал на SIMD функцию оценки углов, где используется похожий набор операций, но о ней здесь рассказывать не буду.

Результат

В итоге в моих тестах производительность детектора FAST выросла примерно до 220% от исходной: с 5,1 до 11,2 мегапикселя в секунду. Этого с запасом хватает для компьютерного зрения в реальном времени: ESP32-S3 спокойно справляется с потоком VGA при 30 кадрах в секунду. Неплохо для чипа за два доллара!

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.