Игрушечный видеоизмеритель (2 параметра)

Jul 20, 2023 03:31

В прошлом посте мы рассмотрели вообще одномерную задачу, сейчас чуть двинемся вперёд, перейдём в ДВА измерения. В точке (0;0) у нас расположена "линза" объектива, слева изображена фотоприёмная ЛИНЕЙКА (как у сканера, до матрицы мы в этот раз не доросли, всё происходит исключительно в плоскости), а справа - три наблюдаемых объекта. Исходное положение изображено красным, сдвинутое - синим.

Мы сейчас полагаем, что эти объекты могут двигаться только поступательно, т.е смещаться по осям X и Y как единое целое, без поворотов. Конфигурация объектов известна: они расположены по одной прямой, расстояние между соседними: 1 метр.




Наша задача: исходя из картинки, полученной на фотоприёмной линейке, узнать координаты центрального объекта, (tx; ty), t от слова translation - смещение (параллельный перенос), притом стараясь минимизировать шумы, благо, в задаче присутствует избыточность, точек 3, тогда как в данной ситуации (без поворотов) хватило бы и двух.

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


Чуть позанудствуем. Вдобавок к показанной на рисунке системе координат (приборной) введём систему координат мишени (на которой и расположены эти 3 светящиеся объекта). В ней эти объекты будут иметь фиксированные координаты A1(0;1) (это верхняя точка), A2(0;0) (средняя точка) и A3(0;-1) (нижняя точка).

Расположение мишени задаётся вектором (tx; ty), который мы и хотим найти, и у нас есть неплохое "нулевое приближение" (t0x, t0y), вблизи которого мы и собираемся работать.

Если мишень расположена именно так, то мы ожидаем увидеть точки на фотоприёмной линейке в позициях







Коэффициент k определяется угловым размером пикселя и фокусным расстоянием объектива. Удобнее будет не напрягать наш алгоритм такими подробностями - и поскорее от пикселей перейти к безразмерным величинам, по сути "тангенсам углов". Поэтому коэффициент k мы выкидываем, и обобщаем эти выкладки:



Но в реальности мы все эти точки находим в позициях fn, которые, вообще говоря, отличаются от "предсказанных" gn. Находим НЕВЯЗКИ:



Именно они послужат входными данными для нашего алгоритма. По ним он откорректирует вектор (tx, ty).

Находим частные производные от выражения для gn, чтобы понять, как каждая из точек на фотоприёмной линейке должна "реагировать" на движение мишени по разным осям:





Каждую такую пару частных производных мы назовём матрицей Mn. Здесь эта "матрица" всего 2×1, т.е мы записываем влияние одной-единственной координаты на фотоприёмной линейки от двух параметров. Во "взрослой" задаче это матрица 6×2, т.е записываем, куда поползёт точка на фотоприёмной матрице от изменения одного из ШЕСТИ параметров сближения. Но смысл тот же самый.

Далее, мы находим матрицу J:



И наконец, записываем, чему должны равняться поправки к измеренному ранее вектору (t0x, t0y):



И теперь, наконец, подставим наши "игрушечные" значения, чтобы понять, как оно работает. Для начала, положим t0y=0, то есть в последний раз мишень стояла строго напротив объектива, ровно так, как мы это изобразили на рисунке. И подставим координаты точек мишеней An. Получим:







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

Теперь посчитаем матрицу J:



Здесь нам несказанно повезло: она оказалась диагональной. Обратная матрица - это просто обратные элементы по диагонали:



Рассмотрим два варианта движения. Сначала все невязки будут указывать в одном направлении, т.е h1=h2=h3=ε. Нетрудно увидеть, что оценка по горизонтали будет НУЛЕВОЙ, т.к h2 мы сразу занулим (мы знаем, что эта точка лежит на оптической оси и поэтому картинка никуда не двинется, если её двигать вдоль оси), а h1 и h3 дадут вклад с противоположными знаками. А по вертикали, мы сначала получим 3ε/t0x, а потом домножим на J-1 и получим εt0x.

Результаты абсолютно логичные и ожидаемые: увидев движение в одну сторону, без изменения масштаба, мы поняли, что мишень двигается поперечно. С масштабом тоже всё правильно: мы же постановили, что будем работать с "тангенсами углов", т.е ε=y/x. Вот мы его и домножаем назад на x, чтобы получить поперечное смещение в метрах.

Теперь верхняя невязка будет со знаком минус, центральная нулевой, а нижняя - со знаком плюс, т.е h1=-ε, h2=0, h3=ε. В этот раз занулится оценка по вертикали, т.к в нашем случае коэффициенты совпадают, мы по сути СКЛАДЫВАЕМ ВСЕ НЕВЯЗКИ и получаем ноль. А вот по горизонтали мы получим положительный (в прямом смысле) результат. До "нормировки" (умножения на J-1) это будет 2ε/t0x2, а после нормировки: εt0x2.

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

А теперь утащим мишень вверх от оптической оси! Для определённости, постановим t0y=1:




Теперь мы получим следующие матрицы M:







Пока всё выглядит довольно безобидно, хотя уже можно заметить: если в прошлый раз при смещении по горизонтали точки "двигались в разные стороны", одна вверх, другая вниз, то здесь все производные имеют один и тот же знак, т.е все 3 точки "ползут вниз". Это уже не вполне интуитивно: мы ожидаем, что согласованное движение в одну сторону - это поперечный сдвиг, а "в разные стороны" - это продольный, приводящий к изменению масштаба.

