<p>我想通过<a href="https://stackoverflow.com/a/17141975/2646573">carlosdc</a>和<a href="https://stackoverflow.com/a/17102503/2646573">Ruediger Jungbeck</a>对答案进行一些扩展,以提供一个更实用的python代码解决方案和一些解释。</p>
<p>首先,如<a href="https://stackoverflow.com/a/17141975/2646573">carlosdc's answer</a>所述,PIL使用反仿射变换是绝对正确的。然而,不需要用线性代数来计算原始变换的逆变换,它可以很容易地直接表示出来。我将使用缩放和围绕其中心旋转图像作为示例,如<a href="https://stackoverflow.com/a/17102503/2646573">Ruediger Jungbeck's answer</a>中的<a href="https://stackoverflow.com/questions/7501009/affine-transform-in-pil-python?">code linked to</a>中所示,但是扩展它来完成剪切也是相当简单的。</p>
<p>在讨论如何表示缩放和旋转的反仿射变换之前,请考虑如何找到原始变换。正如<a href="https://stackoverflow.com/a/17102503/2646573">Ruediger Jungbeck's answer</a>所暗示的,缩放和旋转的组合运算的变换被发现为<em>缩放关于原点</em>的图像和<em>旋转关于原点</em>的图像的基本运算符的组合。</p>
<p>但是,由于我们想缩放并围绕图像的中心旋转图像,并且原点(0,0)是图像的<a href="https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.rotate" rel="noreferrer">defined by PIL to be the upper left corner</a>,我们首先需要翻译图像,使其中心与原点重合。在应用缩放和旋转之后,我们还需要将图像转换回这样一种方式,即图像的新中心(缩放和旋转后可能与旧中心不同)最终位于图像画布的中心。</p>
<p>因此,我们所追求的原始“标准”仿射变换将是以下基本运算符的组合:</p>
<ol>
<li><p>找到图像的当前中心<img src="https://chart.googleapis.com/chart?cht=tx&chl=%28c_x%2C%20c_y%29" alt="(c_x, c_y)"/>,并通过<img src="https://chart.googleapis.com/chart?cht=tx&chl=%28-c_x%2C%20-c_y%29" alt="(-c_x, -c_y)"/>转换图像,因此图像的中心位于原点<img src="https://chart.googleapis.com/chart?cht=tx&chl=%280%2C%200%29" alt="(0, 0)"/>。</p></li>
<li><p>按某个比例因子<img src="https://chart.googleapis.com/chart?cht=tx&chl=%28s_x%2C%20s_y%29" alt="(s_x, s_y)"/>缩放有关原点的图像。</p></li>
<li><p>将图像绕原点旋转一定角度<img src="https://chart.googleapis.com/chart?cht=tx&chl=%5Ctheta" alt="\theta"/>。</p></li>
<li><p>找到图像的新中心<img src="https://chart.googleapis.com/chart?cht=tx&chl=%28t_x%2C%20t_y%29" alt="(t_x, t_y)"/>,并通过<img src="https://chart.googleapis.com/chart?cht=tx&chl=%28t_x%2C%20t_y%29" alt="(t_x, t_y)"/>翻译图像,这样新中心将最终位于图像画布的中心。</p></li>
</ol>
<p>为了找到我们要找的变换,我们首先需要知道基本算子的变换矩阵,如下所示:</p>
<ul>
<li>按<img src="https://chart.googleapis.com/chart?cht=tx&chl=%28x%2C%20y%29" alt="(x, y)"/>:<img src="https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%0A%5Cbegin%7Bbmatrix%7D%0A1%20%26%200%20%26%20x%5C%5C%0A0%20%26%201%20%26%20y%5C%5C%0A0%20%26%200%20%26%201%0A%5Cend%7Bbmatrix%7D" alt=""/>翻译</li>
<li>按<img src="https://chart.googleapis.com/chart?cht=tx&chl=%28s_x%2C%20s_y%29" alt="(s_x, s_y)"/>:<img src="https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%0A%5Cbegin%7Bbmatrix%7D%0As_x%20%26%200%20%26%200%5C%5C%0A0%20%26%20s_y%20%26%200%5C%5C%0A0%20%26%200%20%26%201%0A%5Cend%7Bbmatrix%7D" alt=""/>缩放</li>
<li>旋转<img src="https://chart.googleapis.com/chart?cht=tx&chl=%5Ctheta" alt="\theta"/>:<img src="https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%0A%5Cbegin%7Bbmatrix%7D%0A%5Ccos%28%5Ctheta%29%20%26%20%5Csin%28%5Ctheta%29%20%26%200%5C%5C%0A-%5Csin%28%5Ctheta%29%20%26%20%5Ccos%28%5Ctheta%29%20%26%200%5C%5C%0A0%20%26%200%20%26%201%0A%5Cend%7Bbmatrix%7D" alt=""/></li>
</ul>
<p>然后,我们的复合转换可以表示为:</p>
<p><img src="https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%0AT%20%3D%0A%5Cbegin%7Bbmatrix%7D%0A1%20%26%200%20%26%20t_x%5C%5C%0A0%20%261%20%26%20t_y%5C%5C%0A0%20%26%200%20%26%201%0A%5Cend%7Bbmatrix%7D" alt=""/><img src="https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%0A%5Cbegin%7Bbmatrix%7D%0A%5Ccos%28%5Ctheta%29%20%26%20%5Csin%28%5Ctheta%29%20%26%200%5C%5C%0A-%5Csin%28%5Ctheta%29%20%26%20%5Ccos%28%5Ctheta%29%20%26%200%5C%5C%0A0%20%26%200%20%26%201%0A%5Cend%7Bbmatrix%7D" alt=""/><img src="https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%0A%5Cbegin%7Bbmatrix%7D%0As_x%20%26%200%20%26%200%5C%5C%0A0%20%26%20s_y%20%26%200%5C%5C%0A0%20%26%200%20%26%201%0A%5Cend%7Bbmatrix%7D" alt=""/><img src="https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%0A%5Cbegin%7Bbmatrix%7D%0A1%20%26%200%20%26%20-c_x%5C%5C%0A0%20%261%20%26%20-c_y%5C%5C%0A0%20%26%200%20%26%201%0A%5Cend%7Bbmatrix%7D" alt=""/></p>
<p>等于</p>
<p><img src="https://chart.googleapis.com/chart?cht=tx&chl=T%3D%5Cbegin%7Bbmatrix%7Ds_x%5Ccos%28%5Ctheta%29%26s_y%5Csin%28%5Ctheta%29%26t_x-c_x%20s_x%5Ccos%28%5Ctheta%29-c_y%20s_y%5Csin%28%5Ctheta%29%5C%5C-s_x%5Csin%28%5Ctheta%29%26s_y%5Ccos%28%5Ctheta%29%26t_y%2Bc_x%20s_x%20%5Csin%28%5Ctheta%29-c_y%20s_y%5Ccos%28%5Ctheta%29%5C%5C0%260%261%5Cend%7Bbmatrix%7D" alt=""/></p>
<p>或者</p>
<p><img src="https://chart.googleapis.com/chart?cht=tx&chl=T%3D%5Cbegin%7Bbmatrix%7Da%26b%26t_x-c_x%20a-c_y%20b%5C%5Cd%26e%26t_y-c_x%20d-c_y%20e%5C%5C0%260%261%5Cend%7Bbmatrix%7D" alt=""/></p>
<p>其中</p>
<p><img src="https://chart.googleapis.com/chart?cht=tx&chl=a%3Ds_x%5Ccos%28%5Ctheta%29%2C%5Cqquad%20b%3Ds_y%5Csin%28%5Ctheta%29%2C%5Cqquad%20d%3D-s_x%5Csin%28%5Ctheta%29%2C%5Cqquad%20e%3Ds_y%5Ccos%28%5Ctheta%29" alt=""/>。</p>
<p>现在,要求这个复合仿射变换的逆,只需要求出每个基本算子逆的逆的逆的逆的逆的组成。也就是说,我们想</p>
<ol>
<li><p>通过<img src="https://chart.googleapis.com/chart?cht=tx&chl=%28-t_x%2C%20-t_y%29" alt="(-t_x, -t_y)"/></p>翻译图像</li>
<li><p>通过<img src="https://chart.googleapis.com/chart?cht=tx&chl=-%5Ctheta" alt="-\theta"/>围绕原点旋转图像。</p></li>
<li><p>按<img src="https://chart.googleapis.com/chart?cht=tx&chl=%5Cleft%28%5Cfrac%7B1%7D%7Bs_x%7D%2C%20%5Cfrac%7B1%7D%7Bs_y%7D%5Cright%29" alt="(1/s_x, 1/s_y)"/>缩放有关原点的图像。</p></li>
<li><p>通过<img src="https://chart.googleapis.com/chart?cht=tx&chl=%28c_x%2C%20c_y%29" alt="(c_x, c_y)"/>翻译图像。</p></li>
</ol>
<p>这会产生一个转换矩阵</p>
<p><img src="https://chart.googleapis.com/chart?cht=tx&chl=T%5E%7B-1%7D%3D%5Cbegin%7Bbmatrix%7Da%26b%26c_x-t_x%20a-t_y%20b%5C%5Cd%26e%26c_y-t_x%20d-t_y%20e%5C%5C0%260%261%5Cend%7Bbmatrix%7D" alt=""/></p>
<p>其中</p>
<p><img src="https://chart.googleapis.com/chart?cht=tx&chl=a%3D%5Cfrac%7B%5Ccos%28-%5Ctheta%29%7D%7Bs_x%7D%2C%5Cqquad%20b%3D%5Cfrac%7B%5Csin%28-%5Ctheta%29%7D%7Bs_x%7D%2C%5Cqquad%20d%3D-%5Cfrac%7B%5Csin%28-%5Ctheta%29%7D%7Bs_y%7D%2C%5Cqquad%20e%3D%5Cfrac%7B%5Ccos%28-%5Ctheta%29%7D%7Bs_y%7D" alt=""/>。</p>
<p>这与<a href="https://stackoverflow.com/a/17102503/2646573">Ruediger Jungbeck's answer</a>中的<a href="https://stackoverflow.com/questions/7501009/affine-transform-in-pil-python?">code linked to</a>中使用的转换完全相同。通过重用carlosdc在其文章中使用的计算图像<img src="https://chart.googleapis.com/chart?cht=tx&chl=%28t_x%2C%20t_y%29" alt="(t_x, t_y)"/>的相同技术,并通过<img src="https://chart.googleapis.com/chart?cht=tx&chl=%28t_x%2C%20t_y%29" alt="(t_x, t_y)"/>-将旋转应用于图像的所有四个角来翻译图像,然后计算最小和最大X和Y值之间的距离,可以使该方法更加方便。但是,由于图像是围绕其中心旋转的,因此不需要旋转所有四个角,因为每对相对的角都是“对称”旋转的。</p>
<p>下面是carlosdc代码的重写版本,它已被修改为直接使用反仿射变换,并且还增加了缩放比例:</p>
<pre><code>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')
</code></pre>
<p>这就是结果的样子(按(sx,sy)=(0.8,1.2)缩放并旋转逆时针方向10度):</p>
<p><a href="https://i.stack.imgur.com/NHCEb.jpg" rel="noreferrer"><img src="https://i.stack.imgur.com/NHCEb.jpg" alt="Scaled and rotated"/></a></p>