# Rotation of a QVector of QPointF or QPolygonF from two points instead of Angle

• Hi,
I have a problem to which i cannot find a solution, i have a set of multiple QPointF stored in a vector (and in a QPolygonF for convenience), i also have 2 registration points, all relative to the same origin.
In this application the two registration points are checked with machine vision to determine their offset from expected position, the measured positions and resulting offsets are stored in 2 QPointF.

Basically the surface which has the registration points is never perfectly aligned so it can have X and Y offset, on both registration points (in the order of 1/100 mm)

The goal is to adjust the set of points in the vector to match the actual position of the surface, so bascially a translation and/or a rotation.

However I could not achieve this with Tranform::rotation because the rotation is then relative to an origin which is different than the physical surface rotation origin, and i have no idea how to determine this actual origin (or if it's even possible) nor how to adjust the Tranfrom::rotation function to use this origin.

I have made a function to determine an angle from the offset between theorical position and measured potition of 1 registration point. I then used this angle with Ttransform::rotate but the result doesnt match, simply because the rotation origin or Transfrom::rotate and physical rotation origin are different.

Does anyone have an idea on how i could solve that?

Below is the attempt with a set of 4 points, but it is wrong because the angle is determined using only one registration point, and because of the rotation origin is certainly wrong.

``````double Panel::angleBwPoints(const QPointF V1, const QPointF V2){

double length1 = sqrt(V1.x() * V1.x() + V1.y() * V1.y()); // modulus of Vector V1 i.e. |V1|
double length2 = sqrt(V2.x() * V2.x() + V2.y() * V2.y()); // modulus of Vector V2 i.e. |V2|
double dot = V1.x() * V2.x() + V1.y() * V2.y(); // dot product between two vectors.
double a = dot / (length1 * length2);
if (a >= 1.0) return 0.0;
else if (a <= -1.0) return M_PI;// π
//else return acos(a) * (180.0/M_PI); // rad to degree
else return qRadiansToDegrees(acos(a));
}
``````
``````    QPolygonF pts;
pts << QPointF(2.1805,2.4565)
<< QPointF(6.4641,8.8659)
<< QPointF(4.0768,23.2113)
<< QPointF(12.0465,6.4675);

double ang = angleBwPoints(QPointF(56.2,18.4), QPointF(56.2,18.4)+QPointF(0.0051,0.068));
qInfo( "origin point 1 error X:%.04f Y:%.04f",0.0051,0.068);
qInfo( "Angle error %.06f°",ang);
qInfo( "Original coordinate:");
for(int i=0;i<pts.length();i++){
qInfo( "%.04f %.04f",pts.toList().at(i).x(),pts.toList().at(i).y());
}
pts = QTransform().translate(0.0051,0.068)
.rotate(ang)
.map(pts);
qInfo( "Affined coordinates:");
for(int i=0;i<pts.length();i++){
qInfo( "%.04f %.04f",pts.toList().at(i).x(),pts.toList().at(i).y());
}
``````

Here is a sketch to understand the problem more clearly, on top is what i have in the file, some point (green) and two registration points (red), their position is related to the bottom left corner which is the origin 0.0 , 0.0.

On bottom is the actual surface on the machine bed, of course i increased the error a lot to make it more clear, in reality the angle and offsets are few decimals after 0.

What the machine vision does is to check the two registration points (red), What i want to achieve is to affine/transform coordinates in my vector to match the bottom figure. • Try this: You have the true registration marks, so select one to be the (0.0) value and subtract its value from the other points, i.e., perform a translation. Now you have position vectors, which is what you need to use the transformation matrix. Find the angle between the two real registration marks and rotate the points. Find the distance between the true registration marks and then find the scale value to position the second marks. The final step is to translate the points back by the value of the true registration mark that was selected as the (0,0).

• OH, thanks, that looks very promising, i will now try this method and report results. I thought i would have to use 3*3 matrix but its unlcear how to set these up, if i can do it with translate and rotate its still better.

• One thing to remember is that the transformation matrix was created from an affine matrix that is use to rotate and scale 2x2 matrix which assumes that it is transforming position vectors. That matrix is augmented to produce a larger matrix, the transformation matrix which can handle translation. So the point must always be converted into position vectors to use the transformation matrix. So, if you want to rotate and scale points, you must first translate them to an origin about which you want to rotate and scale, then translate the back.

• position vectors

Well, i tried various things but i still cannot get it right. I have found absolutely zero example on how the QMatrix3x3 can be declared or used, in particular with QPointF vector or array.
I kept trying with the Transform functions based on what i understand of your recommendations but with not much success. The best i can get is the first point matching and the angle matching, but then the second point is still false and thus all the other points in the array are also false. The "real" points (: physical) are simulated with a -1.0,1.0 translation followed by a 10° rotation. In both case the registration points are the two first. The goal is to have the "ideal" points mapped to the "real" points.

It seems that i should use https://doc.qt.io/qt-5/qmatrix.html#map-3 but i dont understand how to define the matrix to my coordinate system, nor what i should use as coordinate system in this case.

N;B. there is no scaling involved here, because the scale will never change between the ideal and real, the only goal is to map the ideal to real, so all the points other than registrations can be accurately positionned on the real surface relatively to the registrations marks.

``````#include "mainwindow.h"
#include <QApplication>
#include <QtMath>
#include <QVector2D>
#include <QMatrix3x3>

double angle2Points(const QPointF V1, const QPointF V2){

double length1 = sqrt(V1.x() * V1.x() + V1.y() * V1.y()); // modulus of Vector V1 i.e. |V1|
double length2 = sqrt(V2.x() * V2.x() + V2.y() * V2.y()); // modulus of Vector V2 i.e. |V2|
double dot = V1.x() * V2.x() + V1.y() * V2.y(); // dot product between two vectors.
double a = dot / (length1 * length2);
if (a >= 1.0) return 0.0;
else if (a <= -1.0) return qRadiansToDegrees(M_PI);
else return qRadiansToDegrees(acos(a));
}
void map(){

QPolygonF ideal;
QPolygonF real;
QPolygonF pts3;
QPolygonF pts4;
QPolygonF pts5;

ideal << QPointF(2.0,2.0)
<< QPointF(15.0,30.0)
<< QPointF(2.1805,2.4565)
<< QPointF(6.4641,8.8659)
<< QPointF(4.0768,23.2113)
<< QPointF(12.0465,6.4675);

qInfo( "\nIdeal"); // ideal
for(int i=0;i<ideal.length()-4;i++){ qInfo( "%.04f %.04f",ideal.toList().at(i).x(),ideal.toList().at(i).y()); }
double ang1 = angle2Points(ideal.toList().at(0), ideal.toList().at(1));
qInfo( "angle %.06f°",ang1);

qInfo( "\nReal"); // real
real = QTransform().translate(-1,1).rotate(10).map(ideal);
for(int i=0;i<real.length()-4;i++){ qInfo( "P%d %.04f %.04f",i,real.toList().at(i).x(),real.toList().at(i).y()); }
double ang2 = angle2Points(real.toList().at(0), ideal.toList().at(1));
qInfo( "angle %.06f°",ang2);

//qInfo("\nTranslated"); // translate by the offset between ideal and real point 0
QPointF ofst1 = ideal.toList().at(0) - real.toList().at(0);
pts3 = QTransform().translate(ofst1.x(),ofst1.y()).map(real);
//for(int i=0;i<pts3.length()-4;i++){ qInfo( "P%d %.04f %.04f",i,pts3.toList().at(i).x(),pts3.toList().at(i).y()); }
double ang3 = angle2Points(pts3.toList().at(0), ideal.toList().at(1));
//qInfo( "angle %.06f°",ang3);

//qInfo("\nRotated");// rotate by the angle offset between real and translated point 0
pts4 = QTransform().rotate(ang3-ang2).map(pts3);
//for(int i=0;i<pts4.length()-4;i++){ qInfo( "P%d %.04f %.04f",i,pts4.toList().at(i).x(),pts4.toList().at(i).y()); }
//double ang4 = angle2Points(pts4.toList().at(0), pts.toList().at(1));
//qInfo( "angle %.06f°",ang4);

qInfo("\nMapped");// translate by the offset between current and real point 0
QPointF ofst2 = real.toList().at(0) - pts4.toList().at(0);
//qInfo( "offset X:%.04f Y:%.04f",ofst2.x(),ofst2.y());
pts5 = QTransform().translate(ofst2.x(),ofst2.y()).map(pts4);
for(int i=0;i<pts5.length()-4;i++){ qInfo( "P%d %.04f %.04f",i,pts5.toList().at(i).x(),pts5.toList().at(i).y()); }
double ang5 = angle2Points(pts5.toList().at(0), ideal.toList().at(1));
qInfo( "angle %.06f°",ang5);

//QVector2D p(0.0, 0.0);
//QVector2D a(0.0, 0.0);
//QMatrix3x3 m;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
map();
a.exit();
return 0;
}
``````

Console output :

``````Ideal
2.0000 2.0000
15.0000 30.0000
angle 18.434949°

Real
P0 0.6223 3.3169
P1 8.5627 33.1490
angle 15.938752°

Translated
P0 0.6223 3.3169
P1 7.2559 33.4665
angle 15.938752°
``````

Update: I finally found a way to use the map function directly, it seems promising however it only works for the first two points:

``````void map2(){

QPolygonF polygonIn;
polygonIn
<< QPointF(2.0000, 2.0000)
<< QPointF(15.0000, 2.0000)
<< QPointF(15.0000, 30.0000)
<< QPointF(2.0000, 30.0000);

QPolygonF polygonOut;
polygonOut
<< QPointF(0.6223,3.3169)
<< QPointF(8.5627,3.3169)
<< QPointF(8.5627,33.1490)
<< QPointF(0.6223,33.1490);

QPolygonF ideal,real,p3;
ideal << QPointF(2.0,2.0)
<< QPointF(15.0,30.0)
<< QPointF(2.1805,2.4565)
<< QPointF(6.4641,8.8659)
<< QPointF(4.0768,23.2113)
<< QPointF(12.0465,6.4675);

QTransform transform;
auto isOk = QTransform::quadToQuad(polygonIn, polygonOut, transform);
if(!isOk) throw std::runtime_error("Transformation impossible");

qInfo( "\nIdeal");
for(int i=0;i<ideal.length();i++){ qInfo( "P%d %.04f %.04f",i,ideal.toList().at(i).x(),ideal.toList().at(i).y()); }

qInfo( "\nReal");
real = QTransform().translate(-1,1).rotate(10).map(ideal);
for(int i=0;i<real.length();i++){ qInfo( "P%d %.04f %.04f",i,real.toList().at(i).x(),real.toList().at(i).y()); }

qInfo( "\nMapped");
for(int i=0;i<ideal.length();i++){ qInfo( "P%d %.04f %.04f",i,transform.map(ideal.toList().at(i)).x(),transform.map(ideal.toList().at(i)).y()); }
}
``````
``````Ideal
P0 2.0000 2.0000
P1 15.0000 30.0000
P2 2.1805 2.4565
P3 6.4641 8.8659
P4 4.0768 23.2113
P5 12.0465 6.4675

Real
P0 0.6223 3.3169
P1 8.5627 33.1490
P2 0.7208 3.7978
P3 3.8263 10.8537
P4 -1.0157 24.5666
P5 9.7404 9.4611

Mapped
P0 0.6223 3.3169
P1 8.5627 33.1490
P2 0.7325 3.8033
P3 3.3490 10.6321
P4 1.8908 25.9161
P5 6.7587 8.0767
``````

• First observation: The points are in mathematical coordinates, but the images are in Qt coordinates. I did not notice that will I made my response, sorry. Now select the 2.0,20 point as the origin, your angle is about that point, and subtract that point from the mark value. Both mark points are now position vectors. For all the measured values subtract the value of the point that corresponds to the origin. Find the scale value that will translated the second true mark value to the second measured mark value, distance only. Now find the angle between the vector from the true mark origin to the second true mark and the vector from the true mark origin to the second measured mark. That is the angle by which all of the measured value require rotation. All that is required is to do an inverse scale back to measured and translate the origin.

I hope this helps.

• About the observation you might want to use a transformation matrix with scale(1.0,-1.0) to flip the image into mathematical coordinates. It will help you to visualize what is happening.

• Thank you again for valuable advises. I will try that.
I want to precise that the sketch i sent is only for illustration of the problem, it does not even match actual coordinates in the array, the application does not involve graphical representation of these points, it handles the machine control.

The coordinates are in millimeters all along, they are never represented graphically.
I use QPointF and QPolygonF to ease the handling within QT and possibly benefit from the transform functions.

• Why don't you use images like those to visualize what is happening. I would first translate every thing to have the first mark point at 0,0. Create an image of that then scale it and create an image, while show the two second mark points close. Finally, rotate the value to get the first mark point over the second mark point. This will allow you to see the error immediately so you can fix it.

On the flipping, remember that the image is rotated above the top of the rectangle used for painting; therefore, you must translate it downward by the height of the rectangle.