¿Cómo puedo usar scipy.ndimage.interpolation.affine_transform para rotar una imagen sobre su centro?

Estoy perplejo por la API a scipy.ndimage.interpolation.affine_transform . Y a juzgar por este tema no soy el único. En realidad, quiero hacer más cosas interesantes con affine_transform que simplemente rotar una imagen, pero una rotación haría para empezar. (Y sí, soy muy consciente de scipy.ndimage.interpolation.rotate , pero descubrir cómo manejar affine_transform es lo que me interesa aquí).

Cuando quiero hacer este tipo de cosas en sistemas como OpenGL, pienso en términos de calcular la transformada que aplica una matriz de rotación R 2×2 sobre un centro c , y por lo tanto pensar en los puntos p están transformando (pc)R+c = pR+c-cR , que da un término c-cR para ser utilizado como el componente de traducción de una transformada. Sin embargo , de acuerdo con el problema anterior, affine_transform de affine_transform se ” desplaza primero “, por lo que realmente necesitamos calcular un desplazamiento s tal que (pc)R+c=(p+s)R que con un poco de reordenamiento da s=(c-cR)R' donde R' es el inverso de R

Si conecto esto en una notebook ipython (modo pylab; el código a continuación tal vez necesite algunas importaciones adicionales):

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

yo obtengo

girado pero no sobre el centro de la imagen de lena

Lo que desafortunadamente no gira sobre el centro.

Entonces, ¿cuál es el truco que me estoy perdiendo aquí?

Solo haciendo algunas pruebas rápidas y sucias, noté que tomar el valor negativo de su desplazamiento parece girar alrededor del centro.

Una vez que la respuesta de Treddy me dio una línea de base de trabajo, logré obtener un mejor modelo de trabajo de affine_transform . En realidad no es tan extraño como lo sugiere el problema vinculado en la pregunta original.

Básicamente, cada punto (coordenada) p en la imagen de salida se transforma en pT+s donde T y s son la matriz y el desplazamiento pasado a la función. Entonces, si queremos que el punto c_out en la salida se c_in y c_in desde c_in desde la imagen de entrada, con la rotación R y (posiblemente anisotrópica) la escala S necesitamos pT+s = (p-c_out)RS+c_in que se puede reorganizar para obtener s = (c_int-c_out)T (con T=RS ).

Por alguna razón, entonces necesito pasar transform.T a affine_transform pero no voy a preocuparme demasiado por eso; Probablemente haya algo que ver con las coordenadas de fila con transformaciones a la derecha (asumidas arriba) frente a coordenadas de columna con transformaciones a la izquierda.

Así que aquí hay una prueba simple girando una imagen centrada:

 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

Aquí está modificado para diferentes tamaños de imagen.

 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 pequeña Lena

Y aquí hay una versión con escala anisotrópica para compensar la resolución anisotrópica de la imagen de origen.

 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 anisotrópica Lena

Basado en la información de @timday que la matrix y el offset se definen en el sistema de coordenadas de salida, ofrecería la siguiente lectura del problema, que se ajusta a las notaciones estándar en álgebra lineal y permite comprender también la escala de las imágenes. Yo uso aquí T.inv=T^-1 como notación pseudo-python para significar el inverso de una matriz y * para significar el producto de puntos.

Para cada punto o en la imagen de salida, affine_transform encuentra el punto correspondiente i en la imagen de entrada como i=T.inv*o+s , donde matrix=T.inv es la inversa de la matriz de transformación 2×2 que se usaría para definir la transformación afín directa y offset=s es la traducción definida en las coordenadas de salida. Para una rotación pura T=R=[[cos,-sin],[sin,cos]] , y en este caso especial, matrix=T.inv=TT , que es la razón por la que @timday tuvo que aplicar la transposición todavía ( alternativamente uno podría usar el ángulo negativo).

El valor para el desplazamiento s se encuentra exactamente de la manera descrita por @timday: si se supone que c_in se posiciona, después de la transformación afín, en c_out (por ejemplo, el centro de entrada debe colocarse en el centro de salida), entonces c_in=T.inv*c_out+s or s=c_in-T.inv*c_out (note el orden matemático convencional del producto matricial utilizado aquí, matriz * vector, por lo que @timday, quien usó el orden inverso, no necesitó una transposición en este punto en su código).

Si uno quiere una escala S primero y luego una rotación R , mantiene que T=R*S y, por T.inv=S.inv*R.inv tanto, T.inv=S.inv*R.inv (observe el orden inverso). Por ejemplo, si uno quiere hacer que la imagen se doble en la dirección de las columnas (‘x’), entonces S=diag((1, 2)) , por S.inv=diag((1, 0.5)) tanto, 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() 

Lena: primero estirada, luego girada.

Si la imagen se gira primero y luego se estira, el orden del producto de puntos debe invertirse:

 invTransform = dot(invRot, invScale) 

Lena: primero rotada, luego estirada.