#!/usr/bin/perl # # affine_transform [-fx] x1,y1 .. x3,y3 u1,v1 .. u3,v3 # # Given two sets of three coordinate pairs (a triangle), work out 6 constants # needed to map the first set of coordinates to the second set of coordinates, # using a affine transformation, or affine matrix. # # The twelve numbers given may be comma or space separated, as one single # argument or over a number of arguments. # # The Affine transformation using these 6 constants (note the order) # is given as... # # u = c1*x + c3*y + c5 # v = c2*x + c4*y + c6 # # By default the 6 constants needed are returned as a comma separated # list of numbers suitable for direct use by the IM -affine setting. # # However you can also specify an '-fx' option which will output the above # equations in the form of an ImageMagick "-fx" expression, to lookup colors # from first 'u' image. # # Note for -fx usage the input coordinates should be reversed, so as to # generate the inverse affine matrix, as you really want to map destination # image coordinates to the source image coordinates. # #### # # Script by Anthony Thyssen (June 2007) # # Requires the "Math::MatrixReal" perl module to be installed. No compilation # is needed to build this module. For the latest version of this module see # http://leto.net/code/Math-MatrixReal/ # use strict; use Math::MatrixReal; use FindBin; my $prog = $FindBin::Script; # Output the program comments as the programs manual sub Usage { print STDERR "$prog: ", @_, "\n"; @ARGV = ( "$FindBin::Bin/$prog" ); while( <> ) { next if 1 .. 2; last if /^###/; s/^#$//; s/^# //; print STDERR "Usage: " if 3 .. 3; print STDERR; } exit 10; } @ARGV = map( /([^\s,]+)/g, @ARGV); # Spilt arguments by spaces and commas my $FX = 0; # output a ImageMagick -fx perspective transformation formula my $TEST = 0; # Apply the constants to the input x,y points to test if ( $ARGV[0] eq '-fx' ) { $FX = 1; shift; } if ( @ARGV != 12 ) { Usage "Incorrect number of arguments, ", scalar(@ARGV), " given\n\t\t\t12 numbers (2 sets of 4 coordinate pairs) are needed."; } # Assign the coordinates of the two triangles (remove commas) my ( $x1, $y1, $x2, $y2, $x3, $y3, $u1, $v1, $u2, $v2, $u3, $v3 ) = @ARGV; # Convert coordinates into a matrix of simultanious equation # Such that ... A * c = r my $A = Math::MatrixReal->new_from_rows( [ [ $x1, 0.0, $y1, 0.0, 1.0, 0.0 ], [ 0.0, $x1, 0.0, $y1, 0.0, 1.0 ], [ $x2, 0.0, $y2, 0.0, 1.0, 0.0 ], [ 0.0, $x2, 0.0, $y2, 0.0, 1.0 ], [ $x3, 0.0, $y3, 0.0, 1.0, 0.0 ], [ 0.0, $x3, 0.0, $y3, 0.0, 1.0 ], ] ); my $r = Math::MatrixReal->new_from_cols( [ [ $u1, $v1, $u2, $v2, $u3, $v3 ] ] ); # Debuging: output an inverse matrix #print $A->inverse(); # Solve the matrix for the constants my ($dim, $c, $base) = $A->decompose_LR()->solve_LR($r); if ( $dim ) { print STDERR "$prog: Coordinates given do not form solvable triangles."; exit 1; } # Extract resulting column vector as an array. my @c = map { $c->element($_,1) } ( 1 .. 6 ); # Test results with original coordinates if ( 0 ) { print "Test Results against Original coordinates\n"; printf "%5.1f,%-5.1f -> %5.1f,%-5.1f (should be %5.1f,%-5.1f )\n", $x1, $y1, $c[0]*$x1 + $c[2]*$y1 + $c[4], $c[1]*$x1 + $c[3]*$y1 + $c[5], $u1, $v1; printf "%5.1f,%-5.1f -> %5.1f,%-5.1f (should be %5.1f,%-5.1f )\n", $x2, $y2, $c[0]*$x2 + $c[2]*$y2 + $c[4], $c[1]*$x2 + $c[3]*$y2 + $c[5], $u2, $v2; printf "%5.1f,%-5.1f -> %5.1f,%-5.1f (should be %5.1f,%-5.1f )\n", $x3, $y3, $c[0]*$x3 + $c[2]*$y3 + $c[4], $c[1]*$x3 + $c[3]*$y3 + $c[5], $u3, $v3; } if ( ! $FX ) { # Output the constants print join(',', map { sprintf "%9.6f", $_ } @c), "\n"; } else { # Output a IM -fx expression printf "xx=%+.6f*i %+.6f*j %+.6f;\n". "yy=%+.6f*i %+.6f*j %+.6f;\n", @c[0,2,4,1,3,5]; }