Тестовая картинка для видеопроцессора

Jun 03, 2020 20:50

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




Так наша "мишень" должна выглядеть с расстояния 300 метров. Практически пустой кадр 1024х1024, а вся информативная часть умещается в 32х32 (именно такая эта картинка, я её увеличил до 64х64 методом nearest neighbour, просто чтобы лучше было видно).

Кажется, что именно работа с большой дальности для видеопроцессора наиболее "нервная" - счёт идёт на пиксели, и нужно успевать отправить измеренные результаты в QuatCore, а тем временем уже сбрасывать сумматоры и приниматься за следующий фрагмент!

Попытаемся понять, что же там должно твориться...


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

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

На данном конкретном рисунке, самые яркие пиксели: (14;6), (21;6), (5;10) и (20;21). Исходя из экспозиции мы делаем совсем грубую оценку дальности (плюс-минус лапоть), которой, тем не менее, хватает, чтобы выбрать радиус пятен: 3 пикселя. В смысле что мы хотим использовать при подсчёте яркостного центра все точки (i;j), для которых (i-x0)2+(j-y0)2≤9.

До нулевой строки ни одно из пятен не простирается, поэтому мы должны "заказать" видеопроцессору поискать самый яркий пиксель на всей строке, т.е рассмотреть интервал [0;31]. Это нужно, чтобы убедиться: там, где мы не ожидаем увидеть ярких пятен, их действительно нет. Если там найдётся что-то яркое, и это что-то не будет вычитаться при помощи "темнового кадра", то нужно вернуться к режиму захвата, и скорее всего выдать ошибку. Я предпочитаю быть параноиками: лучше при малейшей неуверенности так и сказать, чем откидывать всё, что не укладывается в нашу "картину мира" и потом вмазаться на скорости...

На первой строке тоже пятен быть не должно, заказываем интервал [0;31]. То же самое на строке 2.

А вот на строку 3 уже могут "выскочить" пятна с центрами (14;6) и (21;6), ровно одним пикселем: (14;3) и (21;3). Поэтому строка разбивается на 5 частей:

[0;13] - найти самый яркий пиксель (и убедиться, что его яркость чрезвычайно мала),
{14} - найти яркостный центр (точнее, найти две суммы, которые QuatCore применит для нахождения яркостного центра),
[15;20] - найти самый яркий пиксель,
{21} - найти яркостный центр,
[22;31] - найти самый яркий пиксель.

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

На строке 4 у нас отрезки, относящиеся к пятнам, расширяются:

[0;11] - захв,
[12;16] - сопр,
[17;18] - захв,
[19;23] - сопр,
[24;31] - захв.

Ровно такая же картина на строке 5.

Строка 6 проходит ровно через центр пятен, поэтому тут они представлены наиболее широко:
[0;10] - захв,
[11;17] - сопр,
[18;24] - сопр,
[25;31] - захв.

Как видно, два пятна буквально упёрлись друг в друга!

И этого нам пока хватит для "раздумий".

Остальные пятна не так интересны - они соблюдают социальную дистанцию.

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

Вполне должна сработать такая метода:

заказываем целую строку, [0;31], на "захват" (т.е найти координаты самого яркого пикселя на строке и саму эту яркость).

Сначала это нулевая строка, там макс. яркость оказывается нулевой. У нас должен быть некий "порог обнаружения", посчитанный "по наихудшему случаю" (один светодиод отказал, их интенсивность - самая маленькая, какая только бывает согласно ТУ, уголковые отражатели максимально загрязнены, и находятся в самых неудачных углах, и на самом краю диаграммы направленности наших осветителей, и т.д., и потом поделить на 2, чтобы уж наверняка), и ноль явно будет меньше этого порога.

Та же история на первой, второй и третьей строках, там нули.

А вот на четвёртой строке самым ярким оказывается пиксель с координатами (14;4), его яркость 437, на шкале 0..4095 (12-битные отсчёты). QuatCore должен хранить список 4 самых ярких пикселей (скорее, чуть больше), вероятно в порядке убывания. Данная точка, (14;4) заносится в этот список, вместе с яркостью 437. И теперь, к следующей строке нужно сформировать участок вокруг неё, чтобы у нас не могло "плодиться" точек одна рядом с другой, а вместо этого обнаружение яркой точке в окрестности уже найденной лишь уточняло бы её координаты и яркость, т.е сначала схватились за край, а теперь подбираемся к центру!

