Given one image that could be a rotation and scale of another image, what is the best rotation angle and scale factor?
We crop a photograph, and make four variations.
set wrsDEBUG=1 set ANG=100 set FACT1=130 set FACT2=70 set DEP=+depth %IMG7%magick ^ %PICTLIB%20140430\GOPR0166.JPG ^ -crop 700x500+2114+2190 +repage ^ -gravity Center ^ ( -clone 0 ^ -crop 300x200+0+0 +repage ^ %DEP% -write wrs_src1.png ^ +delete ^ ) ^ ( -clone 0 ^ -rotate %ANG% +repage ^ -resize %FACT1%%% +repage ^ -crop 300x200+0+0 +repage ^ %DEP% -write wrs_src2.png ^ +delete ^ ) ^ ( -clone 0 ^ -rotate %ANG% +repage ^ -resize %FACT2%%% +repage ^ -crop 300x200+0+0 +repage ^ %DEP% -write wrs_src3.png ^ +delete ^ ) ^ ( -clone 0 ^ -rotate -%ANG% +repage ^ -resize %FACT1%%% +repage ^ -crop 300x200+0+0 +repage ^ %DEP% -write wrs_src4.png ^ +delete ^ ) ^ ( -clone 0 ^ -rotate -%ANG% +repage ^ -resize %FACT2%%% +repage ^ -crop 300x200+0+0 +repage ^ %DEP% -write wrs_src5.png ^ +delete ^ ) ^ NULL: |
From the above, we effectively make four copies at two different
|
If the method works, it should find both the rotation angle and scale factor for all four images.
See the pages What rotation? and What scale?, which are building blocks for this page.
If one image is thought to be both a rotation and a scale of another, we could iterate between two techniques. Find the rotation that seems to be best, then a scaling of the rotation that seems to be best, and keep iterating until the solution stabilises. The trouble is, we are searching in a two-dimensional space (the dimensions of rotation and scale) and we may find a solution that is optimal locally but not globally. We may be misled into finding a solution that isn't the best posible solution.
Hmmm... searching a 2-dimensional space. ImageMagick can do that, with "-subimage-search". All we need do is create a small image that is 360° wide and one scale factor high, and a larger image that is 720° wide and a greater number of scale factors high. A subimage search will find the rotation and scale of the globally best solution.
We create each image by making a depolar (as in What rotation?), and scaling this to a single row. Each pixel represents the average of the radial lines from the edge to the image centre. For the large image we repeat this at different scales (as in What scale?), then it is cloned and appended so it represents 720°.
IM's subimage-search locates one 2-D image within another, returning a 2-D coordinate. In normal usage, the two dimensions are the x- and y-coordinates of source images. This script transforms images so that the two dimensions are of scale and rotation.
call %PICTBAT%whatRotScale wrs_src1.png wrs_src2.png if ERRORLEVEL 1 exit /B 1 echo ANGLE=%wrsANGLE% ANGLE_ERR=%wrsANGLE_ERR% SCALE=%wrsSCALE% FACT=%wrsFACT% SCORE_SUB=%wrsSCORE_SUB% DODGY=%wrsDODGY%
ANGLE=99.9 ANGLE_ERR=0.3 SCALE=1.30036546 FACT=1.01104669 SCORE_SUB=0.0339344 DODGY=0
These are the two images that were compared:
Multi-scale version of the first image,
%IMG7%magick ^ %TEMP%\wrs_src1_wrs_0.5_1.5_100.png ^ -resize "300x^!" ^ -draw "rectangle 42,86 191,86" ^ wrs_comp1.png |
|
The second image, scaled down for the web,
%IMG7%magick ^ %TEMP%\wrs_src2_wrs.png ^ -resize "150x^!" ^ -scale "x50^!" ^ wrs_comp2.png |
Image #3:
call %PICTBAT%whatRotScale wrs_src1.png wrs_src3.png if ERRORLEVEL 1 exit /B 1 echo ANGLE=%wrsANGLE% ANGLE_ERR=%wrsANGLE_ERR% SCALE=%wrsSCALE% FACT=%wrsFACT% SCORE_SUB=%wrsSCORE_SUB% DODGY=%wrsDODGY%
ANGLE=99.9 ANGLE_ERR=0.3 SCALE=0.784494101 FACT=1.01104669 SCORE_SUB=0.117719 DODGY=0
Image #4:
call %PICTBAT%whatRotScale wrs_src1.png wrs_src4.png if ERRORLEVEL 1 exit /B 1 echo ANGLE=%wrsANGLE% ANGLE_ERR=%wrsANGLE_ERR% SCALE=%wrsSCALE% FACT=%wrsFACT% SCORE_SUB=%wrsSCORE_SUB% DODGY=%wrsDODGY%
ANGLE=260.1 ANGLE_ERR=0.3 SCALE=1.30036546 FACT=1.01104669 SCORE_SUB=0.0345222 DODGY=0
Image #5:
call %PICTBAT%whatRotScale wrs_src1.png wrs_src5.png if ERRORLEVEL 1 exit /B 1 echo ANGLE=%wrsANGLE% ANGLE_ERR=%wrsANGLE_ERR% SCALE=%wrsSCALE% FACT=%wrsFACT% SCORE_SUB=%wrsSCORE_SUB% DODGY=%wrsDODGY% set ANGLE_5=%wrsANGLE% set SCALE_5=%wrsSCALE%
ANGLE=259.8 ANGLE_ERR=0.3 SCALE=0.767444996 FACT=1.01104669 SCORE_SUB=0.114677 DODGY=0
The results are good, but the scales for images #3 and #5 are about 10% out, which isn't great. This error is well beyond the FACT value. We can try for greater precision by calling the script again with smaller gaps between the scale limits.
for /F "usebackq" %%L ^ in (`%IMG7%magick identify ^ -format "MIN=%%[fx:%SCALE_5%/1.2]\nMAX=%%[fx:%SCALE_5%*1.2]" ^ xc:`) ^ do set %%L call %PICTBAT%whatRotScale wrs_src1.png wrs_src5.png %MIN% %MAX% if ERRORLEVEL 1 exit /B 1 echo ANGLE=%wrsANGLE% ANGLE_ERR=%wrsANGLE_ERR% SCALE=%wrsSCALE% FACT=%wrsFACT% SCORE_SUB=%wrsSCORE_SUB% DODGY=%wrsDODGY%
ANGLE=259.8 ANGLE_ERR=0.3 SCALE=0.761868348 FACT=1.0036531 SCORE_SUB=0.113462 DODGY=0
This has slightly improved the result.
This script, whatRotScale.bat, found the scale from the average of radial lines, which naturally smears most data. We can rotate wrs_src1.png by the angle it found (ANGLE_5), and use the greater accuracy of whatScale.bat. After rotating, we crop by 50% to ensure we get no virtual pixels.
%IMG7%magick wrs_src1.png -distort SRT 1,%ANGLE_5% -gravity center -crop 50%%x50%%+0+0 +repage wrs_d1.png %IMG7%magick wrs_src5.png -gravity center -crop 50%%x50%%+0+0 +repage wrs_d5.png call %PICTBAT%whatScale wrs_d1.png wrs_d5.png echo SCALE=%wsSCALE% FACT=%wsFACT% SCORE_SUB=%wsSCORE_SUB% DODGY=%wsDODGY%
SCALE=1.01001437 FACT=1.01104669 SCORE_SUB=0.124641 DODGY=0
That is better. The scale found is within 1% of the true result, and within the limit stated by FACT.
The debugging images show:
We can also test the identity case, ie match an image against itself:
call %PICTBAT%whatRotScale wrs_src1.png wrs_src1.png if ERRORLEVEL 1 exit /B 1 echo ANGLE=%wrsANGLE% ANGLE_ERR=%wrsANGLE_ERR% SCALE=%wrsSCALE% FACT=%wrsFACT% SCORE_SUB=%wrsSCORE_SUB% DODGY=%wrsDODGY%
ANGLE=0 ANGLE_ERR=0.3 SCALE=0.998978955 FACT=1.01104669 SCORE_SUB=0.00586138 DODGY=0
The script below, whatRotScale.bat, returns the aproximate scale and rotation of one image with respect to another. The angle is accurate, but the scale is less accurate. Greater accuracy may be obtained by then using whatScale.bat (see What scale?).
The script works for non-square images. However, it ignores all pixels outside the central inscribed circle.
The search might work faster if it used Searching an image. Watch this space.
This page was inspired by Grayscale Template-Matching Invariant to Rotation, Scale, Translation, Brightness and Contrast (pdf), by Hae Yong Kim and Sidnei Alves de Araújo.
See also:
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
@rem Given same-size images %1 and %2, @rem finds scale S and rotation angle R in "-distort SRT S,R" for %1 to best match %2, and a score. @rem Parameters: @rem %3 min scale (default 0.5) OR "again" to iterate from previous run. @rem %4 max scale (default 1.5) @rem %5 number of scale steps (default 100) @rem %6 @rem %7 @rem @rem Also uses: @rem wrsDO_SCORE_SUB if 0, wrsSCORE_SUB will not be found. @rem wrsMETRIC default RMSE @rem wrsDO_SUPER if 0, no supersampling will be done. @rem wrsDEBUG if 1, creates images for debugging @rem @rem Returns: @rem wrsANGLE (degrees) @rem wrsSCALE @rem wrsFACT error margin, multiple of wrsSCALE @rem wrsSCORE (0.0 to 1.0, more or less). @rem wrsSCORE_SUB (0.0 to 1.0, more or less). @rem @rem Updated: @rem 10-August-2022 for IM v7. @rem @if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal @call echoOffSave if "%wrsMETRIC%"=="" set wrsMETRIC=RMSE if "%wrsDO_SCORE_SUB%"=="" set wrsDO_SCORE_SUB=1 set wrsMIN_SCALE=%3 if "%wrsMIN_SCALE%"=="." set wrsMIN_SCALE= if "%wrsMIN_SCALE%"=="" set wrsMIN_SCALE=0.5 set wrsMAX_SCALE=%4 if "%wrsMAX_SCALE%"=="." set wrsMAX_SCALE= if "%wrsMAX_SCALE%"=="" set wrsMAX_SCALE=1.5 set wrsnSTEPS=%5 if "%wrsnSTEPS%"=="." set wrsnSTEPS= if "%wrsnSTEPS%"=="" set wrsnSTEPS=100 set wrsOFFS_X=%6 if "%wrsOFFS_X%"=="." set wrsOFFS_X= if "%wrsOFFS_X%"=="" set wrsOFFS_X=0 set wrsOFFS_Y=%7 if "%wrsOFFS_Y%"=="." set wrsOFFS_Y= if "%wrsOFFS_Y%"=="" set wrsOFFS_Y=0 if "%wrsDO_SUPER%"=="" set wrsDO_SUPER=1 set wrsDEPTH=+depth if /I "%wrsMIN_SCALE%" EQU "again" ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 9 ^ -format "wrsMIN_SCALE=%%[fx:%wrsSCALE%/pow(%wrsFACT%,10)]\nwrsMAX_SCALE=%%[fx:%wrsSCALE%*pow(%wrsFACT%,10)]" ^ xc:`) do set %%L ) if "%wrsMIN_SCALE%"=="" exit /B 1 call %PICTBAT%setInOut %1 wrs set IN_A=%INFILE% set TEMP_A_BASE=%TEMP%\%~n1_wrs rem FIXME: also offsets set TEMP_A=%TEMP%\%~n1_wrs_%wrsMIN_SCALE%_%wrsMAX_SCALE%_%wrsnSTEPS%%EXT% call %PICTBAT%setInOut %2 wrs set IN_B=%INFILE% set TEMP_B=%TEMP%\%~n2_wrs%EXT% if %IN_B%==%IN_A% set TEMP_B=%TEMP%\%~n2_B_wrs%EXT% set DEBUG_FILE=wrs_%~n1_%~n2_dbg%EXT% for /F "usebackq" %%L ^ in (`%IMG7%magick %IN_A% ^ -format "WW1=%%w\nHH1=%%h\nWW1_2=%%[fx:w/2]\nHH1_2=%%[fx:h/2]\nSRT_OFFS_X=%%[fx:w/2+(%wrsOFFS_X%)]\nSRT_OFFS_Y=%%[fx:h/2+(%wrsOFFS_Y%)]" ^ INFO:`) ^ do set %%L if "%WW1%"=="" exit /B 1 for /F "usebackq" %%L ^ in (`%IMG7%magick identify ^ -precision 9 ^ -format "FACT=%%[fx:pow(%wrsMAX_SCALE%/%wrsMIN_SCALE%,1/%wrsnSTEPS%)]" ^ xc:`) ^ do set %%L if %wrsDO_SUPER%==1 ( set SUPER_UP=-set option:distort:scale 4 set SUPER_FACT=4 ) else ( set SUPER_UP= set SUPER_FACT=1 ) if exist %TEMP_A% goto skipTempA set SCALE=%wrsMIN_SCALE% set NAMES_A= for /L %%i in (1,1,%wrsnSTEPS%) do ( @rem Maybe we should crop all scales so we never have virtual pixels. %IMG7%magick ^ %IN_A% ^ -distort SRT %WW1_2%,%HH1_2%,!SCALE!,0,%SRT_OFFS_X%,%SRT_OFFS_Y% ^ %SUPER_UP% ^ -distort DePolar 0 ^ -scale "x1^!" ^ ^( +clone ^) +append +repage ^ %wrsDEPTH% ^ %TEMP_A_BASE%_%%i%EXT% set NAMES_A=!NAMES_A! %TEMP_A_BASE%_%%i%EXT% for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 9 -format "SCALE=%%[fx:!SCALE!*%FACT%]" ^ xc:`) do set %%L ) %IMG7%magick %NAMES_A% -append +repage %TEMP_A% :skipTempA %IMG7%magick identify %TEMP_A% if not exist %TEMP_B% %IMG7%magick ^ %IN_B% ^ %SUPER_UP% ^ -distort DePolar 0 ^ -scale "x1^!" ^ %wrDEPTH% %TEMP_B% for /F "usebackq tokens=1-4 delims=()@, " %%R ^ in (`%IMG7%magick compare ^ %TEMP_A% %TEMP_B% ^ -similarity-threshold 0 -dissimilarity-threshold 1 -subimage-search ^ -metric %wrsMETRIC% -subimage-search ^ NULL: 2^>^&1`) ^ do ( set COMP_INT=%%R set COMP_FLT=%%S set COMP_XW=%%T set COMP_YW=%%U ) if "%COMP_FLT%" GTR "9" exit /B 1 echo COMP_FLT=%COMP_FLT% COMP_XW=%COMP_XW% COMP_YW=%COMP_YW% set DODGY=0 if %COMP_YW% EQU 0 set DODGY=1 if %COMP_YW% GEQ %wrsnSTEPS% set DODGY=1 for /F "usebackq" %%L ^ in (`%IMG7%magick identify ^ -precision 9 ^ -format "ANGLE=%%[fx:%COMP_XW%*360/(%SUPER_FACT%*%WW1%)]\nANGLE_ERR=%%[fx:360/(%SUPER_FACT%*%WW1%)]" ^ xc:`) ^ do set %%L rem What is the magnification here? set SCALE=%wrsMIN_SCALE% @for /L %%i in (1,1,%COMP_YW%) do @for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 9 -format "SCALE=%%[fx:!SCALE!*%FACT%]" ^ xc:`) do @set %%L echo SCALE=%SCALE% rem Cleanup: remove most temporary files. @for /L %%i in (1,1,%wrsnSTEPS%) do @del %TEMP_A_BASE%_%%i%EXT% 2>nul if %wrsDO_SCORE_SUB%==1 ( rem Test result. for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "cropX=%%[fx:int(min(%WW1%,%WW1%*%SCALE%)/2)]\ncropY=%%[fx:int(min(%HH1%,%HH1%*%SCALE%)/2)]" ^ xc:`) do set %%L for /F "usebackq" %%L in (`%IMG7%magick ^ %IN_A% ^ -distort SRT "%WW1_2%,%HH1_2%,%SCALE%,%ANGLE%,%SRT_OFFS_X%,%SRT_OFFS_Y%" ^ %IN_B% ^ -gravity Center ^ -crop !cropX!x!cropY!+0+0 +repage ^ -write r.png ^ -metric %wrsMETRIC% -format "ScoreSub=%%[distortion]" -compare ^ info:`) do set %%L ) if "%wrsDEBUG%"=="1" ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "cropX=%%[fx:int(min(%WW1%,%WW1%*%SCALE%)/2)]\ncropY=%%[fx:int(min(%HH1%,%HH1%*%SCALE%)/2)]" ^ xc:`) do set %%L %IMG7%magick ^ %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 "%WW1_2%,%HH1_2%,%SCALE%,%ANGLE%,%SRT_OFFS_X%,%SRT_OFFS_Y%" ^ ^( %IN_B% ^ ^( +clone -alpha Transparent ^ -shave 1x1 ^ -bordercolor None ^ -mattecolor #0f0 -frame 1x1 ^ -channel A -evaluate Multiply 0.75 ^ ^) ^ -composite ^ ^) ^ ^( -clone -1 ^ -gravity Center ^ -crop !cropX!x!cropY!+0+0 ^ -alpha Transparent ^ -shave 1x1 ^ -bordercolor None ^ -mattecolor #ff0 -frame 1x1 ^ -channel A -evaluate Multiply 0.75 ^ ^) ^ -layers merge ^ +repage ^ %DEBUG_FILE% ) call echoRestore endlocal & set wrsANGLE=%ANGLE%& set wrsANGLE_ERR=%ANGLE_ERR%& set wrsSCALE=%SCALE%& set wrsFACT=%FACT%& set wrsSCORE=%COMP_FLT%& set wrsSCORE_SUB=%ScoreSub%& set wrsDODGY=%DODGY%
All images on this page were created by the commands shown, using:
%IMG7%magick -version
Version: ImageMagick 7.1.0-42 Q16-HDRI x64 396d87c:20220709 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI OpenCL Delegates (built-in): bzlib cairo freetype gslib heic jng jp2 jpeg jxl lcms lqr lzma openexr pangocairo png ps raqm raw rsvg tiff webp xml zip zlib Compiler: Visual Studio 2022 (193231332)
To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF to JPG.
Source file for this web page is whatrotscale.h1. To re-create this web page, run "procH1 whatrotscale".
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.0 13-May-2014.
Page created 11-Aug-2022 01:20:51.
Copyright © 2022 Alan Gibson.