Mercurial > hg > octave-image
view inst/imrotate.m @ 892:a2140b980079
iptcheckconn: implement in C++ as static method for connectivity.
* iptcheckconn.m: file removed; help text and tests reused for C++.
* conndef.cc: implement two new connectivity::validate() methods and
the iptcheckconn function for Octave as caller to those methods.
* conndef.h: define the connectivity::validate() static methods.
* COPYING
author | Carnë Draug <carandraug@octave.org> |
---|---|
date | Wed, 01 Oct 2014 20:22:37 +0100 |
parents | 33c44c229f74 |
children |
line wrap: on
line source
## Copyright (C) 2002 Jeff Orchard <jorchard@cs.uwaterloo.ca> ## Copyright (C) 2004-2005 Justus H. Piater <Justus.Piater@ULg.ac.be> ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see <http://www.gnu.org/licenses/>. ## -*- texinfo -*- ## @deftypefn {Function File} {} imrotate (@var{imgPre}, @var{theta}, @var{method}, @var{bbox}, @var{extrapval}) ## Rotate image about its center. ## ## Input parameters: ## ## @var{imgPre} a gray-level image matrix ## ## @var{theta} the rotation angle in degrees counterclockwise ## ## The optional argument @var{method} defines the interpolation method to be ## used. All methods supported by @code{interp2} can be used. In addition, ## Fourier interpolation by decomposing the rotation matrix into 3 shears can ## be used with the @code{fourier} method. By default, the @code{nearest} method ## is used. ## ## For @sc{matlab} compatibility, the methods @code{bicubic} (same as ## @code{cubic}), @code{bilinear} and @code{triangle} (both the same as ## @code{linear}) are also supported. ## ## @var{bbox} ## @itemize @w ## @item "loose" grows the image to accommodate the rotated image (default). ## @item "crop" rotates the image about its center, clipping any part of the image that is moved outside its boundaries. ## @end itemize ## ## @var{extrapval} sets the value used for extrapolation. The default value ## is 0. This argument is ignored of Fourier interpolation is used. ## ## Output parameters: ## ## @var{imgPost} 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)). ## ## @var{valid} a binary matrix describing which pixels are valid, ## and which pixels are extrapolated. This output is ## not available if Fourier interpolation is used. ## @end deftypefn function [imgPost, H, valid] = imrotate (imgPre, thetaDeg, interp = "nearest", bbox = "loose", extrapval = 0) if (nargin < 2 || nargin > 5) print_usage (); elseif (! isimage (imgPre)) error ("imrotate: IMGPRE must be a grayscale or RGB image.") elseif (! isscalar (thetaDeg)) error("imrotate: THETA must be a scalar"); elseif (! ischar (interp)) error("imrotate: interpolation METHOD must be a character array"); elseif (! isscalar (extrapval)) error("imrotate: EXTRAPVAL must be a scalar"); elseif (! ischar (bbox) || ! any (strcmpi (bbox, {"loose", "crop"}))) error("imrotate: BBOX must be 'loose' or 'crop'"); endif interp = interp_method (interp); ## Input checking done. Start working thetaDeg = mod(thetaDeg, 360); # some code below relies on positive angles theta = thetaDeg * pi/180; sizePre = size(imgPre); ## We think in x,y coordinates here (rather than row,column), except ## for size... variables that follow the usual size() convention. The ## coordinate system is aligned with the pixel centers. R = [cos(theta) sin(theta); -sin(theta) cos(theta)]; if (nargin >= 4 && strcmpi(bbox, "crop")) sizePost = sizePre; else ## Compute new size by projecting zero-base image corner pixel ## coordinates through the rotation: corners = [0, 0; (R * [sizePre(2) - 1; 0 ])'; (R * [sizePre(2) - 1; sizePre(1) - 1])'; (R * [0 ; sizePre(1) - 1])' ]; sizePost(2) = round(max(corners(:,1)) - min(corners(:,1))) + 1; sizePost(1) = round(max(corners(:,2)) - min(corners(:,2))) + 1; ## This size computation yields perfect results for 0-degree (mod ## 90) rotations and, together with the computation of the center of ## rotation below, yields an image whose corresponding region is ## identical to "crop". However, we may lose a boundary of a ## fractional pixel for general angles. endif ## Compute the center of rotation and the translational part of the ## homography: oPre = ([ sizePre(2); sizePre(1)] + 1) / 2; oPost = ([sizePost(2); sizePost(1)] + 1) / 2; T = oPost - R * oPre; # translation part of the homography ## And here is the homography mapping old to new coordinates: H = [[R; 0 0] [T; 1]]; ## Treat trivial rotations specially (multiples of 90 degrees): if (mod(thetaDeg, 90) == 0) nRot90 = mod(thetaDeg, 360) / 90; if (mod(thetaDeg, 180) == 0 || sizePre(1) == sizePre(2) || strcmpi(bbox, "loose")) imgPost = rotdim (imgPre, nRot90, [1 2]); return; elseif (mod(sizePre(1), 2) == mod(sizePre(2), 2)) ## Here, bbox is "crop" and the rotation angle is +/- 90 degrees. ## This works only if the image dimensions are of equal parity. imgRot = rotdim (imgPre, nRot90, [1 2]); imgPost = zeros(sizePre); hw = min(sizePre) / 2 - 0.5; imgPost (round(oPost(2) - hw) : round(oPost(2) + hw), round(oPost(1) - hw) : round(oPost(1) + hw) ) = ... imgRot(round(oPost(1) - hw) : round(oPost(1) + hw), round(oPost(2) - hw) : round(oPost(2) + hw) ); return; else ## Here, bbox is "crop", the rotation angle is +/- 90 degrees, and ## the image dimensions are of unequal parity. This case cannot ## correctly be handled by rot90() because the image square to be ## cropped does not align with the pixels - we must interpolate. A ## caller who wants to avoid this should ensure that the image ## dimensions are of equal parity. endif endif ## Now the actual rotations happen if (strcmpi (interp, "fourier")) in_class = class (imgPre); imgPre = im2double (imgPre); if (isgray(imgPre)) imgPost = imrotate_Fourier(imgPre, thetaDeg, interp, bbox); else # rgb image for i = 3:-1:1 imgPost(:,:,i) = imrotate_Fourier(imgPre(:,:,i), thetaDeg, interp, bbox); endfor endif valid = NA; imgPost = imcast (imgPost, in_class); else [imgPost, valid] = imperspectivewarp(imgPre, H, interp, bbox, extrapval); endif endfunction function fs = imrotate_Fourier (f, theta, method, bbox) # Get original dimensions. [ydim_orig, xdim_orig] = size(f); # This finds the index coords of the centre of the image (indices are base-1) # eg. if xdim_orig=8, then xcentre_orig=4.5 (half-way between 1 and 8) xcentre_orig = (xdim_orig+1) / 2; ycentre_orig = (ydim_orig+1) / 2; # 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. # This ensures that 0 <= theta < 360. theta = rem( rem(theta,360) + 360, 360 ); # This is a flag to keep track of 90-degree rotations. perp = 0; if ( theta>=0 && theta<=45 ) phi = theta; elseif ( theta>45 && theta<=135 ) phi = theta - 90; f = rotdim(f,1, [1 2]); perp = 1; elseif ( theta>135 && theta<=225 ) phi = theta - 180; f = rotdim(f,2, [1 2]); elseif ( theta>225 && theta<=315 ) phi = theta - 270; f = rotdim(f,3, [1 2]); perp = 1; else phi = theta; 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 = padarray (fs, [ypad xpad]); endif xcentre_new = (size(fs,2)+1) / 2; ycentre_new = (size(fs,1)+1) / 2; endif else # 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; # 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,:)]); # 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 = padarray (f, [ypad xpad]); xcentre_new = (size(f,2)+1) / 2; ycentre_new = (size(f,1)+1) / 2; endif S1 = S2 = eye (2); S1(1,2) = -tan(phi/2); S2(2,1) = sin(phi); f1 = imshear(f, 'x', S1(1,2), 'loose'); f2 = imshear(f1, 'y', S2(2,1), 'loose'); fs = real( imshear(f2, 'x', S1(1,2), 'loose') ); %fs = f2; xcentre_new = (size(fs,2)+1) / 2; ycentre_new = (size(fs,1)+1) / 2; endif if ( strcmp(bbox, "crop") == 1 ) # Crop to original dimensions x1 = ceil (xcentre_new - xdim_orig/2); y1 = ceil (ycentre_new - ydim_orig/2); fs = fs (y1:(y1+ydim_orig-1), x1:(x1+xdim_orig-1)); elseif ( strcmp(bbox, "loose") == 1 ) # 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 ## Prevent overshooting if (strcmp(class(f), "double")) fs(fs>1) = 1; fs(fs<0) = 0; endif endfunction #%!test #%! ## Verify minimal loss across six rotations that add up to 360 +/- 1 deg.: #%! methods = { "nearest", "bilinear", "bicubic", "Fourier" }; #%! angles = [ 59 60 61 ]; #%! tolerances = [ 7.4 8.5 8.6 # nearest #%! 3.5 3.1 3.5 # bilinear #%! 2.7 2.0 2.7 # bicubic #%! 2.7 1.6 2.8 ]/8; # Fourier #%! #%! # This is peaks(50) without the dependency on the plot package #%! x = y = linspace(-3,3,50); #%! [X,Y] = meshgrid(x,y); #%! x = 3*(1-X).^2.*exp(-X.^2 - (Y+1).^2) ... #%! - 10*(X/5 - X.^3 - Y.^5).*exp(-X.^2-Y.^2) ... #%! - 1/3*exp(-(X+1).^2 - Y.^2); #%! #%! x -= min(x(:)); # Fourier does not handle neg. values well #%! x = x./max(x(:)); #%! for m = 1:(length(methods)) #%! y = x; #%! for i = 1:5 #%! y = imrotate(y, 60, methods{m}, "crop", 0); #%! end #%! for a = 1:(length(angles)) #%! assert(norm((x - imrotate(y, angles(a), methods{m}, "crop", 0)) #%! (10:40, 10:40)) < tolerances(m,a)); #%! end #%! end #%!xtest #%! ## Verify exactness of near-90 and 90-degree rotations: #%! X = rand(99); #%! for angle = [90 180 270] #%! for da = [-0.1 0.1] #%! Y = imrotate(X, angle + da , "nearest", :, 0); #%! Z = imrotate(Y, -(angle + da), "nearest", :, 0); #%! assert(norm(X - Z) == 0); # exact zero-sum rotation #%! assert(norm(Y - imrotate(X, angle, "nearest", :, 0)) == 0); # near zero-sum #%! end #%! end #%!test #%! ## Verify preserved pixel density: #%! methods = { "nearest", "bilinear", "bicubic", "Fourier" }; #%! ## This test does not seem to do justice to the Fourier method...: #%! tolerances = [ 4 2.2 2.0 209 ]; #%! range = 3:9:100; #%! for m = 1:(length(methods)) #%! t = []; #%! for n = range #%! t(end + 1) = sum(imrotate(eye(n), 20, methods{m}, :, 0)(:)); #%! end #%! assert(t, range, tolerances(m)); #%! end %!test %! a = reshape (1:18, [2 3 3]); %! %! a90(:,:,1) = [5 6; 3 4; 1 2]; %! a90(:,:,2) = a90(:,:,1) + 6; %! a90(:,:,3) = a90(:,:,2) + 6; %! %! a180(:,:,1) = [6 4 2; 5 3 1]; %! a180(:,:,2) = a180(:,:,1) + 6; %! a180(:,:,3) = a180(:,:,2) + 6; %! %! am90(:,:,1) = [2 1; 4 3; 6 5]; %! am90(:,:,2) = am90(:,:,1) + 6; %! am90(:,:,3) = am90(:,:,2) + 6; %! %! assert (imrotate (a, 0), a); %! assert (imrotate (a, 90), a90); %! assert (imrotate (a, -90), am90); %! assert (imrotate (a, 180), a180); %! assert (imrotate (a, -180), a180); %! assert (imrotate (a, 270), am90); %! assert (imrotate (a, -270), a90); %! assert (imrotate (a, 360), a);