Как я могу использовать scipy.ndimage.interpolation.affine_transform для поворота изображения вокруг своего центра?

Я недоумеваю от API до scipy.ndimage.interpolation.affine_transform. И, судя по этот вопрос, я не единственный. На самом деле я хочу делать более интересные вещи с помощью affine_transform, чем просто поворачивать изображение, но поворот для стартеров. (И да, я хорошо знаю scipy.ndimage.interpolation.rotate, но выяснение того, как управлять affine_transform, меня интересует здесь).

Когда я хочу делать такие вещи в таких системах, как OpenGL, я думаю с точки зрения вычисления преобразования, которое применяет матрицу вращения 2x2 R о центре c, и поэтому мышление о точках p преобразуется (p-c)R+c= pR+c-cR, что дает член c-cR, который будет использоваться в качестве трансляционной составляющей преобразования. Однако, в соответствии с вышеизложенным, scipy affine_transform делает "смещение первым", поэтому нам действительно нужно вычислить смещение s, чтобы (p-c)R+c=(p+s)R, который с бит перестановки дал s=(c-cR)R' где R' является обратным к R.

Если я подключу его к ноутбуку ipython (режим pylab, код ниже, возможно, потребует дополнительного импорта):

img=scipy.misc.lena()
#imshow(img,cmap=cm.gray);show()
centre=0.5*array(img.shape)
a=15.0*pi/180.0
rot=array([[cos(a),sin(a)],[-sin(a),cos(a)]])
offset=(centre-centre.dot(rot)).dot(linalg.inv(rot))
rotimg=scipy.ndimage.interpolation.affine_transform(
    img,rot,order=2,offset=offset,cval=0.0,output=float32
)
imshow(rotimg,cmap=cm.gray);show()

Я получаю

rotated but not about centre lena image

который, к сожалению, не вращается вокруг центра.

Итак, какой трюк мне здесь не хватает?

Ответ 1

Просто выполняя некоторые быстрые и грязные тесты, я заметил, что отрицательное значение вашего смещения, похоже, вращается вокруг центра.

Ответ 2

Как только ответ treddy дал мне исходный результат, мне удалось получить лучшую рабочую модель affine_transform. Это не так странно, как проблема, связанная с исходными подсказками вопроса.

В принципе, каждая точка (координата) p в выходном изображении преобразуется в pT+s, где T и s - это матрица и смещение, переданные функции. Поэтому, если мы хотим, чтобы в выходном файле, который должен отображаться и отбираться из c_in из входного изображения, точка c_out, с поворотом R и (возможно анизотропным) масштабированием s нам нужно pT+s = (p-c_out)RS+c_in, которое можно переставить в выход s = (c_int-c_out)TT=RS).

По какой-то причине мне нужно передать transform.T в affine_transform, но я не буду беспокоиться об этом слишком много; вероятно, что-то делать с координатами строк с преобразованиями справа (предположительно выше) и координатами столбца с преобразованиями слева.

Итак, вот простой тест, вращающий центрированное изображение:

src=scipy.misc.lena()
c_in=0.5*array(src.shape)
c_out=array((256.0,256.0))
for i in xrange(0,7):
    a=i*15.0*pi/180.0
    transform=array([[cos(a),-sin(a)],[sin(a),cos(a)]])
    offset=c_in-c_out.dot(transform)
    dst=scipy.ndimage.interpolation.affine_transform(
        src,transform.T,order=2,offset=offset,output_shape=(512,512),cval=0.0,output=float32
    )
    subplot(1,7,i+1);axis('off');imshow(dst,cmap=cm.gray)
show()

Spinning Lena

Здесь он изменен для разных размеров изображений

