«Поляризация» машинному зрению вместо свёрточных нейросетей и чем отличается мой генератор карт от алгоритма Брезенхема

от автора

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

Сначала о том, каким алгоритмом я планирую заменить в своих работах свёрточные нейросети. Чтобы это работало быстро — нужны карты трассировок. Линии трассировок на карте расположены параллельно под определённым углом на каждой карте — так и происходит условная поляризация. Генератор карт работает быстро и генерирует он карты трассировок направленных прямыми линиями, обрыв каждой линии он отмечает в данных. То-есть сначала запускатеся генератор карт и генерирует картинку, данная анимация существенно отличается от работы генератора и показывает только его ТЗ — в каждом пикселе карты записать координаты следующего пиксела и обозначить в данных окончание каждой линии. Изображения я взял небольшие, но тем не менее файлы анимации достаточно увесистые. Допустим что обрабатываемые изображения будет 7*7 пикселов, а карт трассировок всего четыре, тогда ТЗ генератора примерно будет выглядеть так, но на самом деле его алгоритм намного сложнее и работает на много быстрее — он ничего практически не считает и выдает большие объёмы данных автоматически, но об этом позже, а пока так чисто визуально

Здесь четыре слоя — четыре карты, линии трассировок разных карт расположены с интервалом 45 градусов. Сам код генератора пока может от 0-90 для любых размеров обрабатываемых изображений, но с ограничем высоты изображения — не менее трёх пикселов, так как я вместо блака проверки условий if вставил свою формулу от математической логики — так проще и быстрее. Можно было алгоритмом Брезенхема, но для поставленной задачи он абсолютно аутичен — медленнен. Ниже я разберу алгоритм своего генератора, но пока о алгоритме замены свёрточных нейросетей.

Алгоритм обрабатывающий изображение следует по пикселам согласно трассировкам по картам, где у него обозначен конец каждой линии, как только на изображении соотвествующий пиксел окрашен в искомы свет, алгоритм на своём «поляризованном» слое увеличивает вес этого пиксела и увеличивает вес пикселов линии пока не встретит пустой пиксел на изображении, или не кончится линия, после чего вес обнуляется. В реализации может быть немного иначе, но ТЗ алгоритма, читающего изображение, именно такое. Слои 1-4, это поляризованные слои алгоритма, 5 — изображение.

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

Что такое свёрточная нейросеть в моём понимании — нечто определяющее и разделяющее данные о размерах линий, пересечениях и образуемых углах.

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

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

Далее по генератору карт, тракотороподобному, но быстрому, изображение старое — поэтому в грязи и пыли ;).

Алгоритм-генератор карт записывает от пиксела входа в какой следующий пиксел следовать алгритму-читателю. Делает он это так, буд-то бы идёт от конца карты к началу и записывает в каждый пиксел сведения координат где только что был, НО, на самом деле всё не так, потому что такой метод требует больших затрат, и реализовано всё иначе — построчно: никаких углов, а вместах скоса алгоритм предполагает-координаты откуда мог попасть в текущий пиксел при таком-то наклоне линии. Тогда для всей карты ничего не надо считать абсолютно, так как скос всегда один — шаг вверх (по машинным координатам Y, где верх — это низ) и шаг вправо. А что-же тогда считать? Считать остаётся всего один правый столбец, для которого формула весьма тривиальна, привожу не для разложения, а для визуальной оценки

TracerX := RazmerX — (stepFrequent * y + znak *trunc(y / (stepsBig+1)));

, и самую нижнюю строку, вот тут вот намного сложнее, так как сляпал на скорую руку какую смог математическую логику, что намного проще чем мудрить с набором операторов if … else … и заниматься самовыносом мозгов самому себе, пусть лучше математика сама себя вывозит

TracerX := x — ( znaksign(trunc((1+sign(y-trunc(y/(stepsBig+1))(stepsBig+1)-steps)/2))) + stepFrequent * y + znak * trunc(y / (stepsBig+1)))+1;

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

Проект выложен тут

https://github.com/Andrei-Y/A-trace-map-generator

Папка для скачивания

generator_0-90_deg3.zip

Интерфейс программы, скрин с фоном стола редим отладки

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

Результат работы проверяющего алгоритма — записывает точки и ставить симовол X в ячейке где обрывается линия.

