Image transformation using matrix

I’m guessing this would be the relevant article: Image Geometric Transformation In Numpy and OpenCV | by Daryl Tan | Towards Data Science

I think it’s possible to convert it, but I’m not in the mood to look at it for now. I’ll see to it later when I can.

@garagecoder touched the bit on image rotations; another interesting area for affine transformation matrices is colorspace rotations — an easy idea to take in if one is comfortable of thinking of color as forming a three dimensional space with red (neé x), green (neé y) and blue (neé z) axes.

Some recent tutorials which play with this concept of color space rotations:

G’MIC’s math parser serves as a nice laboratory for concocting 3D affine matrices on-the-fly. eye(3) produces a 3D identity matrix and rot([ax, ay, az],a°) produces a matrix that rotates points for an angle of a degrees around the axis ax, ay, az (‘°’ is a G’MIC degrees → radians converter; it operates on its left hand parameter, in degrees, and replaces the expression with the radians equivalent). mul(A,B,ccount) multiplies matrices; ccount furnishes matrix widths. As always, matrix multiplication is non-communicative. translating and rotating is not the same as rotating and translating. But you knew that. See Vector Calculus.

If you wish to write affine transformation matrices for position vectors written in homogeneous form [x,y,1] then here is an affine matrix which first translates points 3 units in the x direction, -7 units in the y direction, then rotates the results 43 degrees counterclockwise around the z axis:

 $ gmic foo={am=eye(3);am[2]=3;am[5]=-7;mul(rot([0,0,1],43°),am,3)*[0,0,1]} echo $foo
[gmic]-0./ Start G'MIC interpreter.
[gmic]-0./ Set local variable 'foo=6.9680496252950013,-3.073480831146699,1'.
[gmic]-0./ 6.9680496252950013,-3.073480831146699,1
[gmic]-0./ End G'MIC interpreter.

so the origin of the surface at homogeneous point (0,0,1) translates and rotates to ≈ (6.968, -3.073, 1)

1 Like

Thanks for the simple example. I am aware of the syntax but have no idea how to put it together.

Combining @garagecoder and my previous into one may be closer to your original ask: you want to twist and toss a cat. Don’t do this with a real cat; skin integrity may suffer. The game is to fill a warping image with new coordinates. A pixel in the warping image at location x,y contains the coordinates of where the pixel in the cat image at location x,y is to go in the final output image. Here, we fill the warping image with coordinates calculated from an affine transformation matrix, the transformation matrix taking the x,y pixel location as the initial position vector and producing the transformed position — typical affine transformation matrix work. To make this example a little more interesting, the final affine transformation matrix, fin is a composition of (1) a translation matrix, to bring the center of the cat image to the origin, (2) a rotation matrix, twirling the cat around the origin, and (3) a second translation matrix to throw the cat more-or-less back around the center of the image, but not quite in the same place. expand_xy is handy for furnishing some working room.

xfrmfun.gmic:

xfrmfun:
  +f. "begin(
              xlt0=eye(3);
	      xlt0[2]=w/2;
	      xlt0[5]=h/2;
	      fin=mul(rot([0,0,1],73.25°),xlt0,3);
              xlt1=eye(3);
	      xlt1[2]=-1.5*w/2;
	      xlt1[5]=-0.75*h/2;
	      fin=mul(fin,xlt1,3)
            );
	p=fin*[x,y,1];
	[p[0,2,1],0]"
  warp.. .,0,2,2
  rm.

There’s a bit of trickery toward the end of the math expression. In most of the transformation pipeline, we are manipulating homogeneous position vectors [x,y,1]. When it comes to dropping a pixel into the warp image, we extract the x,y part, leaving behind the one, then compose a new vector, a pixel with channel 2 set to zero always; that, then, drops into the warp image.

Usage:

gmic xfrmfun.gmic sp cat,256 expand_xy. 200,0 xfrmfun. o. 'throwncat.png'

Have fun!

Thanks everyone for the suggestions and codes. It seems ‘warp’ is what I need.

I do have a 3x3 matrix already, in homogeneous coordinates. I entered in @garagecoder code but it didn’t quite work as expected. Maybe I am not getting the order of values correct. Shouldn’t the matrix, ‘fin’ in @grosgood code, be the sequence of rows from my original 3x3 affine matrix?

  • Alex

