Pull to refresh

Comments 32

ничего не знаю в верилоге — а вы можете не записывать, а вычитывать данные по заданному адресу?
Тогда можно было бы рисовать тем же формулами, но с вращением координат чтения — что решало бы и проблемы с пропусками точек и с фоном, когда нет данных по краям :)

Ну или сделать конвейер — выделить два массива (рабочий=0 и буфер=1) и текущую точку просто писать в буфер, а на экран в эту же точку выводить из повернутых координат буфера (который был прошлым кадром). После прохода — поменять ссылки на них (рабочий=1 а буфер=0) %)
Ну это на Си так легко сделать, на ПЛИС не знаю. Вы предлагаете сначала записать кадр без изменения, затем расчитать параметры нового изображения, затем вычитывать из записанного кадра в новый расчитанный кадр, а потом снова вычитывать из нового и выводить на экран. Этот метод хорош при наличии фреймбуфера, который заменит нам «буфер=1», у меня его нет.
Там проще можно сделать. Можно ресайз нового кадра делать чтобы он вписывался в наш фрейм 320x240.
Дело ещё и в том, что уменя места в ПЛИС на подобную обвязку может нехватить. Сам CORDIC отожрал неприлично много ресурсов.
На ПЛИС это делается легко. Для каждого буфера добавляется базовый адрес. Его изменение и дает переключение буферов. В целом, на каком железе это работает? Фреймбуфер во внешней памяти или в блочной памяти плисины?
А нет никакого фреймбуфера, есть только область памяти в SDRAM в которую пишется кадр, а затем читается из неё в FIFO, а оттуда в HDMI. Работает на EP2C8F256.
Идею я понял. Попробую повращать читателя, а не писателя как сейчас сделано.
Я это имел в виду. А вы пробовали вычислять синусы и косинусы табличным методом?
Нет. Мне интересен был именно CORDIC. На него у меня есть планы в дальнейших разработках.
Можно же не считать синусы косинусы в зависимости от угла «на лету», достаточно задать начальные значения для нулевого угла, и две константы — sin(1градус), cos(1градус).
Последующие значения вычисляются по формулам суммы углов синусов и косинусов, в итоге для расчета синуса/косинуса понадобится два умножения и одно сложение.
Как вариант. Изучу этот вопрос. Спасибо.
Даже формула суммы углов не нужна, сам CORDIC позволяет легко доворачивать на малый угол, если использовать результат с предыдущего шага.
Здесь синус и косинус угла надо вычислять заранее только один раз на кадр. Как вариант можно таблицу записать и интерполировать. Но зачем это делать, если это уже сделано в CORDIC?
При рекуррентном вычислении надо быть осторожным. Ошибка копиться будет. Можно обратно в 0 градусов не вернуться.
Вот как раз проходя мимо нуля ошибку и можно сбрасывать — в этот момент считаем не от предыдущего, а от нуля.
Но зачем это делать, если это уже сделано в CORDIC?

Да он жрет много ресурсов, и если без него можно обойтись, нафиг он нужен?

При рекуррентном вычислении надо быть осторожным. Ошибка копиться будет. Можно обратно в 0 градусов не вернуться.

Более того, не вернется в 0 гарантировано, ошибка будет только расти. Выход один — обнуление при проходе значений 90*n градусов.
Мне было бы интересным реализация вращения видеоизображения без многократных вычислений.
Например коллега Вячеслав подписался, что в ПЛИС реализовал алгоритм VGA
https://habrahabr.ru/post/157863/
А где VGA там и RGB разъёмы. А там и сигналы строчной и кадровой развертки идут по двум отдельным каналам.
Предлагаю поворачивать видеоизображения управлением кадровой и строчной развертки.
Готов участвовать в написании алгоритма (правда в Фортране).
У меня HDMI.
Боюсь оказаться неправым, но что-то мне подсказывает, что это не совсем возможно.
Душа боится, а руки делают!
Вам прислать переходник HDMI-VGA. Может у Вас тамперированный монитор поэтому в нём нет CRT 640*480 и он не позволяет по сигналам строчной и кадровой развертки dx и dy выводить пиксель в заданную точку экрана?
Полагаю, что повесив в подъезде объявление «Дорогие соседи! Кому не жалко, занесите свой монитор VGA на пятый этаж он мне нужен.» решит проблему.

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

