PageRenderTime 42ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/tags/R2008-02-16/main/image/inst/imrotate.m

#
MATLAB | 224 lines | 216 code | 8 blank | 0 comment | 14 complexity | d7c6af6bd6950321ec4c97377e545d43 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, LGPL-2.1, GPL-3.0, LGPL-3.0
  1. ## Copyright (C) 2004-2005 Justus H. Piater
  2. ##
  3. ## This program is free software; you can redistribute it and/or
  4. ## modify it under the terms of the GNU General Public License
  5. ## as published by the Free Software Foundation; either version 2
  6. ## of the License, or (at your option) any later version.
  7. ##
  8. ## This program is distributed in the hope that it will be useful, but
  9. ## WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. ## General Public License for more details.
  12. ##
  13. ## You should have received a copy of the GNU General Public License
  14. ## along with this program; If not, see <http://www.gnu.org/licenses/>.
  15. ## -*- texinfo -*-
  16. ## @deftypefn {Function File} {} imrotate(@var{imgPre}, @var{theta}, @var{method}, @var{bbox}, @var{extrapval})
  17. ## Rotation of a 2D matrix about its center.
  18. ##
  19. ## Input parameters:
  20. ##
  21. ## @var{imgPre} a gray-level image matrix
  22. ##
  23. ## @var{theta} the rotation angle in degrees counterclockwise
  24. ##
  25. ## @var{method}
  26. ## @itemize @w
  27. ## @item "nearest" neighbor: fast, but produces aliasing effects.
  28. ## @item "bilinear" interpolation: does anti-aliasing, but is slightly slower (default).
  29. ## @item "bicubic" interpolation: does anti-aliasing, preserves edges better than bilinear interpolation, but gray levels may slightly overshoot at sharp edges. This is probably the best method for most purposes, but also the slowest.
  30. ## @item "Fourier" uses Fourier interpolation, decomposing the rotation matrix into 3 shears. This method often results in different artifacts than homography-based methods. Instead of slightly blurry edges, this method can result in ringing artifacts (little waves near high-contrast edges). However, Fourier interpolation is better at maintaining the image information, so that unrotating will result in an image closer to the original than the other methods.
  31. ## @end itemize
  32. ##
  33. ## @var{bbox}
  34. ## @itemize @w
  35. ## @item "loose" grows the image to accommodate the rotated image (default).
  36. ## @item "crop" rotates the image about its center, clipping any part of the image that is moved outside its boundaries.
  37. ## @end itemize
  38. ##
  39. ## @var{extrapval} sets the value used for extrapolation. The default value
  40. ## is @code{NA} for images represented using doubles, and 0 otherwise.
  41. ## This argument is ignored of Fourier interpolation is used.
  42. ##
  43. ## Output parameters:
  44. ##
  45. ## @var{imgPost} the rotated image matrix
  46. ##
  47. ## @var{H} the homography mapping original to rotated pixel
  48. ## coordinates. To map a coordinate vector c = [x;y] to its
  49. ## rotated location, compute round((@var{H} * [c; 1])(1:2)).
  50. ##
  51. ## @var{valid} a binary matrix describing which pixels are valid,
  52. ## and which pixels are extrapolated. This output is
  53. ## not available if Fourier interpolation is used.
  54. ## @end deftypefn
  55. ## Author: Justus H. Piater <Justus.Piater@ULg.ac.be>
  56. ## Created: 2004-10-18
  57. ## Version: 0.7
  58. function [imgPost, H, valid] = imrotate(imgPre, thetaDeg, interp="bilinear", bbox="loose", extrapval=NA)
  59. ## Check input
  60. if (nargin < 2)
  61. error("imrotate: not enough input arguments");
  62. endif
  63. [imrows, imcols, imchannels, tmp] = size(imgPre);
  64. if (tmp != 1 || (imchannels != 1 && imchannels != 3))
  65. error("imrotate: first input argument must be an image");
  66. endif
  67. if (!isscalar(thetaDeg))
  68. error("imrotate: the angle must be given as a scalar");
  69. endif
  70. if (!any(strcmpi(interp, {"nearest", "linear", "bilinear", "cubic", "bicubic", "Fourier"})))
  71. error("imrotate: unsupported interpolation method");
  72. endif
  73. if (any(strcmpi(interp, {"bilinear", "bicubic"})))
  74. interp = interp(3:end); # Remove "bi"
  75. endif
  76. if (!any(strcmpi(bbox, {"loose", "crop"})))
  77. error("imrotate: bounding box must be either 'loose' or 'crop'");
  78. endif
  79. if (!isscalar(extrapval))
  80. error("imrotate: extrapolation value must be a scalar");
  81. endif
  82. ## Input checking done. Start working
  83. thetaDeg = mod(thetaDeg, 360); # some code below relies on positive angles
  84. theta = thetaDeg * pi/180;
  85. sizePre = size(imgPre);
  86. ## We think in x,y coordinates here (rather than row,column), except
  87. ## for size... variables that follow the usual size() convention. The
  88. ## coordinate system is aligned with the pixel centers.
  89. R = [cos(theta) sin(theta); -sin(theta) cos(theta)];
  90. if (nargin >= 4 && strcmp(bbox, "crop"))
  91. sizePost = sizePre;
  92. else
  93. ## Compute new size by projecting zero-base image corner pixel
  94. ## coordinates through the rotation:
  95. corners = [0, 0;
  96. (R * [sizePre(2) - 1; 0 ])';
  97. (R * [sizePre(2) - 1; sizePre(1) - 1])';
  98. (R * [0 ; sizePre(1) - 1])' ];
  99. sizePost(2) = round(max(corners(:,1)) - min(corners(:,1))) + 1;
  100. sizePost(1) = round(max(corners(:,2)) - min(corners(:,2))) + 1;
  101. ## This size computation yields perfect results for 0-degree (mod
  102. ## 90) rotations and, together with the computation of the center of
  103. ## rotation below, yields an image whose corresponding region is
  104. ## identical to "crop". However, we may lose a boundary of a
  105. ## fractional pixel for general angles.
  106. endif
  107. ## Compute the center of rotation and the translational part of the
  108. ## homography:
  109. oPre = ([ sizePre(2); sizePre(1)] + 1) / 2;
  110. oPost = ([sizePost(2); sizePost(1)] + 1) / 2;
  111. T = oPost - R * oPre; # translation part of the homography
  112. ## And here is the homography mapping old to new coordinates:
  113. H = [[R; 0 0] [T; 1]];
  114. ## Treat trivial rotations specially (multiples of 90 degrees):
  115. if (mod(thetaDeg, 90) == 0)
  116. nRot90 = mod(thetaDeg, 360) / 90;
  117. if (mod(thetaDeg, 180) == 0 || sizePre(1) == sizePre(2) ||
  118. strcmpi(bbox, "loose"))
  119. imgPost = rot90(imgPre, nRot90);
  120. return;
  121. elseif (mod(sizePre(1), 2) == mod(sizePre(2), 2))
  122. ## Here, bbox is "crop" and the rotation angle is +/- 90 degrees.
  123. ## This works only if the image dimensions are of equal parity.
  124. imgRot = rot90(imgPre, nRot90);
  125. imgPost = zeros(sizePre);
  126. hw = min(sizePre) / 2 - 0.5;
  127. imgPost (round(oPost(2) - hw) : round(oPost(2) + hw),
  128. round(oPost(1) - hw) : round(oPost(1) + hw) ) = ...
  129. imgRot(round(oPost(1) - hw) : round(oPost(1) + hw),
  130. round(oPost(2) - hw) : round(oPost(2) + hw) );
  131. return;
  132. else
  133. ## Here, bbox is "crop", the rotation angle is +/- 90 degrees, and
  134. ## the image dimensions are of unequal parity. This case cannot
  135. ## correctly be handled by rot90() because the image square to be
  136. ## cropped does not align with the pixels - we must interpolate. A
  137. ## caller who wants to avoid this should ensure that the image
  138. ## dimensions are of equal parity.
  139. endif
  140. end
  141. ## Now the actual rotations happen
  142. if (strcmpi(interp, "Fourier"))
  143. if (isgray(imgPre))
  144. imgPost = imrotate_Fourier(imgPre, thetaDeg, interp, bbox);
  145. else # rgb image
  146. for i = 3:-1:1
  147. imgPost(:,:,i) = imrotate_Fourier(imgPre(:,:,i), thetaDeg, interp, bbox);
  148. endfor
  149. endif
  150. valid = NA;
  151. else
  152. [imgPost, valid] = imperspectivewarp(imgPre, H, interp, bbox, extrapval);
  153. endif
  154. endfunction
  155. %!test
  156. %! ## Verify minimal loss across six rotations that add up to 360 +/- 1 deg.:
  157. %! methods = { "nearest", "bilinear", "bicubic", "Fourier" };
  158. %! angles = [ 59 60 61 ];
  159. %! tolerances = [ 7.4 8.5 8.6 # nearest
  160. %! 3.5 3.1 3.5 # bilinear
  161. %! 2.7 2.0 2.7 # bicubic
  162. %! 2.7 1.6 2.8 ]/8; # Fourier
  163. %!
  164. %! # This is peaks(50) without the dependency on the plot package
  165. %! x = y = linspace(-3,3,50);
  166. %! [X,Y] = meshgrid(x,y);
  167. %! x = 3*(1-X).^2.*exp(-X.^2 - (Y+1).^2) \
  168. %! - 10*(X/5 - X.^3 - Y.^5).*exp(-X.^2-Y.^2) \
  169. %! - 1/3*exp(-(X+1).^2 - Y.^2);
  170. %!
  171. %! x -= min(x(:)); # Fourier does not handle neg. values well
  172. %! x = x./max(x(:));
  173. %! for m = 1:(length(methods))
  174. %! y = x;
  175. %! for i = 1:5
  176. %! y = imrotate(y, 60, methods{m}, "crop", 0);
  177. %! end
  178. %! for a = 1:(length(angles))
  179. %! assert(norm((x - imrotate(y, angles(a), methods{m}, "crop", 0))
  180. %! (10:40, 10:40)) < tolerances(m,a));
  181. %! end
  182. %! end
  183. %!#test
  184. %! ## Verify exactness of near-90 and 90-degree rotations:
  185. %! X = rand(99);
  186. %! for angle = [90 180 270]
  187. %! for da = [-0.1 0.1]
  188. %! Y = imrotate(X, angle + da , "nearest", :, 0);
  189. %! Z = imrotate(Y, -(angle + da), "nearest", :, 0);
  190. %! assert(norm(X - Z) == 0); # exact zero-sum rotation
  191. %! assert(norm(Y - imrotate(X, angle, "nearest", :, 0)) == 0); # near zero-sum
  192. %! end
  193. %! end
  194. %!#test
  195. %! ## Verify preserved pixel density:
  196. %! methods = { "nearest", "bilinear", "bicubic", "Fourier" };
  197. %! ## This test does not seem to do justice to the Fourier method...:
  198. %! tolerances = [ 4 2.2 2.0 209 ];
  199. %! range = 3:9:100;
  200. %! for m = 1:(length(methods))
  201. %! t = [];
  202. %! for n = range
  203. %! t(end + 1) = sum(imrotate(eye(n), 20, methods{m}, :, 0)(:));
  204. %! end
  205. %! assert(t, range, tolerances(m));
  206. %! end