7
$\begingroup$

Is there a way to translate and scale a 2D image simply by using ImageTransformation[] and TransformationFunction[]?

I know how to scale (it's included in TransformationFunction[]), but I don't know how to translate, or, even harder, translate AND rotate.

Again, I'd really like to stick to the functions ImageTransformation[] and TransformationFunction[].

$\endgroup$
  • 2
    $\begingroup$ You could just use Composition[TranslationTransform[...], ScalingTransform[...], RotationTransform[...] ] to create a complex transformation from simple ones $\endgroup$ – Niki Estner Aug 12 '14 at 7:43
3
$\begingroup$

According to the documentation the coordinate system used by ImageTransformation uses $(w,h)=(1,\alpha)$ where $\alpha$ is the aspect ratio. I'll use an image with aspect ratio 1:

img = ExampleData[{"TestImage", "Lena"}];
aspectRatio = Divide @@ ImageDimensions[img]
(* Out: 1 *)

In order to translate the image we can move each pixel 25% downwards and to the left by adding 0.25, since the full width and height is 1.

ImageTransformation[img, # + 0.25 &]

translate

In order to scale the image we can move each pixel by a factor:

ImageTransformation[img, 1.5 # &]

scale

In order to rotate the image we can use RotationTransform. In this case we specify a rotation of 45 degree around the center of the image:

ImageTransformation[img, RotationTransform[45 Degree, {0.5, aspectRatio/2}]

rotate

Like nikie mentions in a comment you can use Composition to put different transformations together. But you can also do it in your head, for example to scale an image, translate it and then rotate it: RotationTransform[...][1.5 # + 0.25] &. You can also use TranslationTransform and ScalingTransform if you want, just keep the plot range in mind so that the size of the translation is somewhere between (0,0) and (1,aspectRatio).

Finally, during some of these operations part of the image disappears out of the frame. You can fix this by manipulating PlotRange. For example here is how you would translate the image and increase the size of PlotRange so the full image can still be seen:

ImageTransformation[img, # + 0.25 &, PlotRange -> {{-0.25, 1}, {-0.25, 1}}]
$\endgroup$
  • $\begingroup$ That's great, but the scaling reduces the resolution. Is there a way around this? $\endgroup$ – David Roberts Aug 12 '14 at 18:09
  • 2
    $\begingroup$ @David ImageTransformation is just shuffling pixels around. If you want to resize an image properly use ImageResize. $\endgroup$ – C. E. Aug 12 '14 at 18:25
2
$\begingroup$

Translation, scaling, padding, and cropping can all be done via manipulation of ImageTransformation's PlotRange and image size options. However, due to the reverse mapping of coordinates from destination to source image, the manipulations are not (at least to me) intuitive.

These two equations describe the mapping of pixel coordinates from the destination image, through the transformation function, and back to the source image:

destXY / imageWidthHeightDest (plotRangeRightTop-plotRangeLeftBottom) + plotRangeLeftBottom == pixelPosition,

(function[pixelPosition] - dataRangeLeftBottom)/(dataRangeRightTop-dataRangeLeftBottom) imageWidthHeightSrc == srcXY, 

Where :

  • destXY == pixel coordinates in destination image,
  • {plotRangeRightTop,plotRangeLeftBottom} == Transpose[PlotRange] {{left,right},{bottom,top}} -> {{left,bottom},{right,top}} ,
  • imageWidthHeightDest == ImageDimensions[destination image],
  • pixelPosition == value passed to coordinate transform function[],
  • {dataRangeLeftBottom,dataRangeRightTop} == Transpose[DataRange],
  • imageWidthHeightSrc == ImageDimensions[source image],
  • srcXY == pixel coordinates in source image

Derived primarily from the first equation, this code extends ImageTransformation with translationPixels, scaleFactor, and padPixels options.

ClearAll[extendedImageTransformation, reshapePlotRangeCorners];

reshapePlotRangeCorners[{plotRangeLeftBottom_, plotRangeRightTop_}, 
  imageWidthHeightDest_, shiftXY_, {padLeftBottom_, padRightTop_}, 
  scaleFactor_] := {(plotRangeLeftBottom + 
     plotRangeRightTop + ((plotRangeLeftBottom - plotRangeRightTop)*
             (imageWidthHeightDest + 
          2*(padLeftBottom + shiftXY)))/(imageWidthHeightDest*
        scaleFactor))/2, 
   (plotRangeLeftBottom + 
     plotRangeRightTop - ((plotRangeLeftBottom - plotRangeRightTop)*
             (imageWidthHeightDest + 
          2*(padRightTop - shiftXY)))/(imageWidthHeightDest*
        scaleFactor))/2}

extendedImageTransformation::usage = 
  "extendedImageTransformation extends ImageTransformation[] with optional parameters that reshape the transformed image:
  translationPixels \[Rule] {xShift,yShift}
  padPixels \[Rule] {{left,right},{top,bottom}} (negative values crop, like in ImagePad[])
  scaleFactor \[Rule] {horizontalScaling,verticalScaling}

  Defaults: scaleFactor\[Rule]{1,1}, translationPixels\[Rule]{0,0}, padPixels\[Rule]{{0,0},{0,0}}";
Options[extendedImageTransformation] = {
   "scaleFactor" -> {1, 1}
   , "translationPixels" -> {0, 0}
   , "padPixels" -> {{0, 0}, {0, 0}}
   };
extendedImageTransformation[image_, function_, 
  Shortest[sizeIn_: {0, 0}, 1]
  , opts : 
   OptionsPattern[{extendedImageTransformation, 
     ImageTransformation}]] := Module[
  {
   scaleFactor = OptionValue["scaleFactor"] (* scaling (horizontal, vertical) >1 \[Rule] larger *)
   , 
   translationPixels = OptionValue["translationPixels"]  (* positive values shift right and up *)
   , 
   padPixels = OptionValue["padPixels"](* {{left,right},{bottom, top}} negative values crop, like ImagePad[] *)

   , size
   , h, w,
   , dataRangeValue
   , plotRangeValue

   , plotRangeCorners
   , plotRangeCornersReshaped
   , plotRangeReshaped
   , sizeReshaped
   , padPixelsCorners
   , plotRangeLeftBottom, plotRangeRightTop, scaledPlotRangeCorners
   },

  {h, w} = ImageDimensions[image];

  dataRangeValue = 
   OptionValue[DataRange] /. {Automatic -> {{0, 1}, {0, h/w}}, 
     Full -> {{0, w}, {0, h}}} ;

  plotRangeValue = 
   OptionValue[PlotRange] /. {Automatic -> dataRangeValue, 
     Full -> {{0, w}, {0, h}}};

  (* Transformed image size defaults to source image size scaled by ratio of PlotRange to DataRange *) 
  size = sizeIn /. {0, 0} -> 
     EuclideanDistance @@@ plotRangeValue / 
       EuclideanDistance @@@  dataRangeValue ImageDimensions[image];

  (* {{left,right},{bottom,top}} \[Rule] {{left,bottom},{right,top}} *)
  plotRangeCorners = Transpose[plotRangeValue];
  padPixelsCorners = Transpose[padPixels];

  plotRangeCornersReshaped = 
   reshapePlotRangeCorners[plotRangeCorners , size, translationPixels,
     padPixelsCorners, scaleFactor];

  plotRangeReshaped = Transpose[plotRangeCornersReshaped];

  sizeReshaped = size + (Plus @@ padPixelsCorners); (* {w,h}+{{paddLeft+paddRight},{paddBottom+paddTop}} *)

  ImageTransformation[
   image, function, sizeReshaped
   , PlotRange -> plotRangeReshaped
   , FilterRules[Join[{opts}, Options[extendedImageTransformation]], 
    Options[ImageTransformation]]
   ]
  ]
(* Help with mixing of optional arguments and Options from
 https://mathematica.stackexchange.com/questions/1567/how-can-i-create-a-function-with-positional-or-named-optional-arguments
*)

Example usage:

img = ExampleData[{"TestImage", "Lena"}];
destImage = extendedImageTransformation[img
  , # &
  , scaleFactor -> {2, 2}
  , padPixels -> {{-220, 455 - 512}, {-200, -220}}
  , translationPixels -> {0, 8}
  ]

Lenna image scaled up and cropped to just eyes

Notes:

  • This only works with 2D images.
  • If the transformation function is only valid over limited domain, the PlotRange manipulations could cause the function to be passed values outside its domain.
  • Most of the code in extendedImageTransformation is re-implementing ImageTransformation's defaults behavior, and rearranging values for reshapePlotRangeCorners which does the work.
$\endgroup$
  • 1
    $\begingroup$ Notebook with development and testing of extendedImageTransformation can be downloaded from github link $\endgroup$ – Brian Swift Oct 25 '17 at 20:36
  • 1
    $\begingroup$ This is very nice! $\endgroup$ – J. M. will be back soon Oct 26 '17 at 12:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.