snibgo's ImageMagick pages

What translation?

Given two images of the same size, where one image could be a translation of another image with a known scale and rotation, what are the best offsets?

The basic problem is trivial: just do a "-subimage-search". This script also scales, rotates and crops the first image before finding it in the second, and adjusts the resulting offsets to give (0,0) when the centres of the input images coincide.

This is a companion page to What rotation? and What scale?, What rotation and scale?. The numeric outputs from those scripts can be used as input to this script. In conjunction with those scripts, this one can answer questions like:

This is a sub-problem of the more general problem: "What areas in image A match what areas in image B?"

Sample inputs

We crop a photograph, make a base image, then three variations at different scales and rotations, and slightly different offsets.

set wtDEBUG=1

set ANG=100

%IM%convert ^
  \pictures\20140430\GOPR0166.JPG ^
  -crop 900x700+2014+2090 +repage ^
  -gravity Center ^
  +depth ^
  ( -clone 0 ^
    -crop 300x200+0+0 +repage ^
    -write wt_src1.png ^
    +delete ) ^
  ( -clone 0 ^
    -crop 300x200+72+42 +repage ^
    -write wt_src2.png ^
    +delete ) ^
  ( -clone 0 ^
    -distort SRT 1.2,100 +repage ^
    -crop 300x200+73+43 +repage ^
    -write wt_src3.png ^
    +delete ) ^
  ( -clone 0 ^
    -distort SRT 0.7,-100 +repage ^
    -crop 300x200+74+44 +repage ^
    -write wt_src4.png ^
    +delete ) ^
  NULL:

This first image, #1, ...

wt_src1.pngjpg

... will be tested against images #2, #3 and #4.

wt_src2.pngjpg wt_src3.pngjpg wt_src4.pngjpg

The method

We scale and rotate the first image, and crop it by 50%. The crop ensures we have no virtual pixels, but what about scale less than 1?

The script may crop one pixel smaller than 50%, to maintain parity of each dimension. If the width was even, it will remain even. If it was odd, it will remain odd. This ensures the difference between the widths is even, so the adjustment to the found offset will be an integer. The same applies to the height.

A conventional subimage-search is then performed, looking for the scaled/rotated/cropped image within the second image.

The script checks for the found offsets being at the minimum or maximum. If they are, it sets wtDODGY to 1.

The found offsets are adjusted by subtracting half the difference between the widths.

Caution: if the true translation is more than width/4 horizontally or height/4 vertically, this script won't find it, and will return a false result that probably won't be caught by wtDODGY. A caller should check ERRORLEVEL and wtDODGY, but also needs to check that wtSCORE is reasonable.

Aside: what RMSE score is reasonable?

I am generally concerned with visual differences. If the RMSE is less than 1% (ie the floating-point value is < 0.01), and the error is evenly distributed, I find it difficult to see any difference, so I would consider the images to be equal.

If the RMSE is greater then 10% (ie the floating-point value is > 0.10), the difference is obvious, and the images are unequal.

So, where does the boundary lie? It depends on the purpose and the type of image. Perhaps 5% is a reasonable compromise.

Using the script

We use the script like this:

Image #2:

call %PICTBAT%whatTrans wt_src1.png wt_src2.png
if ERRORLEVEL 1 exit /B 1

echo wtX=%wtX% wtY=%wtY% wtXY=%wtXY% wtXYneg=%wtXYneg% wtSCORE=%wtSCORE% wtDODGY=%wtDODGY% 
wtX=-72 wtY=-42 wtXY=-72-42 wtXYneg=+72+42 wtSCORE=0.00490372 wtDODGY=0 

The returned offsets are the negative of the ones used in "-crop" to generate image #2.

We can use the returned wtXY or wtXYneg:

%IM%convert ^
  wt_src1.png ^
  wt_src2.png ^
  -geometry %wtXYneg% ^
  -composite ^
  wt_c12.png
wt_c12.pngjpg
%IM%convert ^
  wt_src1.png ^
  ( wt_src2.png ^
    -repage %wtXYneg% ^
  ) ^
  -layers merge ^
  wt_c12lm.png