Таким образом, на пятой строке теперь должно сформироваться 3 отрезка: [0;11] (поиск ДРУГОЙ точки), [12;16] (уточнение координат существующей) и [17;31] (поиск ДРУГОЙ точки).

На отрезке [0;11] не обнаруживается ничего (в смысле, макс. яркость 0. В реальности, из-за шумов матрицы, будет не ноль, но всё равно очень мало). На отрезке [12;16] обнаруживается макс. яркость 3040 (вот это уже ЗАЯВКА!) и коорд. (14;5). Понимаем, что точку (14;4) нужно заменить на (14;5), потому что здесь явно до центра ближе!

И наконец, на отрезке [17;31] обнаруживается макс. яркость 1687 (очень неплохо!) в точке с коорд. (21;5). Её мы тоже заносим в список, теперь ярких точек у нас ДВЕ. И готовим "заказы" видеопроцессору к следующей строке: теперь уже окрестности двух точек.

И так далее, и тому подобное. К концу кадра у нас образуется список из самых ярких точек. Их должно быть как минимум 4, иначе сразу "провал", а вот больше четырёх пусть будет. Когда начнётся "темновой кадр" (когда наши осветители отключены), мы закажем окрестности этих точек, и если там тоже найдём столь же яркие, то выкинем их как блики. И уже после этого выкидывания у нас должно остаться 4 точки. Если это так - всё хорошо, на следующем кадре можно уточнить их координаты уже в режиме сопровождения.

При работе с аналоговой камерой высокой четкости (то бишь, на лабораторном макете, сделанном "на коленке") ровно такой алгоритм вполне сработает, благодаря довольно длинному обратному ходу по строке. При работе с фотоприёмной матрицей 1205ХВ014 "обратный ход" составляет всего 8 тактов - маловато, чтобы QuatCore успел подготовиться к следующей строке, поэтому там, скорее всего, стоит разделить каждую строку на две половинки. Делаем первую половинку - забот не знаем, заказываем вторую - обрабатываем первую и придумываем интервалы для первой половины следующей строки. А уже на каком-нибудь "длинном перегоне" в первой половине следующей строки придумаем интервалы для второй половины. Как-то так...

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

GPU [X+i]

где данные будут отсылаться из шины данных в видеопроцессор. И данными этими будет длина очередного отрезка, тип работы (захват или сопровождение, то бишь нахождение максимума или нахождение двух сумм) и, возможно, "тип синхронизации". Нужно, чтобы даже допущенная однажды ошибка в подсчёте пикселей "сбросилась" к следующей строке, с приходом строчного синхроимпульса. Скажем, тип 0 будет означать "начинаем немедленно", тип 1: "начинаем с приходом строчного синхроимпульса", тип 2: "с приходом кадрового синхроимпульса". Всё вместе оно элементарно умещается в 16 бит, и даже "утрамбовывать" 3 числа в 16-битное значение (с помощью битовых сдвигов, побитовых OR и AND) не понадобится: просто будет где-нибудь в памяти хранится две константы, одна для начала новой строки и одна для начала нового кадра. Хотя для выбора типа работы можно и две отдельные команды ввести, скажем, вместо GPU это будут ACQ (от Acquire) и TRK (от Track).

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

C GPU

то есть это запрос на получение данных с видеопроцессора. Какие именно данные - определяется тем, что мы "заказали" ранее. Это либо максимальная яркость (12 бит) и X-координата самого яркого на этом отрезке пикселя (не более 10 бит). Либо: две суммы, притом довольно крупных, одна в 20 бит, вторая в 26 бит, т.е понадобится эдак 4 посылки по 16 бит, чтобы всё это передать.