Но не будем раньше времени волноваться, продолжим алгоритм. Посчитаем матрицу J:



По вертикали, как будто, всё без изменений: тот же масштаб. А вот масштаб по горизонтали изменился, что немного странно: мы же "ничего не делали", всего лишь сдвинулись вбок! И главное, появились перекрёстные связи, т.е элементы вне главной диагонали!

Обратим матрицу:



Всё чудесатее и чудесатее: теперь у нас и масштаб по вертикали исказился, что кажется совсем криминалом...

Что же, испытаем работу алгоритма, когда все невязки будут указывать в одном направлении, т.е h1=h2=h3=ε.
Сначала найдём "ненормированную оценку", т.е без домножения на J-1:



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

А вот когда мы помножим это дело на J-1, все проблемы "как по волшебству" уйдут:



То есть, алгоритм работает корректно!

И посмотрев ещё разок на вторую картинку, можно понять смысл происходящего: когда мы отодвигаем объект всё дальше и дальше, сохраняя его поперечное положение, он норовит уйти на линию горизонта! Т.е стянуться к оптической оси. И когда объект не очень большой (иными словами, он отодвинут по вертикали более чем на свой размер!), данный эффект оказывается доминирующим. Именно поэтому у нас производные перестали менять знак.

Если отодвинуть объект ещё дальше, не на единичку (как сейчас), а сразу на 10 (т.е на удесятерённый свой размер), уже начинается театр абсурда. Для оценки сдвига "по вертикали" и "по горизонтали" мы будем прибегать к очень похожим вычислениям: в первом случае невязки берём с равными весами, а во втором, условно, домножаем одну на 10, а другую на 9.

И только затем, на этапе умножения на J, мы ВЫЧТЕМ ДВА БЛИЗКИХ ЗНАЧЕНИЯ, и, наконец, разделим горизонтальную и вертикальную компоненту.

Компоненты перемешались, и масштаб коэффициентов вынужденно оказался близким! Если все вычисления точны, то никаких проблем - в итоге все перекрёстные связи будут устранены, что мы и увидели выше. Но в реальном мире мы получаем очень опасную ситуацию. Тут даже плавающая точка не панацея - все мы знаем, как замечательно она вычитает два близких значения!

Попробуем применить "геометрическую интуицию": направим ось X по линии визирования, чтобы снова вернуть чёткое разделение на продольное (меняющее масштаб) и поперечное движение!




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

Да и вообще, менять ось Y (а впоследствии Z) совсем не хотелось: по ней выкладки такие простые и понятные, просто реакция на смещение всех точек ОДИНАКОВАЯ (покуда мишень "плоская", потом реакция самую чуточку меняется, но здесь тоже никаких неожиданностей - чем ближе к объективу - тем сильнее реакция). Поэтому в кои-то веки решено было ввести "кривую" систему координат, не ортогональную и не нормированную!

Собственно, это и не система координат, как таковая, это просто два вектора, "реакцию" системы на которые мы проверяем. Раньше мы брали векторы (1;0) и (0;1). А теперь возьмём (1; t0y/t0x) и (0;1), вот и вся премудрость!

Зная реакцию на обычные, базисные векторы, можно посчитать реакцию и на этот повёрнутый вектор:



В примитивном частном случае, когда anx=0 (т.е мишень плоская), выражение совсем упростится:



Это ровно то, что мы и хотели!!! Когда мы работали на больших дальностях (например, 300 метров), и мишень оказывалась на краю поля зрения, это соответствовало поперечному смещению в десятки метров, поэтому изначально попадающее в числитель значение t0y ЗАБИВАЛО СОБОЙ это выражение, превращая измерение дальности в ещё одно измерение поперечного смещения (мы начинали смотреть больше не на масштаб объекта, а на его положение на фотоприёмной матрице, что с точки зрения математики было совершенно естественно, РЕАКЦИЯ Ж СИЛЬНЕЕ, ВОТ ЕЁ И НАДО ИСПОЛЬЗОВАТЬ), а потом два почти одинаковых измерения вычитались друг из друга.

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

Остаётся лишь дополнительный шаг: посчитав Δt'x и Δt'y (для "сдвинутых" осей), надо их пересчитать в обычные.

Выкладки на удивление просты:



(да, поскольку сдвиг по оси X содержится только в этом векторе, X', и совсем не содержится в Y', значит, всё что мы тут измерили - всё наше!)



То есть, если объект, сидящий не на оси, остаётся на прежнем месте, но при этом уменьшается в размерах, значит, на самом деле он "движется вверх", вдоль линии визирования. Почувствовав изменение размеров и сделав вывод об отдалении, мы теперь ещё и добавляем поперечное движение, которое "напрямую" (по движению картинки по экрану) почувствовать не получается.

Переход от плоскости к пространству не сильно сложнее: ось X' по-прежнему мы направим вдоль линии визирования, что даст одну добавку к mn14 (реакция по горизонтали на изменение дальности) и другую добавку к mn24 (реакция по вертикали на изменение дальности).

Пожалуй, главная подлянка в том, что мишень "трёхмерная", часть её элементов выступает вперёд, из-за чего так просто выражения не уничтожатся (линия визирования же одна, нельзя через каждую точку свою собственную ось провести!), хотя ослабятся ОЧЕНЬ ПРИЛИЧНО.

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

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

Previous post Next post
Up