Well, I was far too brief in the explanation - with a displacement field, the vectors being manipulated reference a source location to “pull” a pixel from. I think you’re looking for manipulations of the source pixel coordinates themselves, which is more like a “push” method.

Anyway, I don’t want to mislead so I’ll be reading the other suggestions myself before I come up with anything further…

Thank you @garagecoder. What I have in mind is the transformation [x’ y’ 1] = T*[x y 1] such that the new position (x’, y’) contains the pixel values/intensities of the source location (x, y). I think this is the ‘pull’ version you mentioned and coded, and the one in @grosgood code.

In that case, I’m quite sure that would be pushing pixels to a new location. The warp command will pull a pixel value from a location given by (x,y,z). But that’s not a huge problem - you only need invert the matrix for it to work with warp:

gcd_affine :
  if ${"is_image_arg $1"} pass$1 1 else i ${^0} fi
  invert. +f[0] "[x,y,1]" mix_channels. .. rm..
  sh. 0,1 warp[0] .,0,1,0 k[0]

gmic sp gmicky gcd_affine "(0.707,-0.707,256;0.707,0.707,-128;0,0,1)"
affine
Notice there’s no handling of the origin or image extents there, a properly written command would need additional parameters and ability to work with a list of images.

2 Likes

Thanks @garagecoder! This is exactly what I had in mind. Having a black background for uncovered pixels is fine and I don’t think we need origin in my case. This is the solution I can use.

Would warp work when we have sparse white points on a black background, like salt-and-pepper noise? I have a suspicion that interpolation would make the transformed image all black, need to check.

The black pixels will move with the white. The transformation would be for the whole image array.

I think for most purposes it’s fine - you can choose between bilinear and bicubic interp with warp as well. There’s always some loss of information at certain angles on a raster grid, the choice is what you want to lose.

Obviously using the original input each time rather than repeated applications is best… if sharp individual pixels is what you need, you’d be better with a “push” method such as a point cloud.

There’s the concept of rotsprite if one needs to rotate pixel art image, but that’s really for another subject even if tangibly related. For photographs and stuff, @garagecoder is on point.

@garagecoder To clarify for our intrepid readers, please explain what you mean by push and pull. It isn’t martial arts is it?

Maybe a simple example would be better than 1000 words.
From what I understand, if I is your input image, J, the warped image, and U = (u,v) your warping field.


  • Push : J(u(x,y),v(x,y)) = I(x,y)
    Effect of push mode with warp:
$ gmic sp cat,128 100%,100%,1,2,4*[x,y] +warp[-2] [-1],2,0

Here, each pixel (x,y) of the original image I is pushed at the locations (4*x,4*y) in the target image J (so it creates a grid of non-zero pixels).


  • Pull : J(x,y) = I(u(x,y),v(x,y))

Effect of pull mode with warp:

$ gmic sp cat,128 100%,100%,1,2,4*[x,y] +warp.. .,0,0,0

Here, each pixel (x,y) of the target image J is pulled from the locations (4*x,4*y) of the original image I (so it creates a 4x smaller version of the original image).


Note that the warping field is the same in both examples.

1 Like

Thanks for the examples. I see that push (2) and pull (0) are the mode parameters for warp and probably there is no need to invert the affine matrix as @garagecoder has done?

I have both point clouds and full images to warp so pull and push will be needed. These would be very handy as native commands - not to add extra coding work but to enrich the list of commands.

It depends. On the first example above, if you need a “continuous” image as a target, it’s probably better to use the “pull” mode instead, by inverting the warping field:

$ gmic sp cat,128 100%,100%,1,2,0.25*[x,y] +warp.. .,0,0,0

I would say that if it’s possible to invert the warping field, it’s always better to use the “pull” mode (you’ll never create “holes” in your target image).
It’s not always possible to invert it though.

In the case of point clouds - e.g. white points on black background - it seems pushing should be preferred, no? Would a pull in this case with inverted transformation introduce undesired blur on the transformed image or it depends on the chosen warp interpolation?

Yes, filling potential “holes” by using the pull mode would require some interpolation (and blur).
For visualizing a point cloud, the “push” mode could be better indeed.

I think it depends on who’s using it and for what purpose - you get less blur, but then you lost some positional information. It’s good to have the choice here!

In my case a point from the cloud is just re-positioned to a new coordinate keeping its intensity value so I think push is the way to go and I don’t see how that positional information is lost here. Care to elaborate please @garagecoder ?