Python / PIL仿射变换

时间:2013-06-12 01:08:09

标签: python matlab python-imaging-library transformation pillow

这是PIL中的一个基本转换问题。我已经尝试了至少几次 在过去的几年里,正确实现这一点,似乎有 我不太了解PIL中的Image.transform。我想要 我可以实现相似变换(或仿射变换) 清楚地说明图像的限制。为了确保我的方法有效,我 在Matlab中实现它。

Matlab实现如下:

im = imread('test.jpg');
y = size(im,1);
x = size(im,2);
angle = 45*3.14/180.0;
xextremes = [rot_x(angle,0,0),rot_x(angle,0,y-1),rot_x(angle,x-1,0),rot_x(angle,x-1,y-1)];
yextremes = [rot_y(angle,0,0),rot_y(angle,0,y-1),rot_y(angle,x-1,0),rot_y(angle,x-1,y-1)];
m = [cos(angle) sin(angle) -min(xextremes); -sin(angle) cos(angle) -min(yextremes); 0 0 1];
tform = maketform('affine',m')
round( [max(xextremes)-min(xextremes), max(yextremes)-min(yextremes)])
im = imtransform(im,tform,'bilinear','Size',round([max(xextremes)-min(xextremes), max(yextremes)-min(yextremes)]));
imwrite(im,'output.jpg');

function y = rot_x(angle,ptx,pty),
    y = cos(angle)*ptx + sin(angle)*pty

function y = rot_y(angle,ptx,pty),
    y = -sin(angle)*ptx + cos(angle)*pty

这可以按预期工作。这是输入:

enter image description here

这是输出:

enter image description here

这是实现相同功能的Python / PIL代码 转化:

import Image
import math

def rot_x(angle,ptx,pty):
    return math.cos(angle)*ptx + math.sin(angle)*pty

def rot_y(angle,ptx,pty):
    return -math.sin(angle)*ptx + math.cos(angle)*pty

angle = math.radians(45)
im = Image.open('test.jpg')
(x,y) = im.size
xextremes = [rot_x(angle,0,0),rot_x(angle,0,y-1),rot_x(angle,x-1,0),rot_x(angle,x-1,y-1)]
yextremes = [rot_y(angle,0,0),rot_y(angle,0,y-1),rot_y(angle,x-1,0),rot_y(angle,x-1,y-1)]
mnx = min(xextremes)
mxx = max(xextremes)
mny = min(yextremes)
mxy = max(yextremes)
im = im.transform((int(round(mxx-mnx)),int(round((mxy-mny)))),Image.AFFINE,(math.cos(angle),math.sin(angle),-mnx,-math.sin(angle),math.cos(angle),-mny),resample=Image.BILINEAR)
im.save('outputpython.jpg')

这是Python的输出:

enter image description here

多年来,我在多个操作系统上尝试使用多个版本的Python和PIL,结果总是大致相同。

这是说明问题的最简单的情况,我明白如果它是我想要的旋转,我可以使用im.rotate调用进行旋转,但我想剪切和缩放,这只是一个例子说明一个问题。我想为所有仿射变换获得相同的输出。我希望能够做到这一点。

修改

如果我将变换线更改为:

im = im.transform((int(round(mxx-mnx)),int(round((mxy-mny)))),Image.AFFINE,(math.cos(angle),math.sin(angle),0,-math.sin(angle),math.cos(angle),0),resample=Image.BILINEAR)

这是我得到的输出:

enter image description here

编辑#2

我旋转了-45度并将偏移更改为-0.5 * mnx和-0.5 * mny并获得了这个:

enter image description here

4 个答案:

答案 0 :(得分:11)

OK!所以我整个周末一直在努力理解这一点,我想我有一个 满足我的回答。谢谢大家的意见和建议!

我首先看一下:

affine transform in PIL python

虽然我看到作者可以进行任意相似变换 没有解释为什么我的代码不起作用,也没有解释空间 我们需要转换的图像布局,也不提供线性 代数解决我的问题。

但我确实从他的代码中看到我确实看到他正在划分旋转部分 矩阵(a,b,d和e)进入比喻,这让我感到奇怪。我回去看了 我引用的PIL文档:

“im.transform(size,AFFINE,data,filter)=> image

对图像应用仿射变换,并将结果放在新图像中 具有给定的大小。

数据是一个6元组(a,b,c,d,e,f),其中包含前两行 仿射变换矩阵。对于输出图像中的每个像素(x,y),新的 值取自输入中的位置(a x + b y + c,d x + e y + f) 图像,四舍五入到最近的像素。

此功能可用于缩放,平移,旋转和剪切原稿 图像“。

所以参数(a,b,c,d,e,f)是变换矩阵,但是映射的那个 目标图像中的(x,y)到源中的(a x + b y + c,d x + e y + f) 图片。但不是要应用变换矩阵的参数,而是 它的逆。那就是:

  • 怪异
  • 与Matlab不同
  • 但是现在,幸运的是,我完全理解

我正在附上我的代码:

import Image
import math
from numpy import matrix
from numpy import linalg

def rot_x(angle,ptx,pty):
    return math.cos(angle)*ptx + math.sin(angle)*pty

def rot_y(angle,ptx,pty):
    return -math.sin(angle)*ptx + math.cos(angle)*pty

angle = math.radians(45)
im = Image.open('test.jpg')
(x,y) = im.size
xextremes = [rot_x(angle,0,0),rot_x(angle,0,y-1),rot_x(angle,x-1,0),rot_x(angle,x-1,y-1)]
yextremes = [rot_y(angle,0,0),rot_y(angle,0,y-1),rot_y(angle,x-1,0),rot_y(angle,x-1,y-1)]
mnx = min(xextremes)
mxx = max(xextremes)
mny = min(yextremes)
mxy = max(yextremes)
print mnx,mny
T = matrix([[math.cos(angle),math.sin(angle),-mnx],[-math.sin(angle),math.cos(angle),-mny],[0,0,1]])
Tinv = linalg.inv(T);
print Tinv
Tinvtuple = (Tinv[0,0],Tinv[0,1], Tinv[0,2], Tinv[1,0],Tinv[1,1],Tinv[1,2])
print Tinvtuple
im = im.transform((int(round(mxx-mnx)),int(round((mxy-mny)))),Image.AFFINE,Tinvtuple,resample=Image.BILINEAR)
im.save('outputpython2.jpg')

和python的输出:

enter image description here

让我在最后的总结中再次说明这个问题的答案:

PIL需要您要应用的仿射变换的反转。

答案 1 :(得分:3)

我希望通过carlosdcRuediger Jungbeck对答案进行扩展,以提供更实用的python代码解决方案,并进行一些解释。

首先,PIL使用反仿射变换是绝对正确的,如carlosdc's answer中所述。但是,没有必要使用线性代数来计算原始变换的逆变换 - 相反,它可以很容易地直接表达。我将使用缩放并围绕其中心旋转图像,例如code linked to中的Ruediger Jungbeck's answer,但是将其扩展为例如剪毛也是。

在接近如何表达用于缩放和旋转的逆仿射变换之前,请考虑我们如何找到原始变换。正如在Ruediger Jungbeck's answer中暗示的那样,缩放和旋转的组合操作的转换被发现为缩放关于原点的图像旋转图像的基本运算符的组合关于起源

但是,由于我们想要围绕自己的中心缩放和旋转图像,并且原点(0,0)是图像的defined by PIL to be the upper left corner,我们首先需要翻译图像,使其中心与起源。在应用缩放和旋转之后,我们还需要将图像转换回图像的新中心(在缩放和旋转后可能与旧中心不同),最终在图像的中心画布。

所以最初的"标准"我们之后的仿射变换将是以下基本算子的组成:

  1. 找到图片的当前中心(c_x, c_y),并按(-c_x, -c_y)翻译图片,因此图片的中心位于原点(0, 0)

  2. 按比例因子(s_x, s_y)缩放关于原点的图像。

  3. 围绕原点旋转图像\theta

  4. 找到图片的新中心(t_x, t_y),并按(t_x, t_y)翻译图片,以便新中心最终位于图片画布的中心。

  5. 为了找到我们之后的转换,我们首先需要知道基本运算符的转换矩阵,如下所示:

    • (x, y)翻译:
    • (s_x, s_y)缩放:
    • \theta轮换:

    然后,我们的复合变换可以表示为:

    等于

    ,其中

    现在,为了找到这个复合仿射变换的逆,我们只需要以相反的顺序计算每个基本算子的逆的组成。也就是说,我们想要

    1. (-t_x, -t_y)

    2. 翻译图像
    3. 通过-\theta旋转原点图像。

    4. (1/s_x, 1/s_y)缩放关于原点的图像。

    5. (c_x, c_y)翻译图片。

    6. 这导致转换矩阵

      ,其中

      这与<{3}}中code linked to中使用的转换完全相同。通过重复使用carlosdc在其帖子中用于计算图像(t_x, t_y)的相同技术,并将图像翻译为(t_x, t_y) - 将旋转应用于图像的所有四个角,可以使其更加方便,然后计算最小和最大X和Y值之间的距离。然而,由于图像围绕其自身的中心旋转,因此不需要旋转所有四个角,因为每对相对的角都旋转并且对称地#34;。

      这是carlosdc代码的重写版本,已被修改为直接使用反仿射变换,并且还增加了缩放:

      from PIL import Image
      import math
      
      
      def scale_and_rotate_image(im, sx, sy, deg_ccw):
          im_orig = im
          im = Image.new('RGBA', im_orig.size, (255, 255, 255, 255))
          im.paste(im_orig)
      
          w, h = im.size
          angle = math.radians(-deg_ccw)
      
          cos_theta = math.cos(angle)
          sin_theta = math.sin(angle)
      
          scaled_w, scaled_h = w * sx, h * sy
      
          new_w = int(math.ceil(math.fabs(cos_theta * scaled_w) + math.fabs(sin_theta * scaled_h)))
          new_h = int(math.ceil(math.fabs(sin_theta * scaled_w) + math.fabs(cos_theta * scaled_h)))
      
          cx = w / 2.
          cy = h / 2.
          tx = new_w / 2.
          ty = new_h / 2.
      
          a = cos_theta / sx
          b = sin_theta / sx
          c = cx - tx * a - ty * b
          d = -sin_theta / sy
          e = cos_theta / sy
          f = cy - tx * d - ty * e
      
          return im.transform(
              (new_w, new_h),
              Image.AFFINE,
              (a, b, c, d, e, f),
              resample=Image.BILINEAR
          )
      
      
      im = Image.open('test.jpg')
      im = scale_and_rotate_image(im, 0.8, 1.2, 10)
      im.save('outputpython.png')
      

      这就是结果的样子(用(sx,sy)=(0.8,1.2)缩放,逆时针旋转10度):

      Ruediger Jungbeck's answer