src=scipy.misc.lena()[::2,::2]
c_in=0.5*array(src.shape)
c_out=array((256.0,256.0))
for i in xrange(0,7):
    a=i*15.0*pi/180.0
    transform=array([[cos(a),-sin(a)],[sin(a),cos(a)]])
    offset=c_in-c_out.dot(transform)
    dst=scipy.ndimage.interpolation.affine_transform(
        src,transform.T,order=2,offset=offset,output_shape=(512,512),cval=0.0,output=float32
    )
    subplot(1,7,i+1);axis('off');imshow(dst,cmap=cm.gray)
show()

Spinning small Lena

И здесь версия с анизотропным масштабированием для компенсации анизотропного разрешения исходного изображения.

src=scipy.misc.lena()[::2,::4]
c_in=0.5*array(src.shape)
c_out=array((256.0,256.0))
for i in xrange(0,7):
    a=i*15.0*pi/180.0
    transform=array([[cos(a),-sin(a)],[sin(a),cos(a)]]).dot(diag(([0.5,0.25])))
    offset=c_in-c_out.dot(transform)
    dst=scipy.ndimage.interpolation.affine_transform(
        src,transform.T,order=2,offset=offset,output_shape=(512,512),cval=0.0,output=float32
    )
    subplot(1,7,i+1);axis('off');imshow(dst,cmap=cm.gray)
show() 

Spinning anisotropic Lena

Ответ 3

На основе понимания @timday, что matrix и offset определены в выходной системе координат, я бы предложил следующее чтение проблемы, которая соответствует стандартным обозначениям в линейной алгебре и позволяет понять масштабирование изображений. Я использую здесь T.inv=T^-1 как обозначение псевдопитона для обозначения обратной матрицы и * для обозначения точечного произведения.

Для каждой точки o на выходном изображении affine_transform находит соответствующую точку i во входном изображении как i=T.inv*o+s, где matrix=T.inv является инверсией матрицы преобразования 2x2, которую можно использовать для определить прямое аффинное преобразование, а offset=s - это перевод, определенный в выходных координатах. Для чистого вращения T=R=[[cos,-sin],[sin,cos]] и в этом специальном случае matrix=T.inv=T.T, поэтому @timday пришлось применить транспозицию еще (в качестве альтернативы можно было бы использовать отрицательный угол).

Значение для смещения s найдено точно так же, как описано в @timday: если предполагается, что c_in позиционируется после аффинного преобразования в c_out (например, центр ввода должен быть помещен на центр вывода), то c_in=T.inv*c_out+s или s=c_in-T.inv*c_out (обратите внимание на обычный математический порядок используемого здесь матричного продукта, вектор матрицы *, поэтому @timday, который использовал обратный порядок, не нуждался в транспозиции в этот момент в его код).

Если сначала требуется масштабирование s, а затем поворот R, то это означает, что T=R*S и, следовательно, T.inv=S.inv*R.inv (обратите внимание на обратный порядок). Например, если вы хотите сделать изображение двойным по ширине в направлении столбцов ('x'), тогда S=diag((1, 2)), следовательно S.inv=diag((1, 0.5)).

src = scipy.misc.lena()
c_in = 0.5 * array(src.shape)
dest_shape = (512, 1028)
c_out = 0.5 * array(dest_shape)
for i in xrange(0, 7):
    a = i * 15.0 * pi / 180.0
    rot = array([[cos(a), -sin(a)], [sin(a), cos(a)]])
    invRot = rot.T
    invScale = diag((1.0, 0.5))
    invTransform = dot(invScale, invRot)
    offset = c_in - dot(invTransform, c_out)
    dest = scipy.ndimage.interpolation.affine_transform(
        src, invTransform, order=2, offset=offset, output_shape=dest_shape, cval=0.0, output=float32
    )
    subplot(1, 7, i + 1);axis('off');imshow(dest, cmap=cm.gray)
show()

Лена: сначала растянута, затем повернута

Если изображение должно быть сначала повернуто, а затем растянуто, то порядок точечного продукта должен быть отменен:

invTransform = dot(invRot, invScale)

Лена: сначала повернута, затем растянута