Я весь внимания и в готовности к сотрудничеству советами и средствами.
Не, переходника мне не надо. У меня выход HDMI и вход на мониторе HDMI, а VGA ну совсем нет ни где.
Не хотите со стороны монитора делать моментальное преобразование видеоизображение.
Попробуем подойти к проблеме со стороны целесообразности работы.
Предположим, что камера у Вас на подвижном основании снимает улицу и при этом качается.
С камерой качается гироскоп и выдает оперативно информацию о наклоне камеры.
Сигнал поступает к Вам на FPGA.
Камера и гироскоп можно использовать, установленные в смартфоне.

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

Не знаю реализацию, которой воспользовались вы, но оригинальный кордик сразу на выходе дает повернуты вектор, умножения и сложения делать не надо. Вычисление sin и cos производятся поворотом вектора (1.0; 0.0), если вместо него подать на вход кордика (x;y) то на выходе сразу будет повернутый вектор

Не знаю что Вы подразумеваете под оригинальным кордиком, но настоящий кордик поворачивает вектор и далее в зависимости от знаков делает сложение умножение/сдвиг. Сам по себе поворот осуществляется с помощью сложения и умножения
Классический кордик описан, например, здесь

Он преобразует (x0; y0; z0) --> K(x0*cos(z0)-y0*sin(z0); x0*sin(z0)+y0*cos(z0); 0)

Отсюда, если взять x0 = 1/K, y0=0, то получим (1/K; 0; z0) --> (cos(z0); sin(z0); 0)

То есть, вычисление косинусов и синусов — частный случай оригинального кордика.

Я лишь обращал внимание, что вместо преобразований:
1) вычислить cos(z0), вычислить sin(z0)
2) вычислить x0*cos(z0)-y0*sin(z0)
3) вычислить x0*sin(z0)+y0*cos(z0)

можно обойтись одним:
1) вычислить x0*cos(z0)-y0*sin(z0) и x0*sin(z0)+y0*cos(z0)
Далее значение угла подаётся на модуль CORDIC, который и вычисляет нам значения sin и cos.