答案 2 :(得分:1)

我认为this应该回答你的问题。

如果没有,你应该考虑将仿射变换连接到另一个变换中。

因此,您可以将所需的操作拆分为:

  1. 将orgin移动到图像的中心

  2. 旋转

  3. 将原点移回

  4. 调整

  5. 你可以计算出一个转换。

答案 3 :(得分:0)

图像围绕中心点旋转。 PIL图像坐标系(0,0)的中心在左上角。

如果您使用矩阵的乘积来构造仿射变换,建议您添加一个临时的定心/定心变换。

我们从以下基本块构建仿射变换

import numpy as np

def translation(x, y):
    mat = np.eye(3)
    mat[0, 2] = x
    mat[1, 2] = y
    return mat

def scaling(s):
    mat = np.eye(3)
    mat[0, 0] = s
    mat[1, 1] = s
    return mat

def rotation(degree):
    mat = np.eye(3)
    rad = np.deg2rad(degree)
    mat[0, 0] = np.cos(rad)
    mat[0, 1] = -np.sin(rad)
    mat[1, 0] = np.sin(rad)
    mat[1, 1] = np.cos(rad)
    return mat

def tmp_center(w, h):
    mat = np.eye(3)
    mat[0, 2] = -w/2
    mat[1, 2] = -h/2
    return mat

然后加载图像,并定义转换。 与其他库不同,请确保使用其他人指出的逆函数。

from PIL import Image
img = Image.from_array(...)
w, h = img.size
T = translation(20, 23) @ tmp_center(-w, -h) @ rotation(5) @ scaling(0.69) @ tmp_center(w, h)
coeff = np.linalg.inv(T).flatten()[:6]

out = img.transform(img.size, Image.AFFINE, coeff, resample.Image.BILINEAR)