透视转换如何在PIL中起作用?

PIL的转换function有一个透视模式,需要8-tupel的数据,但我不知道如何转换,让我们说一个30度的正确倾斜到该tupel。

任何人都可以解释吗?

这是它的文档: http : //effbot.org/imagingbook/image.htm

要应用透视变换,首先必须知道平面A中的四个点,它将被映射到平面B中的四个点。通过这些点,可以导出单应变换。 通过这样做,您可以获得8个系数,并且可以进行转换。

网站http://xenia.media.mit.edu/~cwren/interpolator/以及许多其他文本描述了如何确定这些系数。; 为了方便起见,这里是根据上述链接直接实现的:

import numpy def find_coeffs(pa, pb): matrix = [] for p1, p2 in zip(pa, pb): matrix.append([p1[0], p1[1], 1, 0, 0, 0, -p2[0]*p1[0], -p2[0]*p1[1]]) matrix.append([0, 0, 0, p1[0], p1[1], 1, -p2[1]*p1[0], -p2[1]*p1[1]]) A = numpy.matrix(matrix, dtype=numpy.float) B = numpy.array(pb).reshape(8) res = numpy.dot(numpy.linalg.inv(AT * A) * AT, B) return numpy.array(res).reshape(8) 

其中pb是当前平面中的四个顶点, pa在结果平面中包含四个顶点。

所以,假设我们转换一个图像如下:

 import sys from PIL import Image img = Image.open(sys.argv[1]) width, height = img.size m = -0.5 xshift = abs(m) * width new_width = width + int(round(xshift)) img = img.transform((new_width, height), Image.AFFINE, (1, m, -xshift if m > 0 else 0, 0, 1, 0), Image.BICUBIC) img.save(sys.argv[2]) 

下面是上面代码的示例input和输出:

在这里输入图像描述在这里输入图像描述

我们可以继续上一个代码,并执行透视变换来恢复剪切:

 coeffs = find_coeffs( [(0, 0), (256, 0), (256, 256), (0, 256)], [(0, 0), (256, 0), (new_width, height), (xshift, height)]) img.transform((width, height), Image.PERSPECTIVE, coeffs, Image.BICUBIC).save(sys.argv[3]) 

导致:

在这里输入图像描述

你也可以有一些有趣的目的地点:

在这里输入图像描述在这里输入图像描述

只是稍微解决这个问题因为这是Google在Google中唯一与透视转换有关的东西。 下面是一些稍微更一般的基于上述代码创build一个透视变换matrix,并生成一个函数,将在任意点上运行该变换:

 import numpy as np def create_perspective_transform_matrix(src, dst): """ Creates a perspective transformation matrix which transforms points in quadrilateral ``src`` to the corresponding points on quadrilateral ``dst``. Will raise a ``np.linalg.LinAlgError`` on invalid input. """ # See: # * http://xenia.media.mit.edu/~cwren/interpolator/ # * http://stackoverflow.com/a/14178717/71522 in_matrix = [] for (x, y), (X, Y) in zip(src, dst): in_matrix.extend([ [x, y, 1, 0, 0, 0, -X * x, -X * y], [0, 0, 0, x, y, 1, -Y * x, -Y * y], ]) A = np.matrix(in_matrix, dtype=np.float) B = np.array(dst).reshape(8) af = np.dot(np.linalg.inv(AT * A) * AT, B) return np.append(np.array(af).reshape(8), 1).reshape((3, 3)) def create_perspective_transform(src, dst, round=False, splat_args=False): """ Returns a function which will transform points in quadrilateral ``src`` to the corresponding points on quadrilateral ``dst``:: >>> transform = create_perspective_transform( ... [(0, 0), (10, 0), (10, 10), (0, 10)], ... [(50, 50), (100, 50), (100, 100), (50, 100)], ... ) >>> transform((5, 5)) (74.99999999999639, 74.999999999999957) If ``round`` is ``True`` then points will be rounded to the nearest integer and integer values will be returned. >>> transform = create_perspective_transform( ... [(0, 0), (10, 0), (10, 10), (0, 10)], ... [(50, 50), (100, 50), (100, 100), (50, 100)], ... round=True, ... ) >>> transform((5, 5)) (75, 75) If ``splat_args`` is ``True`` the function will accept two arguments instead of a tuple. >>> transform = create_perspective_transform( ... [(0, 0), (10, 0), (10, 10), (0, 10)], ... [(50, 50), (100, 50), (100, 100), (50, 100)], ... splat_args=True, ... ) >>> transform(5, 5) (74.99999999999639, 74.999999999999957) If the input values yield an invalid transformation matrix an identity function will be returned and the ``error`` attribute will be set to a description of the error:: >>> tranform = create_perspective_transform( ... np.zeros((4, 2)), ... np.zeros((4, 2)), ... ) >>> transform((5, 5)) (5.0, 5.0) >>> transform.error 'invalid input quads (...): Singular matrix """ try: transform_matrix = create_perspective_transform_matrix(src, dst) error = None except np.linalg.LinAlgError as e: transform_matrix = np.identity(3, dtype=np.float) error = "invalid input quads (%s and %s): %s" %(src, dst, e) error = error.replace("\n", "") to_eval = "def perspective_transform(%s):\n" %( splat_args and "*pt" or "pt", ) to_eval += " res = np.dot(transform_matrix, ((pt[0], ), (pt[1], ), (1, )))\n" to_eval += " res = res / res[2]\n" if round: to_eval += " return (int(round(res[0][0])), int(round(res[1][0])))\n" else: to_eval += " return (res[0][0], res[1][0])\n" locals = { "transform_matrix": transform_matrix, } locals.update(globals()) exec to_eval in locals, locals res = locals["perspective_transform"] res.matrix = transform_matrix res.error = error return res 