И всё бы хорошо, если бы не ситуация "то густо, то пусто": то у нас "ничего не происходит" целую строку, 1024 такта подряд. И вдруг начинается серия из очень коротких посылок, вот как в начале поста:
[0;13] - захв
{14} - сопр
[15;20] - захв
{21} - сопр
[22;31] - захв

Только мы отправили "задание" для точки с коорд. 14, как уже должны снять данные, и тут же отправить новую посылку, а времени катастрофически нет, и шина перегружена.

Видимо, нужно ставить и на входе и на выходе FIFO, чтобы можно было и задание "вкачать" заблаговременно, и результаты можно было не торопясь принимать за 2..4 такта, без страха, что они "сгорят" из-за сброса сумматоров.

Хотя есть и другие варианты: мы могли бы дать видеопроцессору прямой доступ к памяти, пусть независимо от QuatCore туда записывает получившиеся результаты.

А может быть, я традиционно слишком жадный и проектирую слишком туповатый видеопроцессор, без своего царя в голове. А правильным решением было бы повысить его автономность: в пределе он сразу выдаст "состояние" (успех/провал) и координаты яркостных центров. Или что-то посередине.

Но тут не только думать, но и прыгать надо :) Вот пока написал программку, которая эту картинку перегоняет в .mif-файл (memory initialization file) для ПЛИС. Получилась такая простыня:

WIDTH=12;
DEPTH=1024;

ADDRESS_RADIX=UNS;
DATA_RADIX=HEX;

CONTENT BEGIN
0 : 000
1 : 000
2 : 000
3 : 000
4 : 000
5 : 000
6 : 000
7 : 000
8 : 000
9 : 000
10 : 000
11 : 000
12 : 000
13 : 000
14 : 000
15 : 000
16 : 000
17 : 000
18 : 000
19 : 000
20 : 000
21 : 000
22 : 000
23 : 000
24 : 000
25 : 000
26 : 000
27 : 000
28 : 000
29 : 000
30 : 000
31 : 000
32 : 000
33 : 000
34 : 000
35 : 000
36 : 000
37 : 000
38 : 000
39 : 000
40 : 000
41 : 000
42 : 000
43 : 000
44 : 000
45 : 000
46 : 000
47 : 000
48 : 000
49 : 000
50 : 000
51 : 000
52 : 000
53 : 000
54 : 000
55 : 000
56 : 000
57 : 000
58 : 000
59 : 000
60 : 000
61 : 000
62 : 000
63 : 000
64 : 000
65 : 000
66 : 000
67 : 000
68 : 000
69 : 000
70 : 000
71 : 000
72 : 000
73 : 000
74 : 000
75 : 000
76 : 000
77 : 000
78 : 000
79 : 000
80 : 000
81 : 000
82 : 000
83 : 000
84 : 000
85 : 000
86 : 000
87 : 000
88 : 000
89 : 000
90 : 000
91 : 000
92 : 000
93 : 000
94 : 000
95 : 000
96 : 000
97 : 000
98 : 000
99 : 000
100 : 000
101 : 000
102 : 000
103 : 000
104 : 000
105 : 000
106 : 000
107 : 000
108 : 000
109 : 000
110 : 000
111 : 000
112 : 000
113 : 000
114 : 000
115 : 000
116 : 000
117 : 000
118 : 000
119 : 000
120 : 000
121 : 000
122 : 000
123 : 000
124 : 000
125 : 000
126 : 000
127 : 000
128 : 000
129 : 000
130 : 000
131 : 000
132 : 000
133 : 000
134 : 000
135 : 000
136 : 000
137 : 000
138 : 000
139 : 000
140 : 000
141 : 11D
142 : 1B5
143 : 1B5
144 : 072
145 : 000
146 : 000
147 : 000
148 : 000
149 : 0AA
150 : 013
151 : 000
152 : 000
.....
END;

(там 1024 строки)

Но зато, если получится по-задуманному, оно влезет в 5576ХС6Т (2880 ЛЭ и 5 килобайт памяти) СО ВСЕМИ НАВОРОТАМИ :) Надо же свою леворукость как-то оправдывать...

Poll Данные с видеообработчика

странные девайсы, ПЛИС, программки, работа

Previous post Next post
Up