首页 文章

Python / PIL仿射变换

提问于
浏览
12

这是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调用进行旋转,但我也想剪切和缩放,这只是一个例子来说明问题 . 我想为所有仿射变换获得相同的输出 . 我希望能够做到这一点 .

EDIT:

如果我将变换线更改为:

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

EDIT #2

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

enter image description here

3 回答

  • 1

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

    如果没有,你应该考虑仿射变换可以连接成另一个变换 .

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

    • 将orgin移动到图像的中心

    • 旋转

    • 将原点移回

    • 调整大小

    你可以计算出一个转换 .

  • 2

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

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

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

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

    所以原来的“标准”仿射变换我们之后将成为以下基本运算符的组成:

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

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

    • 以原点
      \theta
      旋转原点图像 .

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

    为了找到我们所追求的变换,我们首先需要知道基本运算符的变换矩阵,如下所示:

    • 翻译
      (x, y)

    • 缩放
      (s_x, s_y)


    • \theta
      轮换:

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

    等于

    要么

    哪里


    .

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

    • 通过
      (-t_x, -t_y)
      翻译图像

    • 通过
      -\theta
      旋转原点图像 .

    • 通过
      (1/s_x, 1/s_y)
      缩放关于原点的图像 .


    • (c_x, c_y)
      翻译图像 .

    这导致变换矩阵

    哪里


    .

    这与Ruediger Jungbeck's answercode linked to中使用的转换完全相同 . 通过重复使用carlosdc在其帖子中用于计算图像
    (t_x, t_y)
    的相同技术,并通过
    (t_x, t_y)
    将图像转换到图像的所有四个角,然后计算最小值之间的距离,可以更方便和最大X和Y值 . 但是,由于图像围绕其自身的中心旋转,因此不需要旋转所有四个角,因为每对相对的角都旋转"symmetrically" .

    这是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度):

    Scaled and rotated

  • 10

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

    我从这看起来:

    affine transform in PIL python

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

    但我确实从他的代码中看到我确实看到他将矩阵的旋转部分(a,b,d和e)划分为令我惊讶的刻度 . 我回过头来阅读我引用的PIL文档:

    “im.transform(大小,AFFINE,数据,过滤器)=>图像

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

    数据是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 requires the inverse of the affine transformation you want to apply.

相关问题