wt_c12lm.pngjpg

Image #3:

call %PICTBAT%whatTrans wt_src1.png wt_src3.png 1.2 100
if ERRORLEVEL 1 exit /B 1

echo wtX=%wtX% wtY=%wtY% wtXY=%wtXY% wtXYneg=%wtXYneg% wtSCORE=%wtSCORE% wtDODGY=%wtDODGY% 
wtX=-73 wtY=-43 wtXY=-73-43 wtXYneg=+73+43 wtSCORE=0.00113308 wtDODGY=0 

Image #4:

set wtDELTEMP=0

call %PICTBAT%whatTrans wt_src1.png wt_src4.png 0.7 -100
if ERRORLEVEL 1 exit /B 1

set wtDELTEMP=

echo wtX=%wtX% wtY=%wtY% wtXY=%wtXY% wtXYneg=%wtXYneg% wtSCORE=%wtSCORE% wtDODGY=%wtDODGY% 
wtX=-74 wtY=-44 wtXY=-74-44 wtXYneg=+74+44 wtSCORE=0.0305385 wtDODGY=0 

The debugging images show:

wt_src12_dbg.pngjpg wt_src13_dbg.pngjpg wt_src14_dbg.pngjpg

The returned offsets are accurate. The scores are generally good, except for #4. We will see below why this is and what to do about it.

Here is the "-distort SRT" that transforms from image #1 to #4:

%IM%convert ^
  wt_src1.png ^
  -distort SRT 150,100,0.7,-100,76,56 ^
  wt_trans4.png
wt_trans4.pngjpg

It doesn't re-create image #4, of course. It can't. Image #4 has a wider field of view than image #1. But this transformation best matches corresponding points in both images.

The six SRT parameters are:

This is the scaled, rotated and cropped version of #1 that is compared to #4:

%IM%convert ^
  %TEMP%\wt_src1_0.7_-100_0_wt.miff ^
  wt_for4.png
wt_for4.pngjpg

We can see virtual pixels top-right and bottom-left. The 50% crop wasn't sufficient to avoid them. This is why the score wasn't very good. We use set wtTUNE_CROP=1 to tell the script to adjust the crop according to the scale:

set wtDELTEMP=0
set wtTUNE_CROP=1
call %PICTBAT%whatTrans wt_src1.png wt_src4.png 0.7 -100
if ERRORLEVEL 1 exit /B 1
set wtTUNE_CROP=
set wtDELTEMP=

echo wtX=%wtX% wtY=%wtY% wtXY=%wtXY% wtXYneg=%wtXYneg% wtSCORE=%wtSCORE% wtDODGY=%wtDODGY% 
wtX=-74 wtY=-44 wtXY=-74-44 wtXYneg=+74+44 wtSCORE=0.00114207 wtDODGY=0 

By avoiding virtual pixels, the score is far more accurate.

This is the crop that is compared to #4:

%IM%convert ^
   %TEMP%\wt_src1_0.7_-100_1_wt.miff ^
  wt_for4a.png
wt_for4a.pngjpg

We can also test the identity case, ie match an image against itself:

call %PICTBAT%whatTrans wt_src1.png wt_src1.png
if ERRORLEVEL 1 exit /B 1

echo wtX=%wtX% wtY=%wtY% wtXY=%wtXY% wtXYneg=%wtXYneg% wtSCORE=%wtSCORE% wtDODGY=%wtDODGY% 
wtX=0 wtY=0 wtXY=+0+0 wtXYneg=+0+0 wtSCORE=0.00490372 wtDODGY=0 

The score is not exactly zero because "-distort 1,0" slightly changes pixels.

Scripts

For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.

whatTrans.bat

rem Given same-sized images %1 and %2,
rem scales %1 by %3 and rotates by %4, and crops it.
rem Returns location of resultimg image in %2 as offset from centres,
rem ie (0,0) means scaled and rotated %1 is not displaced over %2.
rem %5 is prefix for environment variables for numeric results. [wt]

