Частичные ошибки при вычислении аффинного преобразования с помощью OpenCV

В последнее время я много работал с OpenPnP (и это здорово!), и мой интерес достиг достаточно пика, чтобы попытаться по-настоящему понять, как это работает.

Я пытаюсь выполнить базовое преобразование печатной платы с тремя контрольными точками и тремя функциями на С# с помощью OpenCVSharp.

Я смоделировал геометрию в каком-то программном обеспечении САПР, чтобы еще раз проверить работоспособность, вот моя «плата».

F1-3 — это 3 контрольные точки, причем F1 соответствует 0,0 системы координат печатной платы. P1-3 — это три «функции» на печатной плате, которые меня интересуют.

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

Я думаю, мне нужно выполнить «аффинное преобразование», и за несколько часов я собрал это воедино.

using OpenCvSharp;

namespace affinetransform
{
    class Program
    {
        static void Main(string[] args)
        {
            // Define fiducials in PCB coordinates
            Point2f[] pcbFiducials = new Point2f[]
            {
                new Point2f(0, 0),
                new Point2f(100, 0),
                new Point2f(100, -80)
            };

            // Corresponding fiducial points as measured in machine coordinates
            Point2f[] cameraFiducials = new Point2f[]
            {
                new Point2f(190.62f, -83.7f),
                new Point2f(290.24f, -74.99f),
                new Point2f(297.21f, -154.68f)
            };

            // Compute the affine transformation matrix
            Mat affineTransform = Cv2.GetAffineTransform(InputArray.Create(pcbFiducials), InputArray.Create(cameraFiducials));

            // Define the 3 'features' on the PCB we're interested in, in PCB coordinates
            Point2f[] pcbFeatures = new Point2f[]
            {
                new Point2f(24, -18),
                new Point2f(35, -62),
                new Point2f(74, -28)
            };

            // Convert feature points to a Mat object, because we need to pass Mat type to the Transform method
            Mat pcbFeaturesMat = new Mat(pcbFeatures.Length, // Rows
                                            1,                  // Columns
                                            MatType.CV_32FC2,
                                            pcbFeatures);       // The features on the PCB we want to transform
            for (int i = 0; i < pcbFeaturesMat.Rows; i++)
            {
                for (int j = 0; j < pcbFeaturesMat.Cols; j++)
                {
                    Console.Write($"{pcbFeaturesMat.At<float>(i, j)}\t");
                }
                Console.WriteLine();
            }


            // Transform feature points to machine/camera coordinates
            Mat cameraFeaturesMat = new Mat();
            Cv2.Transform(pcbFeaturesMat, cameraFeaturesMat, affineTransform);

            // Save the transformed points to a CSV file
            SavePointsToCsv(cameraFeaturesMat, @"C:\users\user\desktop\cameraFeatures.csv");
        }

        static void SavePointsToCsv(Mat points, string filename)
        {
            using (var writer = new System.IO.StreamWriter(filename))
            {
                writer.WriteLine("X,Y");
                for (int i = 0; i < points.Rows; i++)
                {
                    float x = points.At<float>(i, 0);
                    float y = points.At<float>(i, 1);
                    writer.WriteLine($"{x},{y}");
                }
            }
            Console.WriteLine($"Saved transformed points to {filename}");
        }
    }
}

И это преобразованные данные, которые он выводит, что не совсем неправильно. Как вы можете видеть из моих изображений, 216,09, 230,89 и 266,78 — это 3 правильных преобразованных значения X в этой таблице, но в неправильных позициях, а остальные 3 точки данных — это просто позиция X машины F1, повторенная дважды, и огромное количество.

X,Y
216.09705,230.88875
230.88875,266.7783
266.7783,1.11E-43

Сама матрица аффинного преобразования, вычисленная этим кодом, имеет следующий вид:

Transformation Matrix:
0.99619995117187, -0.08712501525878906, 190.6199951171875
0.08709999084472657, 0.9961249351501464, -83.69999694824219

Совершенно озадачен и надеюсь, что какой-нибудь умный народ из OpenCV укажет мне правильное направление!

