diff main/image/inst/imtransform.m @ 11662:fbd81057dab0 octave-forge

image: new spatial transformation functions * maketform: accept input/output control points as second and third argument * imtransform: new function * findsbounds: new function * private/istform: add comment on what function does * NEWS/INDEX: update list of new functions
author carandraug
date Sun, 28 Apr 2013 02:31:13 +0000
parents
children 0f16ee5611b8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/image/inst/imtransform.m	Sun Apr 28 02:31:13 2013 +0000
@@ -0,0 +1,350 @@
+## Copyright (C) 2012 Pantxo Diribarne
+##
+## 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 Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {Function File} {@var{B} =} imtransform (@var{A}, @var{T})
+## @deftypefnx {Function File} {@var{B} =} imtransform (@var{A}, @var{T}, @var{interp})
+## @deftypefnx {Function File} {@var{B} =} imtransform (@dots{}, @var{prop}, @var{val})
+## Transform image.
+##
+## Given an image @var{A} in one space, returns an image @var{B}
+## resulting from the forward transform defined in the transformation
+## structure @var{T}.  An additionnal input argument @var{interp},
+## 'bicubic', 'bilinear' (default) or 'nearest',
+## specifies the interpolation method to be used.  Finally, the
+## transformation can be tuned using  @var{prop}/@var{val} pairs.  The
+## following properties are supported:
+## 
+## @table @asis
+## @item "udata"
+## Specifies the input space horizontal limits. Value must be a two
+## elements vector [minval maxval]. Default: [1 columns(@var{A})]
+##
+## @item "vdata"
+## Specifies the input space vertical limits. Value must be a two
+## elements vector [minval maxval]. Default: [1 rows(@var{A})]
+##
+## @item "xdata"
+## Specifies the requiered output space horizontal limits. Value must
+## be a two elements vector [minval maxval]. Default: estimated using
+## udata, vdata and findbounds function.
+##
+## @item "ydata"
+## Specifies the requiered output space vertical limits. Value must
+## be a two elements vector [minval maxval]. Default: estimated using
+## udata, vdata and findbounds function.
+##
+## @item "xyscale"
+## Specifies the output scale in outputspace_units/pixel. If a scalar
+## is provided, both vertical and horizontal dimensions are scaled the
+## same way. If @var{val} is a two element vector, it must indicate
+## consecutively horizontal and vertical scales. Default value is
+## computed using the input space scale provided
+## that the number of pixel of any dimension of the output image does
+## not exceed 20000.
+##
+## @item "size"
+## Size of the output image (1-by-2 vector). Overrides the effect of
+## "xyscale" property. 
+##
+## @item "fillvalues"
+## Color of the areas where no interpolation where possible, e.g. when
+## coorfinates of points in the output space are out of the limits of
+## the input space. @var{val} must be coherent with the input image
+## format: for grayscale and indexed images (2D) @var{val} must be
+## scalar, for RGB (n-by-m-by-3) @var{val} must be a 3 element vector.
+## e.g.:
+##
+## @end table
+##
+## @seealso{maketform, cp2tform, tforminv, tformfwd, findbounds}
+## @end deftypefn
+
+## Author: Pantxo Diribarne <pantxo@dibona>
+
+function [varargout] = imtransform (im, T, varargin)
+
+  if (nargin < 2)
+    print_usage ();
+  elseif (! istform (T))
+    error ("imtransform: T must be a transformation structure (see `maketform')");
+  endif
+
+  ## Parameters
+  interp = "linear";
+  imdepth = size (im, 3);
+  maximsize = [20000 20000];
+
+  udata = [1; columns(im)];
+  vdata = [1; rows(im)];
+  xdata = ydata = [];
+  xyscale = [];
+  imsize = [];
+  fillvalues = ones (1, imdepth) * NaN;
+
+  if (isempty (varargin))
+    xydata = findbounds (T, [udata vdata]);
+    xdata = xydata(:,1);
+    ydata = xydata(:,2);
+  else
+    ## interp
+    if (floor (numel (varargin)/2) != numel (varargin)/2)
+      allowed = {"bicubic", "bilinear", "nearest"};
+      tst = strcmp (varargin{1}, allowed);
+      if (!any (tst))
+        error ("imtransform: expect one of %s as interp method", disp (allowed));
+      else
+        interp = {"pchip", "linear", "nearest"}{find (tst)};
+      endif
+      varargin = varargin(2:end);
+    endif
+
+    ## options
+    allowed = {"udata", "vdata", "xdata", "ydata", ...
+               "xyscale", "size", "fillvalues"};
+    props = varargin(1:2:end);
+    vals = varargin(2:2:end);
+    np = numel (props);
+    if (!all (cellfun (@ischar, props)))
+      error ("imtransform: expect property/value pairs.");
+    endif
+
+    props = tolower (props);
+    tst = cellfun (@(x) any (strcmp (x, allowed)), props);
+    if (!all (tst))
+      error ("imtransform: unknown property %s", disp (props{!tst}));
+    endif
+
+    ## u(vxy)data
+    iolims = allowed(1:4);
+    for ii = 1:numel (iolims)
+      tst = cellfun (@(x) any (strcmp (x, iolims{ii})), props);
+      if (any (tst))
+        prop = props{find (tst)(1)};
+        val = vals{find (tst)(1)};
+        if (isnumeric (val) && numel (val) == 2)
+          if (isrow (val))
+            val = val';
+          endif
+          eval (sprintf ("%s = val;", prop),
+                "error (\"imtransform: %s\n\", lasterr ());");
+        else
+          error ("imtransform: expect 2 elements real vector for %s", prop)
+        endif
+      endif
+    endfor
+    if (isempty (xdata) && isempty (ydata))
+      xydata = findbounds (T, [udata vdata]);
+      xdata = xydata(:,1);
+      ydata = xydata(:,2);
+    elseif (isempty (xdata))
+      xydata = findbounds (T, [udata vdata]);
+      xdata = xydata(:,1);
+    elseif (isempty (ydata))
+      xydata = findbounds (T, [udata vdata]);
+      ydata = xydata(:,2);
+    endif
+
+    ## size and xyscale
+    tst = strcmp ("size", props);
+    if (any (tst))
+      val = vals{find (tst)(1)};
+      if (isnumeric (val) && numel (val) == 2 &&
+          all (val > 0))
+        imsize = val;
+      else
+        error ("imtransform: expect 2 elements real vector for size");
+      endif
+    elseif (any (tst = strcmp ("xyscale", props)))
+      val = vals{find (tst)(1)};
+      if (isnumeric (val) && all (val > 0))
+        if (numel (val) == 1)
+          xyscale(1:2) = val;
+        elseif (numel (val) == 2)
+          xyscale = val;
+        else
+          error ("imtransform: expect 1 or 2 element(s) real vector for xyscale");
+        endif
+      else
+        error ("imtransform: expect 1 or 2 elements real vector for xyscale");
+      endif
+    else
+      xyscale = [(diff (udata) / columns (im)) (diff (vdata) / rows (im))];
+    endif
+
+    ## Fillvalues
+    tst = strcmp ("fillvalues", props);
+    if (any (tst))
+      val = vals{find (tst)(1)};
+      if (isnumeric (val) && numel (val) == 1)
+        fillvalues(1:end) = val;
+      elseif (isnumeric (val) && numel (val) == 3)
+        fillvalues = val;
+      else
+        error ("imtransform: expect 1 or 3 elements real vector for `fillvalues'");
+      endif
+    endif
+  endif
+
+  ## Ouput/Input pixels
+  if (isempty (imsize))
+    if (isempty (xyscale))
+      xyscale = [(diff (udata) / columns (im)) (diff (vdata) / rows (im))];
+    endif
+    xscale = xyscale(1);
+    yscale = xyscale(2);
+    xsize = floor (diff (xdata) / xscale);
+    ysize = floor (diff (ydata) / yscale);
+    if (xsize > maximsize(2) || ysize > maximsize(1))
+      if (xsize >= ysize)
+        scalefactor = (diff (xdata) / maximsize(2)) / xscale;
+      else
+        scalefactor = (diff (ydata) / maximsize(1)) / yscale;
+      endif
+      xscale *= scalefactor
+      yscale *= scalefactor
+      xsize = floor (diff (xdata) / xscale);
+      ysize = floor (diff (ydata) / yscale);
+      warning ("imtransform: output image two large, adjusting the largest dimension to %d", maximsize);
+    endif
+    imsize = [ysize xsize];
+  endif
+  [xx yy] = meshgrid (linspace (xdata(1), xdata(2), imsize(2)),
+                      linspace (ydata(1), ydata(2), imsize(1)));
+
+  [uu vv] = meshgrid (linspace (udata(1), udata(2), size(im)(2)),
+                      linspace (vdata(1), vdata(2), size(im)(1)));
+
+  ## Input coordinates
+  [uui, vvi] = tforminv (T, reshape (xx, numel (xx), 1),
+                         reshape (yy, numel (yy), 1));
+  uui = reshape (uui, size (xx));
+  vvi = reshape (vvi, size (yy));
+  ## Interpolation
+  for layer = 1:imdepth
+    imout(:,:,layer) = interp2 (uu, vv, im(:,:,layer), ...
+                                uui, vvi, interp, fillvalues(layer));
+  endfor
+  if (nargout == 1)
+    varargout{1} = imout;
+  else
+    varargout = {imout, xdata, ydata};
+  endif
+endfunction
+
+%!demo
+%! ## Various linear transforms
+%! figure (); 
+%! im = [checkerboard(20, 2, 4); checkerboard(40, 1, 2)];
+%! %input space corners
+%! incp = [1 1; 160 1; 160 160; 1 160];
+%! udata = [min(incp(:,1)) max(incp(:,1))];
+%! vdata = [min(incp(:,2)) max(incp(:,2))];
+%! subplot (2,3,1); 
+%! imshow (im)
+%! hold on
+%! plot (incp(:,1), incp(:,2), 'ob')
+%! axis on
+%! xlabel ('Original')
+%! 
+%! % Translation and scaling
+%! outcp = incp * 2;
+%! outcp(:,1) += 200; 
+%! outcp(:,2) += 500;
+%! T = maketform ('affine', incp(1:3,:), outcp(1:3,:));
+%! subplot (2,3,2); 
+%! [im2 xdata ydata] = imtransform (im, T, 'udata', udata,
+%!                                  'vdata', vdata, 'fillvalues', 1);
+%! imh = imshow (im2); set (imh, 'xdata', xdata, 'ydata', ydata) 
+%! set (gca, 'xlim', xdata, 'ylim', ydata)
+%! axis on, hold on, xlabel ('Translation / Scaling');
+%! plot (outcp(:,1), outcp(:,2), 'or')
+%! 
+%! % Shear
+%! outcp = [1 1; 160 1; 140 160; -19 160]; % affine only needs 3 control points
+%! T = maketform ('affine', incp(1:3,:), outcp(1:3,:));
+%! subplot (2,3,3); 
+%! [im2 xdata ydata] = imtransform (im, T, 'udata', udata,
+%!                                  'vdata', vdata, 'fillvalues', 1);
+%! imh = imshow (im2); set (imh, 'xdata', xdata, 'ydata', ydata) 
+%! set (gca, 'xlim', xdata, 'ylim', ydata)
+%! axis on, hold on, xlabel ('Shear');
+%! plot (outcp(:,1), outcp(:,2), 'or')
+%! 
+%! % Rotation 
+%! theta = pi/4;
+%! T = maketform ('affine', [cos(theta) -sin(theta); ...
+%!                           sin(theta) cos(theta); 0 0]);
+%! outcp = tformfwd (T, incp);
+%! subplot (2,3,4); 
+%! [im2 xdata ydata] = imtransform (im, T, 'udata', udata,
+%!                                  'vdata', vdata, 'fillvalues', 1 );
+%! imh = imshow (im2); set (imh, 'xdata', xdata, 'ydata', ydata) 
+%! set (gca, 'xlim', xdata, 'ylim', ydata)
+%! axis on, hold on, xlabel ('Rotation');
+%! plot (outcp(:,1), outcp(:,2), 'or')
+%!
+%! % Reflection around x axis
+%! outcp = incp;
+%! outcp(:,2) *= -1;
+%! T = cp2tform (incp, outcp, 'similarity');
+%! subplot (2,3,5); 
+%! [im2 xdata ydata] = imtransform (im, T, 'udata', udata,
+%!                                  'vdata', vdata, 'fillvalues', 1 );
+%! imh = imshow (im2); set (imh, 'xdata', xdata, 'ydata', ydata) 
+%! set (gca, 'xlim', xdata, 'ylim', ydata)
+%! axis on, hold on, xlabel ('Reflection');
+%! plot (outcp(:,1), outcp(:,2), 'or')
+%!
+%! % Projection
+%! outcp = [1 1; 160 -40; 220 220; 12 140];
+%! T = maketform ('projective', incp, outcp);
+%! subplot (2,3,6); 
+%! [im2 xdata ydata] = imtransform (im, T, 'udata', udata,
+%!                                  'vdata', vdata, 'fillvalues', 1 );
+%! imh = imshow (im2); set (imh, 'xdata', xdata, 'ydata', ydata) 
+%! set (gca, 'xlim', xdata, 'ylim', ydata)
+%! axis on, hold on, xlabel ('Projection');
+%! plot (outcp(:,1), outcp(:,2), 'or')
+
+%!demo
+%! ## Streched image
+%! rad = 2; % minimum value: 4/pi
+%! [uu vv] = meshgrid ((-2:2)/rad, (-2:2)/rad);
+%! rescfactor = sin ((uu.^2 + vv.^2).^.5);
+%! inpts = [(reshape (uu, numel (uu), 1)), (reshape (vv, numel (uu), 1))]; 
+%! xx = rescfactor .* sign(uu);
+%! yy = rescfactor .* sign(vv);
+%! outpts = [reshape(xx, numel (xx), 1) reshape(yy, numel (yy), 1)];
+%! 
+%! T = cp2tform (inpts, outpts, "polynomial", 4);
+%! figure;
+%! subplot (1,2,1)
+%! im = zeros (800, 800, 3);
+%! im(:,:,1) = checkerboard (100) > 0.2;
+%! im(:,:,3) = checkerboard (100) < 0.2;
+%! [im2 xdata ydata] = imtransform (im, T, 'udata', [-2 2],
+%!                                  'vdata', [-2 2], 'fillvalues',
+%!                                  [0 1 0]);
+%! imh = imshow (im2);
+%! set (imh, 'xdata', xdata, 'ydata', ydata)
+%! set (gca, 'xlim', xdata, 'ylim', ydata)
+%! [im cmap] = imread ('default.img');
+%! subplot (1,2,2)
+%! [im2 xdata ydata] = imtransform (im, T, 'udata', [-1 1],
+%!                                  'vdata', [-1 1], 'fillvalues',
+%!                                  round (length (cmap) / 2));
+%! imh = imshow (im2, cmap);