Как я могу оценить позу камеры с 3D-2D-точечными соответствиями (используя opencv)
Здравствуйте, моя цель состоит в том, чтобы разработать функцию отслеживания головы, которая будет использоваться в кабине самолета (симулятора), чтобы обеспечить AR для поддержки гражданских пилотов при посадке и полете в плохих визуальных условиях.
Мой подход заключается в том, чтобы обнаружить характерные точки (в темных светодиодах симулятора), для которых мне известны трехмерные координаты, а затем вычислить приблизительную (R = t) позу (с надетой головой) (вращение, соединенное с переводом).
Проблема, которая у меня есть, заключается в том, что предполагаемая поза кажется неправильной, и проекция моих трехмерных точек (которую я также использовал для оценки позы) не перекрывается точками 2D-изображения (или не видна).
Мои вопросы:
Как я могу оценить позу камеры с заданным набором точечных соответствий 2D-3D.
Почему это не работает, как я это пробую и где могут быть источники ошибок?
Насколько точными должны быть измерения (3D и 2D точек и матрицы камеры), чтобы теоретическое решение работало в реальных условиях?
Будет ли подход работать для копланарных точек (ось x,y изменилась) в теории?
Аппаратное обеспечение, которое я использую, - Epson BT-200.
В самолете я определил фиксированную ординату, к которой я рассчитываю относительные переводы и повороты как результат моей программы. Программа определяет координаты изображения (уникальных) светодиодов и сопоставляет их с соответствующей трехмерной координатой. С помощью матрицы камеры, которую я получил с помощью образца Android-кода open-cv ( https://github.com/Itseez/opencv/tree/master/samples/android/camera-calibration), я пытаюсь оценить позу с помощью solvePnP.
Матрица моей камеры и искажения меняются незначительно. Вот некоторые значения, которые я получил от процедуры. Я удостоверился, что круговое расстояние моего распечатанного шаблона круга совпадает с записанным в исходном коде (измеряется в метрах).
Вот несколько примеров и как я создаю его OpenCV Mat.
// protected final double[] DISTORTION_MATRIX_VALUES = new double[]{
// /*This matrix should have 5 values*/
// 0.04569467373955304,
// 0.1402980385369059,
// 0,
// 0,
// -0.2982135315849994
// };
// protected final double[] DISTORTION_MATRIX_VALUES = new double[]{
// /*This matrix should have 5 values*/
// 0.08245931646421553,
// -0.9893762277047577,
// 0,
// 0,
// 3.23553287438898
// };
// protected final double[] DISTORTION_MATRIX_VALUES = new double[]{
// /*This matrix should have 5 values*/
// 0.07444480392067945,
// -0.7817175834131075,
// 0,
// 0,
// 2.65433773093283
// };
protected final double[] DISTORTION_MATRIX_VALUES = new double[]{
/*This matrix should have 5 values*/
0.08909941096327206,
-0.9537960457721699,
0,
0,
3.449728790843752
};
protected final double[][] CAMERA_MATRIX_VALUES = new double[][]{
/*This matrix should have 3x3 values*/
// {748.6595405553738, 0, 319.5},
// {0, 748.6595405553738, 239.5},
// {0, 0, 1}
// {698.1744297982436, 0, 320},
// {0, 698.1744297982436, 240},
// {0, 0, 1}
// {707.1226937511951, 0, 319.5},
// {0, 707.1226937511951, 239.5},
// {0, 0, 1}
{702.1458656346429, 0, 319.5},
{0, 702.1458656346429, 239.5},
{0, 0, 1}
};
private void initDestortionMatrix(){
distortionMatrix = new MatOfDouble();
distortionMatrix.fromArray(DISTORTION_MATRIX_VALUES);
}
private void initCameraMatrix(){
cameraMatrix = new Mat(new Size(3,3), CvType.CV_64F);
for(int i=0;i<CAMERA_MATRIX_VALUES.length; i++){
cameraMatrix.put(i, 0, CAMERA_MATRIX_VALUES[i]);
}
}
Чтобы оценить позу камеры, я использую solvePnP (и solvePnPRansac), как описано в нескольких местах ( 1, 2, 3, 4). Результат convertPnP я использую в качестве входных данных для проекции ( Calib3d.projectPoints). Обратный результат конкатинированного результата [R|t] я использую в качестве оценочной позы.
Поскольку мои результаты в продуктивной среде были слишком плохими, я создал среду тестирования. В этой среде я помещаю камеру (которая из-за ее 3D-формы (это стекло) немного повернута вниз по краю стола. Этот край я использую как ординату мировой системы координат. Я искал, как open-cv Система координат может быть ориентирована и найти разные ответы (один на стеке потока и один на официальном YouTube-разговоре об opencv). В любом случае я проверил, правильно ли я получил систему координат по проекциям трехмерных точек (описанных в этой системе координат) на изображение и проверяется, остается ли данная форма мира постоянной.
Итак, я подошел с z, указывающим вперед, y вниз и x вправо.
Чтобы приблизиться к своему решению, я оценил позу в моей среде тестирования. Вывод вектора трансляции и вывод ангела Эйлера относится к обратному [R|t]. Ангелы Эйлера могут отображаться неправильно (они могут быть поменяны местами или неверно, если мы примем во внимание порядок), потому что я вычисляю их с помощью конвективных (я полагаю, ссылаясь на систему координат самолета), используя систему координат open-cv. (Вычисление происходит в классе Pose, который я прикреплю). Но в любом случае даже вектор перевода (обратного) представляется неверным (в моем простом тесте).
В одном тесте с этим изображением у меня был крен (который мог бы быть шагом в координатах самолета) 30° и перемещение вверх на 50 см. Это кажется более разумным. Поэтому я предположил, что, поскольку мои очки копланарны, я могу получить неоднозначные результаты. Таким образом, я реализовал другой тест с точкой, которая изменилась в оси Z. Но с этим тестом даже проекция не удалась.
Для solvePnP я попробовал все разные флаги алгоритма решения и разные параметры для алгоритма Ransac.
Может быть, вы можете как-то помочь мне найти мою ошибку или показать мне хороший путь для решения моей первоначальной проблемы. Я собираюсь приложить также мой исходный код для отладки со многими операторами println и изображениями отладки. Этот код содержит мои точечные измерения.
Заранее благодарны за Вашу помощь.
Класс Main.java: Класс Pose.java: 0.png
1.png
РЕДАКТИРОВАТЬ 22.03.2015: Наконец-то я смог найти ошибки, которые я сделал.
- Я изменил объект Mat в цикле for, потому что OpenCV много работает с вызовом по ссылке, и здесь я не был достаточно осторожен. Так что tvec и rvec для перепроецирования были не правы.
- Одна из моих точек в среде тестирования (в координатах изображения) была помечена неправильно из-за путаницы в направлении оси.
Так что мой подход в целом был правильным. Я не получаю хотя бы (часто) действительных репроекций в моем наборе тестовых данных.
К сожалению, алгоритмы OpenCV PnP: "ITERATIVE, P3P, EPNP" возвращают различные результаты, и даже при использовании очень неточной, но близкой внутренней догадки результаты только иногда верны. Предполагается, что алгоритм P3P обеспечивает 3 решения, но OpenCV предоставляет только одно. Предполагается, что EPNP даст хорошие результаты, но с EPNP OpenCV возвращает худшие результаты, полученные из моего человеческого наблюдения.
Теперь проблема в том, как отфильтровать неточные значения или убедиться, что функция OpenCV возвращает действительные. (Может быть, я должен изменить нативный код, чтобы получить 3 решения для PnP).
Сжатые изображения здесь (37 МБ) действительно показывают мои текущие результаты (с ITERATIVE PnP-Solver) с внутренним предположением о нулевом вращении и 75 см вверх. Распечатка имеет ось x вперед, ось y влево и z, а также соответствующие углы крена, тангажа и рыскания.
1 ответ
Одна вещь, которую я узнал, пытаясь внедрить мою систему слежения за головой, состоит в том, что вы должны начать с простой задачи, а затем перейти к более сложной. Ваш вопрос довольно актуален, и, к сожалению, у меня нет времени анализировать его и искать ошибку или логическую ошибку в вашем коде, поэтому, по крайней мере, я постараюсь дать вам несколько советов и рабочих примеров.
Вот учебник OpenCV для поиска перемещения и поворота объекта. Это написано на Python, если это проблема здесь, часть моего старого проекта C++.
Мой проект выполняет ту же задачу, используя функцию solvePnP или solvePnPRansac (вы можете изменить режим). Обратите внимание, что мой код является частью какого-то старого проекта "игровой площадки", поэтому даже после очистки, которую я выполнил, он довольно грязный. Когда вы запустите его, покажите печатную шахматную доску камере, нажмите "p", чтобы начать оценку положения и вращения, "m", чтобы изменить режим (0-ransac, 1-pnp, 2-posit, который, кажется, не работает...) или 'd' для включения / выключения с использованием коэффициентов искажения.
Оба проекта основаны на поиске шахматной доски, но их будет легко модифицировать для использования других объектов.
Калибровка камеры - пока я работал над своей системой отслеживания головы, мне никогда не удавалось откалибровать камеру дважды с одинаковыми результатами... Поэтому я решил использовать какой-то файл калибровки, который я нашел на github, и он работал хорошо - Здесь вы можете найти более подробную информацию о том, что ссылка на этот файл.
редактировать:
Попробуйте начать с как можно более простого решения, которое дает хорошие результаты в некоторых (даже простых) ситуациях. На мой взгляд, неплохо бы начать с замены листа бумаги из вашей тестовой среды печатной шахматной доской из учебника ( этой) и заставить ее работать. Переход от этого к вашей проблеме будет намного проще, чем начинать с вашей проблемы. Попробуйте создать любое рабочее решение на любом языке программирования - подумайте об использовании версии OpenCV на Python или C++ - там гораздо больше уроков / примеров, чем для версии Java, и сравнение результатов из вашего кода с результатами из некоторого рабочего кода сделает это намного проще. Когда у вас будет какое-то рабочее решение, попробуйте изменить его для работы с вашей средой тестирования. Есть много вещей, которые могут привести к тому, что он сейчас не работает - недостаточно очков, ошибки в вашем коде или даже в Java-оболочке OpenCV, плохая интерпретация результатов и т. Д...
edit2:
Используя очки из вашего кода, мне удалось получить следующие результаты:
rvec = [[-158.56293283], [1.46777938], [-17.32569125]]
tvec = [[-36.23910413], [-82.83704819], [266.03157578]]
К сожалению, для меня трудно сказать, хорошие результаты или нет... Единственное, что может быть неправильным для меня, это то, что 2 угла отличаются от 0 (или 180). Но если вы измените последний ряд points2d
от (355,37), (353,72), (353,101)
в
(355,37), (355, 72), (355, 101)
(я думаю, это ваша ошибка, а не правильный результат) вы получите:
rvec = [[-159.34101842], [1.04951033], [-11.43731376]]
tvec = [[-25,74308282], [ -82,58461674], [ 268.12321097]]
что может быть намного ближе к правильному результату. Изменение матрицы камеры приводит к значительным изменениям результатов, поэтому рассмотрим тестирование значений из этого поста.
Обратите внимание, что все значения rvec умножаются на 180.0/3.14
- в C++ и в Python вектор rvec, возвращаемый solvePnPRansac, содержит углы в радианах.