cordic CORDIC(
    .clk(clk),
    .rst(~nRst),
    .x_i(17'd19896),
    .y_i(16'd0),
    .theta_i(cordic_angle),
    .x_o(COS),
    .y_o(SIN),
    .theta_o(),
    .valid_in(),
    .valid_out()
    );


Далее не сложно догадаться, что расчёт координат каждого последующего пикселя будет производиться по формуле:

x’ = cos(angle) * x — sin(angle) * y;
y’ = sin(angle) * x + cos(angle) * y;


Вместо этого можно было бы написать что-то вроде:
cordic CORDIC(
    .clk(clk),
    .rst(~nRst),
    .x_i(x),
    .y_i(y),
    .theta_i(cordic_angle),
    .x_o(x'),
    .y_o(y'),
    .theta_o(),
    .valid_in(),
    .valid_out()
    );

второй вариант — это использовать честную координату (ну, почти честную) для расчета начала строки, а для следующих — алгоритм брезенхема.
Брезенхем же в чем — есть виртуальная шестеренка с К зубцами (если использовать вашу КПДВ и рисовать строки слева направо от 0,0 до W,0 и снизу вверх), на каждый ее шаг «поворота» увеличиваем координату х на 1, а как сделает полный оборот — увеличиваем координату у на 1, т.д — по крайней мере до угла 45 градусов :)
Если больше 45 — то там по столбцам нужно идти. Но идея от єтого не меняется.

При угле поворота картинки 0 градусов — К равно W (т.е. ни разу у не увеличивается), при 45 — К равно 1 (т.е. у увелчивается так же как и х — а каждом шаге).
Так как строчки идут параллельно, то у всех у них будет одинаковый угол — и параметр К. Отличаться только будет координата начала и фаза виртуальной «шестерни». Так что нужно будет честно вычислить только в начале кадра — куда придет 0,0 и какая в этой точке фаза (интуитивно кажется — что можно задать равной 0 — типа начало, но хз, надо нарисовать или попробовать).
После этого — для каждой следуюущей строки можно вычислять только стартовую х_і координату (а у — получить из брезенхема, так как это y_0 + i — (х_0 — х_і) / К. Заодно и дырок между строчками не будет). Начальная фаза при этом будет равна (х_0 — х_і) mod К.

Далее — можно заметить еще, что если размеры (H, W) исходного и результирующего прямоугольников равны, и мы делаем поворот вокруг цента, то если мы сейчас читаем из точки с координатой (х_і, у_і), и после преобразования, ее координаты (х2_і, у2_і) находятся вне результирующего прямоугольника, то это значит, что в результирующем прямоугольнике есть точка (х_і, H — у_і), у которой нужно выставить цвет фона :)
(В принципе, это соображение будет работать при любом угле поворота… вроде бы ...)

если это все можно выразить на верилоге — было бы интересно посмотреть результат :DDD
хммм… как-то сложно написал… но зато без промежуточного буфера! :)
Не уверен, что у меня получится это реализовать )).
Там есть ещё одна проблема, которая заключается в скорости чтения/записи данных во внешнюю память. Сейчас камера выдаёт 25 fps. С разрешением 320x240 времени хватает на запись данных не бёрстами. При увеличении угла поворота возможность записи бёрстами сходит на нет т.к. адреса пикселей находятся не в одном ряду, а это означает что каждую запись надо делать начиная с открытия ряда, выдержки tRRD, tRP для SDRAM, закрытия ряда. Это очень сильно влияет на производительность. Отсюда, если выбирать данные из SDRAM по столбцам, то на каждый пиксель надо тратить уйму времени, да плюс ещё и входные данные от камеры надо успевать записывать, а HDMI контроллер не ждёт, он работает на 25Мгц. По этому для данной реализации куда дешевле по времени все процедуры преобразования производить на записи, а чтение пусть работает бёрстами по строкам.
хмм, а возвращаясь к идее из первого комментария:
если в памяти буферы расположить не подряд — А0A1...Am, B0B1...Bm, C0C1...Cm, а чередуя, чтобы А0B0C0[ВЫРАВНИВАНИЕ], А1B1C1[ВЫРАВНИВАНИЕ]… АmBmCm[ВЫРАВНИВАНИЕ],
и потом на приходе очередного сигнала вычислять координату чтения, читать данные из промежуточного буфера, а потом делать запись данных из камеры на рабочий и запись прочитанного (или черного/фонового) цвета на выходной буфер делать сразу одним заходом (только надо, чтобы коррекция адресов для буферов А/B/C происходила прямо в проводах)
так можно же?
Не знаю. Надо будет подумать.
Поворот на ПЛИС можно сделать двумя последовательными косыми сдвигами. Отдельно по строкам, отдельно по столбцам. Тогда с памятью идет поточная работа. В результате выходит последовательное применение двух одномерных фильтров. Справедливости ради, стоит сказать, что это не совсем «косой сдвиг», но суть близкая. Пусть сначала идет обработка по строкам (матрица A), а потом по столбцам (матрица B). Матрица поворота (М) раскладывается так (для аффинных преобразований такой прием проходит):
B * A = M
A вида (косой горизонтальный сдвиг)
a1 a2
1 0
B вида (косой вертикальный сдвиг)
1 0
b1 b2
т.е.
x* = a1 x + a2 y
y* = b1 x* + b2 y (стоит обратить внимание, что «x» тут новое)
Осталось найти коэффициенты (a1, a2, b1, b2) этих матриц. Выходит система уравнений. Ответ:
a1 = cos(alpha)
a2 = sin(alpha)
b1 = cos(alpha) / sin(alpha) = ctg(alpha)
b2 = -1 / sin(alpha)
Извините, в матрице A ошибка:
a1 a2
0 1
и ответ:
a1 = cos(alpha)
a2 = sin(alpha)
b1 = -sin(alpha) / cos(alpha) = -tg(alpha)
b2 = 1 / cos(alpha)

Еще такой подход не работает на углах поворота близких к 90 градусам. Можно работать от -45 до 45, а иначе работать с модификацией, которая как бы поворачивающей заранее изображение на 90 градусов (меняет х и у местами).
а на этом железе есть какая-то разница целочисленные вычисления или нет?
Есть. Для простоты можно считать, что числа только целочисленные (с фиксированной точностью).
Sign up to leave a comment.

Articles