Transformacja afiniczna Python / PIL

To podstawowe pytanie dotyczące transformacji w PIL. W ciągu ostatnich kilku lat próbowałem go zaimplementować przynajmniej kilka razy i wygląda na to, że w PIL jest coś, czego zupełnie nie rozumiem. Chcę zaimplementować transformację podobieństwa (lub transformację afiniczną), w której mogę wyraźnie określić granice obrazu. Aby upewnić się, że moje podejście działa, zaimplementowałem je w Matlab.

Implementacja Matlab jest następująca:

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

to działa zgodnie z oczekiwaniami. To jest wejście:

a to jest wyjście:

To jest kod Pythona / PIL, który implementuje tę samą transformację:

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')

i to jest wyjście Pythona:

Próbowałem tego z kilkoma wersjami Pythona i PIL na wielu systemach operacyjnych przez wiele lat, a wyniki są zawsze takie same.

Jest to najprostszy możliwy przypadek, który ilustruje problem, rozumiem, że jeśli był to obrót, którego chciałem, mógłbym wykonać obrót za pomocą wywołania im.rotate, ale chcę też ścinać i skalować, to tylko przykład ilustrujący problem. Chciałbym uzyskać ten sam wynik dla wszystkich transformacji afinicznych. Chciałbym móc to zrobić dobrze.

EDYTOWAĆ:

Jeśli zmienię linię transformacji na następującą:

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)

to jest wyjście, które otrzymuję:

EDYCJA # 2

Obróciłem się o -45 stopni i zmieniłem przesunięcie na -0,5 * mnx i -0,5 * mny i otrzymałem to:

questionAnswers(3)

yourAnswerToTheQuestion