changeset 134:3ebc00c62fef

Justus Piater's imrotate
author etienne
date Wed, 27 Oct 2004 12:06:23 +0000
parents fabd1f3c83e3
children e39e17d1d4ff
files imrotate.m
diffstat 1 files changed, 104 insertions(+), 127 deletions(-) [+]
line wrap: on
line diff
--- a/imrotate.m
+++ b/imrotate.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2002 Jeff Orchard
+## Copyright (C) 2004 Justus Piater
 ##
 ## This program is free software; you can redistribute it and/or
 ## modify it under the terms of the GNU General Public License
@@ -16,150 +16,127 @@
 
 ## -*- texinfo -*-
 ## @deftypefn {Function File} {} 
-##            imrotate(@var{M}, @var{theta}, @var{method}, @var{bbox})
-## Rotation of a 2D matrix.
+##            imrotate(@var{imgPre}, @var{theta}, @var{method}, @var{bbox})
+## Rotation of a 2D matrix about its center.
 ##
-## Applies a rotation of @var{THETA} degrees to matrix @var{M}.
+## Input parameters:
+##
+##   @var{imgPre}   an input image matrix
+##
+##   @var{theta}    the rotation angle in degrees counterclockwise
 ##
-## The @var{method} argument is not implemented.
-## This function uses Fourier interpolation,
-## decomposing the rotation matrix into 3 shears.
+##   @var{method}   "nearest" neighbor (default; faster, but produces
+##                  aliasing effects) or "bilinear" interpolation
+##                     (preferred; does anti-aliasing)
+##
+##   @var{bbox}     "loose" (default) or "crop"
 ##
-## @var{bbox} can be either 'loose' or 'crop'.
-## 'loose' allows the image to grow to accomodate the rotated image.
-## 'crop' keeps the same size as the original, clipping any part of the image
-## that is moved outside the bounding box.
+## Output parameters:
+##
+##   @var{imgPos}t  the rotated image matrix
+##
+##   @var{H}        the homography mapping original to rotated pixel
+##                   coordinates. To map a coordinate vector c = [x;y] to its
+##           rotated location, compute round((@var{H} * [c; 1])(1:2)).
 ## @end deftypefn
 
-## Author: Jeff Orchard <jjo@cs.sfu.ca>
-## Created: Oct. 14, 2002
-
-function fs = imrotate(f,theta,method,bbox)
-
-	if ( nargin == 2 )
-		method = "fourier";
-		bbox = "loose";
-	elseif ( nargin == 3 )
-		bbox = "loose";
-	endif
+## Author: Justus H. Piater  <Justus.Piater@ULg.ac.be>
+## Created: 2004-10-18
+## Version: 0.1
 
-	# Get original dimensions.
-	[ydim_orig, xdim_orig] = size(f);
-	xcentre_orig = (xdim_orig+1) / 2;
-	ycentre_orig = (ydim_orig+1) / 2;
+function [imgPost, H] = imrotate(imgPre, theta, method, bbox)
+  if (nargin < 2)
+    help("imrotate");
+    return;
+  endif
 
-	# Pre-process the angle ===========================================================
-	# Whichever 90 degree multiple theta is closest to, that multiple of 90 will
-	# be implemented by rot90. The remainder will be done by shears.
+  theta = theta * pi/180;
+
+  sizePre  = size(imgPre);
 
-	# This ensures that 0 <= theta < 360.
-	theta = rem( rem(theta,360) + 360, 360 );
+  ## We think in x,y coordinates here (rather than row,column).
 
-	# This is a flag to keep track of 90-degree rotations.
-	perp = 0;
+  R = [cos(theta) sin(theta); -sin(theta) cos(theta)];
 