@rem Also uses:
@rem   wtMETRIC        default RMSE
@rem   wtTUNE_CROP     if 1, crop percentage will be adjusted for the given scale
@rem   wtDEBUG         if 1, creates image for debugging
@rem   wtDEBUG_FILE    name of debugging image file
@rem   wtDELTEMP       if not 0, will remove temporary file.
@rem
@rem Returns:
@rem   wtX, wtY    integer pixel offsets.
@rem                 Positive values mean the first image needs moving
@rem                 down and right in order to register with the second image.
@rem   wtXY        the previous, concatenated in the format +X+Y or -X-Y etc.
@rem   wtXYneg     like wtXY but with negated values.
@rem   wtSCORE     0.0 to 1.0, more or less
@rem   wtDEBUG_OUTFILE  name of debugging image file
@rem
@rem Last updated:
@rem   6-August-2016 Cleaned up.


@if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave


set SCALE=%3
if "%SCALE%"=="." set SCALE=
if "%SCALE%"=="" set SCALE=1

set ANGLE=%4
if "%ANGLE%"=="." set ANGLE=
if "%ANGLE%"=="" set ANGLE=0

set ENV_PREF=%5
if "%ENV_PREF%"=="." set ENV_PREF=
if "%ENV_PREF%"=="" set ENV_PREF=wt

set TMPEXT=.miff

if "%wtMETRIC%"=="" set wtMETRIC=RMSE

if "%wtTUNE_CROP%"=="" set wtTUNE_CROP=0

call %PICTBAT%setInOut %1 wt
set IN_A=%INFILE%
set TEMP_A=%TEMP%\%~n1_%SCALE%_%ANGLE%_%wtTUNE_CROP%_wt%TMPEXT%

call %PICTBAT%setInOut %2 wt
set IN_B=%INFILE%

if not "%wtDELTEMP%"=="0" (
  del %TEMP_A% 2>nul
)

if "%wtDEBUG_OUTFILE%"=="" (
  set DEBUG_FILE=wt_%~n1_%~n2_dbg%EXT%
) else (
  set DEBUG_FILE=%wtDEBUG_OUTFILE%
)

for /F "usebackq" %%L ^
in (`%IM%identify -format "WW2=%%w\nHH2=%%h" %IN_B%`) ^
do set %%L

set /A CROP_W=WW2/2
set /A CROP_H=HH2/2

rem Maybe do next only if SCALE < 1.
rem If done when scale near 2, there is no wriggle-room for search.
if %wtTUNE_CROP%==1 for /F "usebackq" %%L in (`%IM%identify ^
  -format "CROP_W=%%[fx:%WW2%*%SCALE%/2]\nCROP_H=%%[fx:%HH2%*%SCALE%/2]" ^
  xc:`) do set %%L

rem Ensure CROP_X has same parity (least significant bit) as WW2,
rem and CROP_Y has same parity as HH2.
set /A CROP_W-=CROP_W%%2^^WW2%%2
set /A CROP_H-=CROP_H%%2^^HH2%%2

echo %0: SCALE=%SCALE% ANGLE=%ANGLE% CROP_W=%CROP_W% CROP_H=%CROP_H%

%IM%convert ^
  %IN_A% ^
  -distort SRT %SCALE%,%ANGLE% -gravity center -crop %CROP_W%x%CROP_H%+0+0 +repage ^
  %TEMP_A%

for /F "usebackq" %%L in (`%IM%convert ^
  -ping %TEMP_A% ^
  -format "WW1=%%w\nHH1=%%h\nW_2=%%[fx:(%WW2%-w)/2]\nH_2=%%[fx:(%HH2%-h)/2]" ^
  info:`) do set %%L

set /A diffW=WW2-WW1
set /A diffH=HH2-HH1

set siDEBUG=
call %PICTBAT%srchImg %IN_B% %TEMP_A%
if ERRORLEVEL 1 exit /B 1

set /A RESULT_XI=siCOMP_XW-diffW/2
set /A RESULT_YI=siCOMP_YW-diffH/2

