Python/PIL仿射变换

2024-05-23 22:51:11 发布

您现在位置:Python中文网/ 问答频道 /正文

这是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


Tags: imagemathsincosminmaxptyim
3条回答

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

如果不是,您应该考虑仿射转换可以连接到另一个转换中。

所以你可以把你想要的手术分成:

  1. 将orgin移到图像的中心

  2. 旋转

  3. 将原点移回

  4. 调整大小

你甚至可以计算出一个变换。

好的!所以我整个周末都在努力理解,我想我有一个 让我满意的答案。谢谢大家的意见和建议!

我先看看这个:

affine transform in PIL python

虽然我看到作者可以做任意的相似性转换 不能解释为什么我的代码不能工作,也不能解释 我们需要转换的图像布局,也不提供线性 我问题的代数解。

但我确实从他的代码中看到,他在划分 矩阵(a,b,d和e)的比例,这让我觉得很奇怪。我回去看书了 我引用的PIL文件:

“im.transform(size,AFFINE,data,filter)=>;图像

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

数据是一个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)是a变换矩阵,但是映射 目标图像中的(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需要应用仿射变换的逆。

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

首先,如carlosdc's answer所述,PIL使用反仿射变换是绝对正确的。然而,不需要用线性代数来计算原始变换的逆变换,它可以很容易地直接表示出来。我将使用缩放和围绕其中心旋转图像作为示例,如Ruediger Jungbeck's answer中的code linked to中所示,但是扩展它来完成剪切也是相当简单的。

在讨论如何表示缩放和旋转的反仿射变换之前,请考虑如何找到原始变换。正如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)翻译图像,这样新中心将最终位于图像画布的中心。

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

  • (x, y)翻译
  • (s_x, s_y)缩放
  • 旋转\theta

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

等于

或者

其中

现在,要求这个复合仿射变换的逆,只需要求出每个基本算子逆的逆的逆的逆的逆的组成。也就是说,我们想

  1. 通过(-t_x, -t_y)

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

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

  4. 通过(c_x, c_y)翻译图像。

这会产生一个转换矩阵

其中

这与Ruediger Jungbeck's answer中的code linked to中使用的转换完全相同。通过重用carlosdc在其文章中使用的计算图像(t_x, t_y)的相同技术,并通过(t_x, t_y)-将旋转应用于图像的所有四个角来翻译图像,然后计算最小和最大X和Y值之间的距离,可以使该方法更加方便。但是,由于图像是围绕其中心旋转的,因此不需要旋转所有四个角,因为每对相对的角都是“对称”旋转的。

下面是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

相关问题 更多 >