Маты имеют строки, столбцы и глубину/каналы. вам нужна матрица «один столбец» с 32FC2 (2-канальными) данными. попробуйте. ваша матрица преобразования, вероятно, в порядке, но pcbFeaturesMat, вероятно, нет. Я удивлен, какую бы оболочку вы ни использовали, она не жаловалась на несоответствие между заданным Point2f[] и аргументами конструктора - на самом деле я не мог протестировать ваш код. Я не настроен на C#. дайте мне знать, если это проблема. тогда я сделаю из этого ответ.

Christoph Rackwitz 23.05.2024 07:14

матрица выглядит примерно так? [[0.9962, -0.08713, 190.62], [0.0871, 0.99612, -83.7]] Я получаю это для преобразования(): [[ 216.09705, -99.53985], [ 230.88875, -142.41124], [ 266.7783 , -105.1461 ]] (проверено на Python, проблем с преобразованием данных нет, поскольку этого не происходит с Python/numpy/OpenCV)

Christoph Rackwitz 23.05.2024 07:19

Я бы рекомендовал взять систему PNP в качестве мировой структуры, затем найти контрольные точки на печатной плате и вычислить преобразование между кадром PNP и кадром печатной платы.

Christoph Rackwitz 23.05.2024 07:32

Спасибо за ваши комментарии @ChristophRackwitz. В моем примере кода pcbFeaturesMat уже имеет тип 32FC2, если я чего-то не понимаю.

mountainred 23.05.2024 12:22

Скоро мы получим значения матрицы, AFK прямо сейчас, но ваши рассчитанные преобразованные значения действительно верны как по X, так и по Y! Обратитесь к вашей рекомендации по созданию мировоззрения, я думаю, может быть, я не понимаю терминологию - разве это не то, что я сделал? Моя зеленая точка представляет собой 0,0 машины, и я «зафиксировал» контрольные значения относительно этой точки.

mountainred 23.05.2024 12:24

Я добавил в свой вопрос матричные значения, которые такие же, как ваши (только с большим количеством десятичных знаков). Так это что-то!

mountainred 23.05.2024 12:42

Я также понял, что по преобразованным данным XY я каким-то образом скопировал неправильно, и одно из значений не соответствует тому, что генерирует этот код, поэтому я это исправил.

mountainred 23.05.2024 12:47

вы поняли мою точку зрения насчет одной колонки?

Christoph Rackwitz 23.05.2024 13:07

Нет, я так не думаю. Если в конструкторе pcbFeaturesMat я изменю 2 на 1, результирующее преобразование все равно будет неправильным, так что, думаю, это говорит мне о том, что я все еще в тупике! Очень признателен за любую помощь, которую вы можете здесь предоставить.

mountainred 23.05.2024 13:36

В этом есть смысл, поскольку это значение меняется каждый раз, когда я запускаю программу, поэтому я согласен — определенно не инициализировано. Обратитесь к вашему комментарию «один столбец», означает ли это, что Mat pcbFeaturesMat = new Mat() следует передать 1 вместо 2 для аргумента столбцов?

mountainred 23.05.2024 14:19

это все еще может быть неправильным, но уже менее ошибочным. это одно число E+21 — неинициализированная память. Я знаком с API-интерфейсами OpenCV C++ и Python, но не с этой сторонней оболочкой C#. возможно, проверьте pcbFeaturesMat изолированно. Я считаю, что это наиболее вероятный кандидат на проблему. проверьте его результирующую форму, количество каналов, тип элемента и его значения. если значения не совпадают с тем, что вы передали конструктору... -- Я не могу предсказать, что конструктор Mat сделает с массивом Point2f. это могут быть ссылки или шесть последовательных чисел с плавающей запятой, упакованных в память.

Christoph Rackwitz 23.05.2024 14:20

pcbFeaturesMat имеет тип 3x2xCV_32FC2, и его данные приведены ниже, что кажется неправильным. 24, 35 и 74 должны находиться в столбце X. [24, 35, ][74, 0][3.5096498E-20, 0]

mountainred 23.05.2024 14:36

да, и я сказал, что это неправильно. это должен быть один столбец.

Christoph Rackwitz 23.05.2024 14:43

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

mountainred 23.05.2024 17:14

Вы упомянули, что настроили это на Python, у меня здесь тоже есть настройка среды Python 3, есть ли шанс, что вы поделитесь своим кодом, и я смогу разобраться с этим, чтобы увидеть, есть ли какие-либо различия?

mountainred 23.05.2024 17:25