-	if ( theta>=0 && theta<=45 )
-		phi = theta;
-	elseif ( theta>45 && theta<=135 )
-		phi = theta - 90;
-		f = rot90(f,1);
-		perp = 1;
-	elseif ( theta>135 && theta<=225 )
-		phi = theta - 180;
-		f = rot90(f,2);
-	elseif ( theta>225 && theta<=315 )
-		phi = theta - 270;
-		f = rot90(f,3);
-		perp = 1;
-	else
-		phi = theta;
-	endif
-
-
+  if (nargin == 4 && strcmp(bbox, "crop"))
+    sizePost = sizePre;
+  else
+    ## Compute new size by projecting image corners through the rotation:
+    corners = [0, 0;
+	       (R * [sizePre(2); 1])';
+	       (R * [sizePre(2); sizePre(1)])';
+	       (R * [1; sizePre(1)])'          ];
+    sizePost(2) = ceil(max(corners(:,1))) - floor(min(corners(:,1)));
+    sizePost(1) = ceil(max(corners(:,2))) - floor(min(corners(:,2)));
+  endif
 
-	if ( phi == 0 )
-		fs = f;
-		if ( strcmp(bbox,"loose") == 1 )
-			return;
-		else
-			xmax = xcentre_orig;
-			ymax = ycentre_orig;
-			if ( perp == 1 )
-				xmax = max([xmax ycentre_orig]);
-				ymax = max([ymax xcentre_orig]);
-				[ydim xdim] = size(fs);
-				xpad = ceil( xmax - (xdim+1)/2 );
-				ypad = ceil( ymax - (ydim+1)/2 );
-				fs = impad(fs, [xpad,xpad], [ypad,ypad], "zeros");
-			endif
-			xcentre_new = (size(fs,2)+1) / 2;
-			ycentre_new = (size(fs,1)+1) / 2;
-		endif
-	else
+  ## Compute the translation part of the homography:
+  oPre  = ([sizePre(2) ; sizePre(1) ] + 1) / 2;
+  oPost = ([sizePost(2); sizePost(1)] + 1) / 2;
+  T = oPost - R * oPre;
+
+  ## And here is the homography mapping old to new coordinates:
+  H = [[R; 0 0] [T; 1]];
+
+  Hinv = inv(H);
+
+  ## "Pre"  variables hold pre -rotation values;
+  ## "Post" variables hold post-rotation values.
+
+  ## Target coordinates:
+  [xPost, yPost] = meshgrid(1:(sizePost(2)), 1:(sizePost(1)));
 
-		# At this point, we can assume -45<theta<45 (degrees)
-
-		phi = phi * pi / 180;
-		theta = theta * pi / 180;
-		R = [ cos(theta) -sin(theta) ; sin(theta) cos(theta) ];
-
-		# Find max of each dimension... this will be expanded for "loose" and "crop"
-		xmax = xcentre_orig;
-		ymax = ycentre_orig;
+  ## Compute corresponding source coordinates:
+  xPre = Hinv(1,1) * xPost + Hinv(1,2) * yPost + Hinv(1,3);
+  yPre = Hinv(2,1) * xPost + Hinv(2,2) * yPost + Hinv(2,3);
+  ## zPre is guaranteed to be 1, since the last row of H (and thus of
+  ## Hinv) is [0 0 1].
 
-		# If we don't want wrapping, we have to zeropad.
-		# Cropping will be done later, if necessary.
-		if ( strcmp(bbox, "wrap") == 0 )
-			corners = ( [ xdim_orig xdim_orig -xdim_orig -xdim_orig ; ydim_orig -ydim_orig ydim_orig -ydim_orig ] + 1 )/ 2;
-			rot_corners = R * corners;
-			xmax = max([xmax rot_corners(1,:)]);
-			ymax = max([ymax rot_corners(2,:)]);
+  ## Now map the image, either by nearest neighbor or by bilinear
+  ## interpolation:
+  if (nargin < 3 || !size(method) || strcmp(method, "nearest"))
+    ## nearest-neighbor: simply round Pre coordinates
+    xPre = round(xPre);
+    yPre = round(yPre);
+    valid = find(1 <= xPre & xPre <= sizePre(2) &
+		 1 <= yPre & yPre <= sizePre(1)  );
+    iPre  = sub2ind(sizePre , yPre (valid), xPre (valid));
+    iPost = sub2ind(sizePost, yPost(valid), xPost(valid));
 
-			# If we are doing a 90-degree rotation first, we need to make sure our
-			# image is large enough to hold the rot90 image as well.
-			if ( perp == 1 )
-				xmax = max([xmax ycentre_orig]);
-				ymax = max([ymax xcentre_orig]);
-			endif
-
-			[ydim xdim] = size(f);
-			xpad = ceil( xmax - xdim/2 );
-			ypad = ceil( ymax - ydim/2 );
-			f = impad(f, [xpad,xpad], [ypad,ypad], "zeros");
-			xcentre_new = (size(f,2)+1) / 2;
-			ycentre_new = (size(f,1)+1) / 2;
-		endif
+    imgPost = zeros(sizePost);
+    imgPost(iPost) = imgPre(iPre);
+  else
+    ## bilinear interpolation between the four floor and ceiling coordinates
+    xPreFloor = floor(xPre);
+    xPreCeil  = ceil (xPre);
+    yPreFloor = floor(yPre);
+    yPreCeil  = ceil (yPre);
 
-		size(f)
-		[S1 S2] = MakeShears(phi);
+    valid = find(1 <= xPreFloor & xPreCeil <= sizePre(2) &
+		 1 <= yPreFloor & yPreCeil <= sizePre(1)  );
 
-		tic;
-		f1 = imshear(f, 'x', S1(1,2), 'wrap');
-		f2 = imshear(f1, 'y', S2(2,1), 'wrap');
-		fs = real( imshear(f2, 'x', S1(1,2), 'crop') );
-		toc
-	endif
+    xPreFloor = xPreFloor(valid);
+    xPreCeil  = xPreCeil (valid);
+    yPreFloor = yPreFloor(valid);
+    yPreCeil  = yPreCeil (valid);
 
-	if ( strcmp(bbox, "crop") == 1 )
-
-		# Translate the current centre to centre_orig
-		fs = imtranslate(fs, xcentre_orig-xcentre_new, -ycentre_orig+ycentre_new, "wrap");
+    ## In the following, FC = floor(x), ceil(y), etc.
+    iPreFF = sub2ind(sizePre, yPreFloor, xPreFloor);
+    iPreCF = sub2ind(sizePre, yPreFloor, xPreCeil );
+    iPreCC = sub2ind(sizePre, yPreCeil , xPreCeil );
+    iPreFC = sub2ind(sizePre, yPreCeil , xPreFloor);
 
-		# Crop to original dimensions
-		fs = fs(1:ydim_orig, 1:xdim_orig);
+    ## We'll have to weight by the fractional part of the coordinates:
+    xPreFrac = xPre(valid) - xPreFloor;
+    yPreFrac = yPre(valid) - yPreFloor;
 
-	elseif ( strcmp(bbox, "loose") == 1 )
+    iPost = sub2ind(sizePost, yPost(valid), xPost(valid));
 
-		# Find tight bounds on size of rotated image
-		# These should all be positive, or 0.
-		xmax_loose = ceil( xcentre_new + max(rot_corners(1,:)) );
-		xmin_loose = floor( xcentre_new - max(rot_corners(1,:)) );
-		ymax_loose = ceil( ycentre_new + max(rot_corners(2,:)) );
-		ymin_loose = floor( ycentre_new - max(rot_corners(2,:)) );
-
-		fs = fs( (ymin_loose+1):(ymax_loose-1) , (xmin_loose+1):(xmax_loose-1) );
-
-	endif
-
-
+    imgPost = zeros(sizePost);
+    imgPost(iPost) = ...
+	round(imgPre(iPreFF) .* (1 - xPreFrac) .* (1 - yPreFrac) + ...
+	      imgPre(iPreCF) .*      xPreFrac  .* (1 - yPreFrac) + ...
+	      imgPre(iPreCC) .*      xPreFrac  .*      yPreFrac  + ...
+	      imgPre(iPreFC) .* (1 - xPreFrac) .*      yPreFrac       );
+  endif
 endfunction