echo %0: COMP_FLT=%siCOMP_FLT% COMP_XW=%siCOMP_XW% COMP_YW=%siCOMP_YW%


set DODGY=0
if %siCOMP_XW%==0 set DODGY=1
if %siCOMP_YW%==0 set DODGY=1
if %siCOMP_XW%==%diffW% set DODGY=1
if %siCOMP_YW%==%diffH% set DODGY=1


if "%wtDEBUG%"=="1" (

  for /F "usebackq" %%L in (`%IM%convert ^
    -ping %IN_A% ^
    -format "W_2=%%[fx:w/2]\nH_2=%%[fx:h/2]\nnewX=%%[fx:w/2+(%RESULT_XI%)]\nnewY=%%[fx:h/2+(%RESULT_YI%)]" ^
    info:`) do set %%L

  %IM%convert ^
  %IN_A% ^
  ^( +clone -alpha Transparent ^
    -shave 1x1 ^
    -bordercolor None ^
    -mattecolor #f00 -frame 1x1 ^
    -channel A -evaluate Multiply 0.75 ^
  ^) ^
  -composite ^
  -virtual-pixel None ^
  +distort SRT !W_2!,!H_2!,%SCALE%,%ANGLE%,!newX!,!newY! ^
  ^( %IN_B% ^
    ^( +clone -alpha Transparent ^
       -shave 1x1 ^
       -bordercolor None ^
       -mattecolor #0f0 -frame 1x1 ^
       -channel A -evaluate Multiply 0.75 ^
    ^) ^
    -composite ^
  ^) ^
  ^( %TEMP_A% ^
    ^( +clone -alpha Transparent ^
       -shave 1x1 ^
       -bordercolor None ^
       -mattecolor #ff0 -frame 1x1 ^
    ^) ^
    -composite ^
    -alpha set ^
    -channel A -evaluate Multiply 0.5 ^
    -repage +%siCOMP_XW%+%siCOMP_YW% ^
  ^) ^
  -layers merge ^
  +repage ^
  %DEBUG_FILE%
)

if not "%wtDELTEMP%"=="0" (
  del %TEMP_A% 2>nul
)

set SX=
set SY=
if %RESULT_XI% GEQ 0 set SX=+
if %RESULT_YI% GEQ 0 set SY=+
set wtXY=%SX%%RESULT_XI%%SY%%RESULT_YI%

set /A SXn=-%RESULT_XI%
set /A SYn=-%RESULT_YI%
set SX=
set SY=
if %SXn% GEQ 0 set SX=+
if %SYn% GEQ 0 set SY=+
set wtXYneg=%SX%%SXn%%SY%%SYn%

echo %0: RESULT_XI=%RESULT_XI%  RESULT_YI=%RESULT_YI% wtXY=%wtXY%

call echoRestore

endlocal &set %ENV_PREF%X=%RESULT_XI%&^
set %ENV_PREF%Y=%RESULT_YI%&^
set %ENV_PREF%SCORE=%siCOMP_FLT%&^
set %ENV_PREF%DODGY=%DODGY%&^
set %ENV_PREF%DEBUG_FILE=%DEBUG_FILE%&^
set %ENV_PREF%XY=%wtXY%&^
set %ENV_PREF%XYneg=%wtXYneg%

All images on this page were created by the commands shown, using:

%IM%identify -version
Version: ImageMagick 6.9.5-3 Q16 x86 2016-07-22 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2015 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Visual C++: 180040629
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib cairo flif freetype jng jp2 jpeg lcms lqr openexr pangocairo png ps rsvg tiff webp xml zlib

To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG to JPG.

Source file for this web page is whattrans.h1. To re-create this web page, run "procH1 whattrans".


This page, including the images, is my copyright. Anyone is permitted to use or adapt any of the code, scripts or images for any purpose, including commercial use.

Anyone is permitted to re-publish this page, but only for non-commercial use.

Anyone is permitted to link to this page, including for commercial use.


Page version v1.1 14-August-2016.

Page created 17-Aug-2016 11:11:45.

Copyright © 2016 Alan Gibson.