Пока всё. генератор не доделал до полной развертки от 0 до 180 градусов, так как отвлёкся на пояснительный материал. Генератор быстрый, почти молниеносный, но так-же может сломать мозг. Приведу ядро движка генератора, язык программирование FPC Lazarus.

          repeat             //////////////////////////////////////////////////////////////////////////////////////////////////             {%REGION 'Engine'}             asm                      JMP     p             end;             p1:               StringGrid1.Cells[px^, py^] :=                 IntToStr(pTracerX^) + ',' + IntToStr(pTracerY^) + ',' + '1';             //запись правого столбца             p := @p3;             goto p4;             p2:             x1:=x+1;             y1:=y+1;               StringGrid1.Cells[px^, py^] := IntToStr(px1^) + ',' + IntToStr(py1^) + ',' + '0';             //автоматическое забивание координат в местах скоса трассы             p := @p3;             goto p4;             p3:               x1:=x+1;               y1:=y;               StringGrid1.Cells[px^, py^] := IntToStr(px1^) + ',' + IntToStr(py1^) + ',' + '0';             //автоматическое забивание координат трассы на прямых участках             p4:               Dec(x);             {%ENDREGION}           until x < Biger; 

Скажу сразу что на этом коде всякие блюстители этикета кода вывихнут себе окончательно себе мозги, так как этот код концептуальный, в нём идея, и её нужно понять прежде чем понять то, как это работает. На C или C++ при распараллеливании этот код будет ещё стремительнее, он и так ничего не делает кроме одного столбца и одной строки из всей карты. Алгоритм Брезенхема не предназначен для текущего ТЗ. Ну и собственно с обработкой изображения я ожидаю такого-же эффекта в сравнении с свёрточными нейросетями. Ядро движка немного пришлось изменит при переходе на интервал 0-90 градусов. Далее ещё будет немного. Код самого движка тут под спойлером, остальной код в папке по ссылке что ранее, если у кого-то будет желание почитать и проверить

Hidden text
    repeat       repeat         asm                  JMP     logic         end;         logic1:           logic := @logic2;         goto logic4;         logic2:           Edge := @Edge3;         logic := @logic3;         logic3:           TracerY := y - steps1;         TracerX := 0;         if TracerY < 0 then         begin           TracerX := RazmerX - (stepFrequent * y + znak *trunc(y / (stepsBig+1)));           if TracerX < 0 then TracerX := 0;           TracerY := 0;         end;         logic4:           steps := stepsBig;         repeat           if steps = 0 then           begin             Biger := x - stepRare+1;             steps := stepsBig+1;           end           else           begin             Biger := x - stepFrequent + 1;           end;           if Biger < 0 then Biger := 0;           repeat             //////////////////////////////////////////////////////////////////////////////////////////////////             {%REGION 'Engine'}             asm                      JMP     p             end;             p1:               StringGrid1.Cells[px^, py^] :=                 IntToStr(pTracerX^) + ',' + IntToStr(pTracerY^) + ',' + '1';             //запись правого столбца             p := @p3;             goto p4;             p2:             x1:=x+1;             y1:=y+1;               StringGrid1.Cells[px^, py^] := IntToStr(px1^) + ',' + IntToStr(py1^) + ',' + '0';             //автоматическое забивание координат в местах скоса трассы             p := @p3;             goto p4;             p3:               x1:=x+1;               y1:=y;               StringGrid1.Cells[px^, py^] := IntToStr(px1^) + ',' + IntToStr(py1^) + ',' + '0';             //автоматическое забивание координат трассы на прямых участках             p4:               Dec(x);             {%ENDREGION}           until x < Biger;           asm                    JMP     Edge           end;           Edge1:             //подсчёт шагов в первой строке - других расчётов для первой строки не требуется и знание этого значения нужно для последней строки             Inc(steps1);           goto Edge3;           Edge2:             //трассировку начальной ячейки каждого шага последней обрабатываемой строки кроме правого столбца           if RazmerX > RazmerY then begin                   Dec(steps1);           TracerX := x - ( znak*sign(trunc((1+sign(y-trunc(y/(stepsBig+1))*(stepsBig+1)-steps)/2))) +  stepFrequent * y + znak * trunc(y / (stepsBig+1)))+1;                                        if TracerX<1 then begin                                        TracerY := y - steps1;                                        TracerX := 0;                                        if TracerY < 0 then TracerY := 0;                                        end;          end else begin            Dec(steps1);             TracerY := y - steps1;             TracerX := 0;           end;           p := @p1;           goto Edge4;           Edge3:             p := @p2;           Edge4:             Dec(steps);         until x < 0;         Inc(y);         yf := y + 1;         p := @p1;         x := xRazmerX;       until y > yRazmerY - 1;       Edge := @Edge2;     until y > yRazmerY;

Думаю что данный пояснительный материал вносит какую-то ясность о том, как мой алгоритм ничего не делая справляется с своим ТЗ. Ну и хоть какую-то по паланам замены свёртков. Всем спасибо за внимание, думаю и так понятно что быстрых результатов от меня ожидать не стоит, так как всё делаю в послерабочее время, которого совсем мало для подобных вещей.

Всем всего доброго и успехов.


ссылка на оригинал статьи https://habr.com/ru/articles/743758/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *