最近做一个项目,需要自己生成图片数据。查了下需要用到仿射变换(Affine transformation)跟投射变换(perspective transformation)。折腾了一两天,把自己的心得记录一下,有些坑网上并没有找到答案。
首先是仿射变换,其实是将图形在2D平面内做变换,变换前后图片中原来平行的线仍会保持平行,可以想象是将长方形变换为平行四边形。细节问题我这里不说了,网上资料不少。先要调用opencv函数cv2.getAffineTransform来计算变换矩阵,然后利用变换矩阵将图片进行变换。
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[50, 50], [200, 50], [100, 200]])
#有了原始三个点和新的三个点坐标,便可以通过warpAffine
#函数计算出变换矩阵
#这个矩阵相当与讲坐标系进行了某种线性变换
M = cv2.getAffineTransform(pts1, pts2)
dst1 = cv2.warpAffine(img, M, (2*cols, 2*rows))
But, 这不是重点,我的需求是给定平面内一个点,计算变换后的点坐标。查看Opencv官方手册,发现公式(x_new, y_new).T = map_matrix*(x, y, 1).T
于是根据这个公式,进行编写就好了。
np.matrix(M)*np.matrix([50, 200, 1]).T
=> np.matrix([100, 200]).T
这个是容易验证的。
坑的地方在投射变换,相当于3D变换,图像变换是没有问题的。跟仿射变换的区别是这里需要提供变换前后的四个点。
h, w, _ = img.shape
ratio = 0.1
pts1 = np.float32([[0, 0], [w, 0], [0, h], [w, h]])
pts2 = np.float32([[0, 0], [w, ratio*h], [0, h], [w, (1-ratio)*h]]
M = cv2.getPerspectiveTransform(pts1, pts2)
img_new = cv2.warpPerspective(img, M, (w, h))
但是在透射变换时,通过变换矩阵讲平面内点变过去的时候,就有点问题了。举个例子:
pts1 = np.float32([[0, 0], [10, 0], [0, 5], [10, 5]])
pts2 = np.float32([[0, 0], [10, 1], [0, 5], [10, 4]])
M = cv2.getPerspectiveTransform(pts1, pts2)
np.matrix(M)*np.matrix([10, 0, 1]).T
=> np.matrix([16.6666666, 1.666666, 1.66666]).T
看到没,原始的点(10,0)并没有变换到(10, 1)。结果是(16.666666, 1.66666)
我查看官方文档,发现文档上写的公式是:
(t*x_new, t*y_new, t).T = map_matrix*(x, y, 1).T
之前一直很郁闷这个t是什么东西,如果再结合上面的结果,发现t像是一个放大系数。通过对比,可以看到t=1.6666, 将(16.66666, 1.66666)除以t,便刚好得到了我们要的结果。汗。。。
也没时间研究这个了,先用起来再说。希望也能帮到别人~
补充一下这个t的含义,个人理解应该是按照我给的四个点的变换,使得三维的坐标系有一个拉伸,t就是拉伸系数。而要得到二维平面的点坐标,则需要将三维的点往二维平面上投影,投影后的坐标需要除以这个拉伸系数。