В последнее время я много работал с 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 укажет мне правильное направление!
матрица выглядит примерно так? [[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)
Я бы рекомендовал взять систему PNP в качестве мировой структуры, затем найти контрольные точки на печатной плате и вычислить преобразование между кадром PNP и кадром печатной платы.
Спасибо за ваши комментарии @ChristophRackwitz. В моем примере кода pcbFeaturesMat уже имеет тип 32FC2, если я чего-то не понимаю.
Скоро мы получим значения матрицы, AFK прямо сейчас, но ваши рассчитанные преобразованные значения действительно верны как по X, так и по Y! Обратитесь к вашей рекомендации по созданию мировоззрения, я думаю, может быть, я не понимаю терминологию - разве это не то, что я сделал? Моя зеленая точка представляет собой 0,0 машины, и я «зафиксировал» контрольные значения относительно этой точки.
Я добавил в свой вопрос матричные значения, которые такие же, как ваши (только с большим количеством десятичных знаков). Так это что-то!
Я также понял, что по преобразованным данным XY я каким-то образом скопировал неправильно, и одно из значений не соответствует тому, что генерирует этот код, поэтому я это исправил.
вы поняли мою точку зрения насчет одной колонки?
Нет, я так не думаю. Если в конструкторе pcbFeaturesMat я изменю 2 на 1, результирующее преобразование все равно будет неправильным, так что, думаю, это говорит мне о том, что я все еще в тупике! Очень признателен за любую помощь, которую вы можете здесь предоставить.
В этом есть смысл, поскольку это значение меняется каждый раз, когда я запускаю программу, поэтому я согласен — определенно не инициализировано. Обратитесь к вашему комментарию «один столбец», означает ли это, что Mat pcbFeaturesMat = new Mat() следует передать 1 вместо 2 для аргумента столбцов?
это все еще может быть неправильным, но уже менее ошибочным. это одно число E+21 — неинициализированная память. Я знаком с API-интерфейсами OpenCV C++ и Python, но не с этой сторонней оболочкой C#. возможно, проверьте pcbFeaturesMat изолированно. Я считаю, что это наиболее вероятный кандидат на проблему. проверьте его результирующую форму, количество каналов, тип элемента и его значения. если значения не совпадают с тем, что вы передали конструктору... -- Я не могу предсказать, что конструктор Mat сделает с массивом Point2f. это могут быть ссылки или шесть последовательных чисел с плавающей запятой, упакованных в память.
pcbFeaturesMat имеет тип 3x2xCV_32FC2, и его данные приведены ниже, что кажется неправильным. 24, 35 и 74 должны находиться в столбце X. [24, 35, ][74, 0][3.5096498E-20, 0]
да, и я сказал, что это неправильно. это должен быть один столбец.
Итак, я обновил вопрос, изменив конструктор для ширины одного столбца, по-прежнему используя типы данных CV_32FC2, и выходные данные преобразования. Аффинная матрица остается неизменной, но преобразование по-прежнему неверно (однако все еще содержит 3 правильных значения). Итак, я все еще не понимаю, что здесь не так, должно быть как-то связано с типами данных, ожидаемыми оболочкой.
Вы упомянули, что настроили это на Python, у меня здесь тоже есть настройка среды Python 3, есть ли шанс, что вы поделитесь своим кодом, и я смогу разобраться с этим, чтобы увидеть, есть ли какие-либо различия?
со стороны Python это всего лишь пара np.float32([ [a,b], [c,d], [e,f] ]).reshape((3, 1, 2)). возможно, вам придется подождать кого-то, кто знает особенности оболочки C# и особенности C#
Я только что заметил, что хотя матрица affineTransform верна, ее тип 2*3*CV_64FC1, который не соответствует типам CV_32FC2 для реперов и точек, это проблема?
это не проблема как таковая, если только не задействованы преобразования типов на уровне C#/обертки, которые кажутся склонными к сбою, если пользователь не совсем уверен в этом. сам C++ API, в частности метод at<T>(), всегда требует, чтобы программист точно знал или вручную проверял, что матрица, к которой осуществляется доступ, имеет правильный тип. Я нахожу это утомительным и конструктивным недостатком как языка C++, так и C++ API OpenCV, поэтому я придерживаюсь Python.





Хорошо понял. Похоже, преобразование все время было рассчитано правильно, но я неправильно его печатал.
После вычисления преобразования я конвертирую результат обратно в 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}");
}
}
}
Маты имеют строки, столбцы и глубину/каналы. вам нужна матрица «один столбец» с
32FC2(2-канальными) данными. попробуйте. ваша матрица преобразования, вероятно, в порядке, ноpcbFeaturesMat, вероятно, нет. Я удивлен, какую бы оболочку вы ни использовали, она не жаловалась на несоответствие между заданным Point2f[] и аргументами конструктора - на самом деле я не мог протестировать ваш код. Я не настроен на C#. дайте мне знать, если это проблема. тогда я сделаю из этого ответ.