со стороны Python это всего лишь пара np.float32([ [a,b], [c,d], [e,f] ]).reshape((3, 1, 2)). возможно, вам придется подождать кого-то, кто знает особенности оболочки C# и особенности C#

Christoph Rackwitz 23.05.2024 17:49

Я только что заметил, что хотя матрица affineTransform верна, ее тип 2*3*CV_64FC1, который не соответствует типам CV_32FC2 для реперов и точек, это проблема?

mountainred 23.05.2024 21:05

это не проблема как таковая, если только не задействованы преобразования типов на уровне C#/обертки, которые кажутся склонными к сбою, если пользователь не совсем уверен в этом. сам C++ API, в частности метод at<T>(), всегда требует, чтобы программист точно знал или вручную проверял, что матрица, к которой осуществляется доступ, имеет правильный тип. Я нахожу это утомительным и конструктивным недостатком как языка C++, так и C++ API OpenCV, поэтому я придерживаюсь Python.

Christoph Rackwitz 24.05.2024 09:10
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
18
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Хорошо понял. Похоже, преобразование все время было рассчитано правильно, но я неправильно его печатал.

После вычисления преобразования я конвертирую результат обратно в Point2f[], с которым гораздо проще работать, а затем распечатываю его.

using OpenCvSharp;
using System;

namespace AffineTransform
{
    class Program
    {
        static void Main(string[] args)
        {
            // Define the locations of the fiducials on the PCB in PCB coordinates
            Point2f[] pcbFiducials = new Point2f[]
            {
                new Point2f(0, 0),
                new Point2f(100, 0),
                new Point2f(100, -80)
            };

            // Define the same fiducial points as measured in machine coordinates, i.e. the position the PCB sits in the machine
            Point2f[] cameraFiducials = new Point2f[]
            {
                new Point2f(190.62f, -83.7f),
                new Point2f(290.24f, -74.99f),
                new Point2f(297.21f, -154.68f)
            };

            // Compute the affine transformation matrix
            Mat affineTransform = Cv2.GetAffineTransform(pcbFiducials, cameraFiducials);

            // Print the affine transformation matrix to check it's correctness
            Console.WriteLine("Affine Transformation Matrix:");
            for (int i = 0; i < affineTransform.Rows; i++)
            {
                for (int j = 0; j < affineTransform.Cols; j++)
                {
                    Console.Write($"{affineTransform.At<double>(i, j)}\t");
                }
                Console.WriteLine();
            }

            // Define the 3 'features' on the PCB we're interested in transforming to machine coordinates, in PCB coordinates
            Point2f[] pcbFeatures = new Point2f[]
            {
                new Point2f(24, -18),
                new Point2f(35, -62),
                new Point2f(74, -28)
            };

            // Convert feature points to a Mat object, as Mat is needed to pass to Transform() later
            Mat pcbFeaturesMat = new Mat(pcbFeatures.Length, 1, MatType.CV_32FC2, pcbFeatures);

            // Transform feature points to machine/camera coordinates, same reason as above
            Mat cameraFeaturesMat = new Mat();

            // Compute the actual transform
            Cv2.Transform(pcbFeaturesMat, cameraFeaturesMat, affineTransform);

            // Convert the result back to Point2f[], to make them much easier to actually use
            Point2f[] cameraFeatures = new Point2f[cameraFeaturesMat.Rows];
            for (int i = 0; i < cameraFeaturesMat.Rows; i++)
            {
                cameraFeatures[i] = new Point2f(cameraFeaturesMat.At<Point2f>(i).X, cameraFeaturesMat.At<Point2f>(i).Y);
            }

            // Print the transformed points
            Console.WriteLine("Transformed Points:");
            foreach (var point in cameraFeatures)
            {
                Console.WriteLine($"X = {point.X}, Y = {point.Y}");
            }

            // Save the transformed points to a CSV file
            SavePointsToCsv(cameraFeatures, @"C:\users\user\desktop\cameraFeatures.csv");
        }

        static void SavePointsToCsv(Point2f[] points, string filename)
        {
            using (var writer = new System.IO.StreamWriter(filename))
            {
                writer.WriteLine("X,Y");
                foreach (var point in points)
                {
                    writer.WriteLine($"{point.X},{point.Y}");
                }
            }
            Console.WriteLine($"Saved transformed points to {filename}");
        }
    }
}

Другие вопросы по теме