这是一个生成变换系数的纯Python版本 (正如我见过的几个请求)。 我制作并使用它来制作PyDraw纯Python图像绘制软件包。

如果将它用于您自己的项目,请注意,计算需要几个高级matrix操作,这意味着此函数需要另一个幸运的纯Python,matrix库,最初由Raymond Hettinger编写,可以在这里或这里 下载 。

 import matfunc as mt def perspective_coefficients(self, oldplane, newplane): """ Calculates and returns the transform coefficients needed for a perspective transform, ie tilting an image in 3D. Note: it is not very obvious how to set the oldplane and newplane arguments in order to tilt an image the way one wants. Need to make the arguments more user-friendly and handle the oldplane/newplane behind the scenes. Some hints on how to do that at http://www.cs.utexas.edu/~fussell/courses/cs384g/lectures/lecture20-Z_buffer_pipeline.pdf | **option** | **description** | --- | --- | oldplane | a list of four old xy coordinate pairs | newplane | four points in the new plane corresponding to the old points """ # first find the transform coefficients, thanks to http://stackoverflow.com/questions/14177744/how-does-perspective-transformation-work-in-pil pb,pa = oldplane,newplane grid = [] for p1,p2 in zip(pa, pb): grid.append([p1[0], p1[1], 1, 0, 0, 0, -p2[0]*p1[0], -p2[0]*p1[1]]) grid.append([0, 0, 0, p1[0], p1[1], 1, -p2[1]*p1[0], -p2[1]*p1[1]]) # then do some matrix magic A = mt.Matrix(grid) B = mt.Vec([xory for xy in pb for xory in xy]) AT = A.tr() ATA = AT.mmul(A) gridinv = ATA.inverse() invAT = gridinv.mmul(AT) res = invAT.mmul(B) a,b,c,d,e,f,g,h = res.flatten() # finito return a,b,c,d,e,f,g,h 

8个变换系数(a,b,c,d,e,f,g,h)对应于以下变换:

x'=(a x + b y + c)/(g x + h y + 1)
y'=(d x + e y + f)/(g x + h y + 1)

这8个系数通常可以通过求解定义平面上4个点如何变换的8个(线性)方程来find(2D中的4个点 – > 8个方程),尽pipe你可能会看到mmgp解答这个问题的答案发现它更准确地改变线

 res = numpy.dot(numpy.linalg.inv(AT * A) * AT, B) 

 res = numpy.linalg.solve(A, B) 

即没有真正的理由在那里实际地倒置Amatrix,或者将其乘以它的转置并失去一点准确性,以求解方程。

至于你的问题,对于(x0,y0)周围的theta度的简单倾斜,你正在寻找的系数是:

 def find_rotation_coeffs(theta, x0, y0): ct = cos(theta) st = sin(theta) return np.array([ct, -st, x0*(1-ct) + y0*st, st, ct, y0*(1-ct)-x0*st,0,0]) 

一般来说,任何仿射变换必须有(g,h)等于零。 希望有所帮助!