需求场景:不考虑z轴,已知两点的图纸坐标和设备坐标,计算图纸上其他坐标点的设备坐标。
算法核心思想:已知两点的图纸坐标 P 1 P_1 P1和 P 2 P_2 P2,及对应的设备坐标 P 1 ′ P_1^{'} P1′和 P 2 ′ P_2{'} P2′。对于图纸上任意一点 P P P,对图纸上两点间的向量 P 1 P ⃗ \vec{P_1P} P1P进行缩放和旋转得到 P 1 ′ P ′ ⃗ \vec{P_1^{'}P^{'}} P1′P′,再将 P 1 ′ P ′ ⃗ \vec{P_1^{'}P^{'}} P1′P′平移至 P 1 ′ P_1^{'} P1′,即可得到P点的设备坐标 P ′ P^{'} P′。至于缩放系数 S S S和旋转角度 θ \theta θ可通过分析 P 1 P 2 ⃗ \vec{P_1P_2} P1P2和 P 1 ′ P 2 ′ ⃗ \vec{P_1^{'}P_2^{'}} P1′P2′得知。
预备知识
三角和差公式
c o s ( A + B ) = c o s A c o s B − s i n A s i n B s i n ( A + B ) = s i n A c o s B + c o s A s i n B (1) cos(A+B)=cosAcosB - sinAsinB\\ sin(A+B)=sinAcosB + cosAsinB \tag{1} cos(A+B)=cosAcosB−sinAsinBsin(A+B)=sinAcosB+cosAsinB(1)
反正切函数
θ = a r c t a n ( y x ) a t a n 2 ( y , x ) = { a r c t a n ( y x ) x > 0 a r c t a n ( y x ) + π y ≥ 0 , x < 0 a r c t a n ( y x ) − π y < 0 , x < 0 + π 2 y > 0 , x = 0 − π 2 y < 0 , x = 0 u n d e f i n e d y = 0 , x = 0 (2) \theta=arctan(\frac{y}{x}) \\ atan2(y,x)= \begin{cases} arctan(\frac{y}{x}) &x>0 \\ arctan(\frac{y}{x})+\pi &y \geq 0,x<0 \\ arctan(\frac{y}{x})-\pi &y <0,x<0 \\ +\frac{\pi}{2} &y>0,x=0 \\ -\frac{\pi}{2} &y<0,x=0 \\ undefined &y=0,x=0 \end{cases} \tag{2} θ=arctan(xy)atan2(y,x)=⎩ ⎨ ⎧arctan(xy)arctan(xy)+πarctan(xy)−π+2π−2πundefinedx>0y≥0,x<0y<0,x<0y>0,x=0y<0,x=0y=0,x=0(2)
- θ \theta θ为斜率对应的角度 ( − π 2 , π 2 ) (-\frac{\pi}{2},\frac{\pi}{2}) (−2π,2π), a t a n 2 ( y , x ) atan2(y,x) atan2(y,x)求解向量对应的角度(向量角) [ − π , π ] [-\pi,\pi] [−π,π]。
仿射变换
变换前: P ( x , y ) P(x,y) P(x,y),变换后: P ′ ( X , Y ) P'(X,Y) P′(X,Y)
平移
X = x + d x Y = y + d y (3) X=x+dx \\ Y=y+dy \tag{3} X=x+dxY=y+dy(3)
- d x dx dx和 d y dy dy分别为点在 x x x方向上的和在 y y y方向上的变换量。
缩放
X = S x x Y = S y y (4) X=S_{x}x \\ Y=S_{y}y \tag{4} X=SxxY=Syy(4)
- S x S_{x} Sx和 S y S_{y} Sy分别为点在 x x x方向上的和在 y y y方向上的缩放系数。
- 在图纸坐标 -> 设备坐标过程中,应保证 S x = S y S_{x}=S_{y} Sx=Sy,若 S x ! = S y S_{x}!=S_{y} Sx!=Sy,可先对设备坐标的y轴进行缩放,使得 S x = S y S_{x}=S_{y} Sx=Sy,最后再缩放回来。
旋转
旋转前:
x
=
r
∗
c
o
s
α
y
=
r
∗
s
i
n
α
(5)
x=r*cos\alpha \\ y=r*sin\alpha \tag{5}
x=r∗cosαy=r∗sinα(5)
逆时针旋转
θ
\theta
θ角度后:
X
=
r
∗
c
o
s
(
α
+
θ
)
Y
=
r
∗
s
i
n
(
α
+
θ
)
(6)
X=r*cos(\alpha+\theta) \\ Y=r*sin(\alpha+\theta) \tag{6}
X=r∗cos(α+θ)Y=r∗sin(α+θ)(6)
由
(
2
)
(2)
(2)和
(
5
)
(5)
(5),可将
(
6
)
(6)
(6)化简为:
X
=
x
c
o
s
θ
−
y
s
i
n
θ
Y
=
y
c
o
s
θ
+
x
s
i
n
θ
(7)
X=xcos\theta- ysin\theta \\ Y=ycos\theta + xsin\theta \tag{7}
X=xcosθ−ysinθY=ycosθ+xsinθ(7)
参数求解
缩放系数 S S S
∣ P 1 P 2 ∣ ⃗ = ( P 2 x − P 1 x ) 2 + ( P 2 y − P 1 y ) 2 ∣ P 1 ′ P 2 ′ ∣ ⃗ = ( P ′ 2 x − P ′ 1 x ) 2 + ( P ′ 2 y − P ′ 1 y ) 2 S = ∣ P 1 ′ P 2 ′ ∣ ⃗ ∣ P 1 P 2 ∣ ⃗ (8) \vec{|P_1P_2|}=\sqrt{(P_{2x}-P_{1x})^2+(P_{2y}-P_{1y})^2}\\ \vec{|P_1^{'}P_2{'}|}=\sqrt{(P{'}_{2x}-P{'}_{1x})^2+(P{'}_{2y}-P{'}_{1y})^2} \\ S=\frac{\vec{|P_1^{'}P_2{'}|}}{\vec{|P_1P_2|}} \tag{8} ∣P1P2∣=(P2x−P1x)2+(P2y−P1y)2∣P1′P2′∣=(P′2x−P′1x)2+(P′2y−P′1y)2S=∣P1P2∣∣P1′P2′∣(8)
旋转角度θ
由
(
2
)
(2)
(2)可知:
α
=
a
t
a
n
2
(
P
2
y
−
P
1
y
,
P
2
x
−
P
1
x
)
α
′
=
a
t
a
n
2
(
P
′
2
y
−
P
′
1
y
,
P
′
2
x
−
P
′
1
x
)
θ
=
α
′
−
α
(9)
\alpha=atan2(P_{2y}-P_{1y},P_{2x}-P_{1x}) \\ \alpha^{'}=atan2(P{'}_{2y}-P{'}_{1y},P{'}_{2x}-P{'}_{1x}) \\ \theta =\alpha^{'}-\alpha \tag{9}
α=atan2(P2y−P1y,P2x−P1x)α′=atan2(P′2y−P′1y,P′2x−P′1x)θ=α′−α(9)
- 这里求得的即是 ( 7 ) (7) (7)中的旋转角度,可直接带入。
完整代码
C++
#include <iostream>
#include <cmath>
struct Point {
double x, y;
};
//计算向量的模
double norm(Point a, Point b) {
return std::sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y));
}
Point transformPoint(Point p1a,Point p1b,Point p2a,Point p2b,Point pa){
//计算缩放系数(公式8)
double norm_a=norm(p1a,p2a);
double norm_b=norm(p1b,p2b);
double S=norm_b/norm_a;
//计算旋转角度(公式9)
double alpha_a=std::atan2(p2a.y-p1a.y, p2a.x-p1a.x);
double alpha_b=std::atan2(p2b.y-p1b.y, p2b.x-p1b.x);
double theta=alpha_b-alpha_a;
//p1a->p2a向量
Point vec_p1a_pa={pa.x-p1a.x,pa.y-p1a.y};
//应用缩放(公式4)
Point with_scale;
with_scale.x=S*vec_p1a_pa.x;
with_scale.y=S*vec_p1a_pa.y;
//应用旋转(公式7)
Point with_rotated;
with_rotated.x=with_scale.x*std::cos(theta) - with_scale.y*std::sin(theta);
with_rotated.y=with_scale.y*std::cos(theta) + with_scale.x*std::sin(theta);
//平移至p1b(公式3)
Point pb;
pb.x=with_rotated.x + p1b.x;
pb.y=with_rotated.y + p1b.y;
return pb;
}
int main() {
Point p1a = {5.82, 5.72}, p1b = {687.6,60.9};
Point p2a = {36.82, 38.92}, p2b = {718.2,94.3};
Point pa = {26.32,38.92};
Point pb = transformPoint(p1a,p1b,p2a,p2b,pa);
std::cout << "Transformed point: (" << pb.x << ", " << pb.y << ")" << std::endl;
return 0;
}
如果要转换的点非常多,可以使用矩阵的方式,我抽空另开博文。