Using FFT, we find relative rotation of images invariant to scale, translation, or other factors.
The page What rotation? gives a method for finding the angle of rotation between two images, assuming:
However, when we have photographs or frames from a video, the camera has probably rotated through multiple axes, so the second condition is false, and the problem is more difficult.
The problem can be solved with Fast Fourier Transformations (FFT), finding the relative rotation even when there is also scaling and translation. A byproduct of this method also finds the best translation, after applying the rotation.
In priciple, the method is simple: find the FFT spectrum of each image, and apply the What rotation? method to those. But there are some complications.
For a worked example, we use two images from the What rotation? page.
wr_src1.png |
|
wr_src2.png |
Find the FFT spectrum of each image. Ensure the same level adjustments are made to both.
call %PICTBAT%fwdFft ^ wr_src1.png ^ wrf_s1_XX.miff ^ wrf_s1_ |
|
call %PICTBAT%fwdFft ^ wr_src2.png ^ wrf_s2_XX.miff ^ wrf_s2_ ^ %wrf_s1__max% ^ %wrf_s1__mean% |
The dominant features look suspiciously like artefacts from the image boundaries. Adust the images so they both self-tile.
call %PICTBAT%deEdgeForFft3 ^ wr_src1.png wrf_s1_de.miff . 4c |
|
call %PICTBAT%deEdgeForFft3 ^ wr_src2.png wrf_s2_de.miff . 4c |
Do the FFT on these de-edged versions:
call %PICTBAT%fwdFft ^ wrf_s1_de.miff ^ wrf_s1de_XX.miff ^ wrf_s1_ |
|
call %PICTBAT%fwdFft ^ wrf_s2_de.miff ^ wrf_s2de_XX.miff ^ wrf_s2_ ^ %wrf_s1__max% ^ %wrf_s1__mean% |
That's better. Now apply the whatRot method on the spectrum images. (Beware: whatRot.bat finds the rotation of the inscribed circles of the spectrums, so it discards some high-frequency data.) As mentioned in What rotation: False results, we need siDIV_DIM.
set siDIV_DIM=40 call %PICTBAT%whatRot wrf_s1de_spec.miff wrf_s2de_spec.miff if ERRORLEVEL 1 exit /B 1 set siDIV_DIM= echo wrANGLE=%wrANGLE% wrANGLE_ERR=%wrANGLE_ERR% wrSCORE=%wrSCORE% wrSCORE_SUB=%wrSCORE_SUB% wrDODGY=%wrDODGY%
wrANGLE=279.9 wrANGLE_ERR=0.3 wrSCORE=0.0559544 wrSCORE_SUB=0.0629347 wrDODGY=0
The result is 280°, which is 180° away from the correct answer of 100°. This is fair enough; a Fourier transform of a line at 23° is the same as one at 180°+23°. By trying out both possibilities, we readily find which one is correct. The centre of these source images align. If they didn't, we would need to find the translation first.
%IM%convert ^ wr_src1.png ^ -rotate %wrANGLE% ^ +repage ^ wr_src2.png ^ -gravity Center ^ -compose Difference -composite ^ wrf_diff.miff |
|
for /F "usebackq" %%L in (`%IM%identify ^ -format "angp180=%%[fx:%wrANGLE%+180]" ^ xc:`) do set %%L %IM%convert ^ wr_src1.png ^ -rotate %angp180% ^ +repage ^ wr_src2.png ^ -gravity Center ^ -compose Difference -composite ^ wrf_diff2.miff |
In a script, we could take a suitable crop and find the average lightness. The result that is closest to black is the correct one.
However, we would generally need to use the script whatTrans.bat, and this can do a rotation first and return a score.
call %PICTBAT%whatTrans wr_src1.png wr_src2.png 1 %wrANGLE% if ERRORLEVEL 1 exit /B 1 echo wtX=%wtX% wtY=%wtY% wtXY=%wtXY% wtXYneg=%wtXYneg% wtSCORE=%wtSCORE% wtDODGY=%wtDODGY%
wtX=-3 wtY=1 wtXY=-3+1 wtXYneg=+3-1 wtSCORE=0.124719 wtDODGY=0
call %PICTBAT%whatTrans wr_src1.png wr_src2.png 1 %angp180% 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.00649212 wtDODGY=0
The second score is lower, so %angp180% is the correct angle and wtX,wtY give the displacements of the centres.
%IM%convert ^ wr_src1.png ^ -rotate %angp180% ^ +repage ^ wr_src2.png ^ -gravity Center ^ -geometry %wtXY% ^ -compose Difference -composite ^ wrf_diff3.miff |
We implement the above in the script whatRotFft.bat. This calls whatRot.bat on the spectrums to find the rotation, then calls whatTrans.bat on the original images to find the best translation, and which of the two possible rotations is the correct one.
We will create debugging images:
set wrfDEBUG=1
We try the script with example images from the What translation? page.
call %PICTBAT%whatRotFft ^ wt_src1.png wt_src2.png wrf_ret12. if ERRORLEVEL 1 goto :error %IM%convert %wrf_ret12.DEBUG_FILE% wrf_dbg12.jpg set wrf_ret12. wrf_ret12.ANGLE=0 wrf_ret12.ANGLE_ERR=0.3 wrf_ret12.DEBUG_FILE=wrf_a_dbg.miff wrf_ret12.DODGY=0 wrf_ret12.SCORE=0.004911 wrf_ret12.X=-72 wrf_ret12.XY=-72-42 wrf_ret12.XYneg=+72+42 wrf_ret12.Y=-42 This is the correct solution. |
|
call %PICTBAT%whatRotFft ^ wt_src1.png wt_src3.png wrf_ret13. if ERRORLEVEL 1 goto :error %IM%convert %wrf_ret13.DEBUG_FILE% wrf_dbg13.jpg set wrf_ret13. wrf_ret13.ANGLE=3.9 wrf_ret13.ANGLE_ERR=0.3 wrf_ret13.DEBUG_FILE=wrf_a_dbg.miff wrf_ret13.DODGY=0 wrf_ret13.SCORE=0.134195 wrf_ret13.X=-67 wrf_ret13.XY=-67+26 wrf_ret13.XYneg=+67-26 wrf_ret13.Y=26 This is NOT the correct solution. |
|
call %PICTBAT%whatRotFft ^ wt_src1.png wt_src4.png wrf_ret14. if ERRORLEVEL 1 goto :error %IM%convert %wrf_ret14.DEBUG_FILE% wrf_dbg14.jpg set wrf_ret14. wrf_ret14.ANGLE=345 wrf_ret14.ANGLE_ERR=0.3 wrf_ret14.DEBUG_FILE=wrf_a_dbg.miff wrf_ret14.DODGY=0 wrf_ret14.SCORE=0.134907 wrf_ret14.X=-68 wrf_ret14.XY=-68-46 wrf_ret14.XYneg=+68+46 wrf_ret14.Y=-46 This is NOT the correct solution. |
The images have a lot of noisy non-aligned high-frequency detail, and images #3 and #4 overlap #1 by only about 50%. This has cased the wrong solution to be found. By setting wrCROP_PC, we reduce the influence of high-frequency data on the search.
set wrCROP_PC=25
call %PICTBAT%whatRotFft ^ wt_src1.png wt_src2.png wrf_ret12_cr. if ERRORLEVEL 1 goto :error %IM%convert %wrf_ret12_cr.DEBUG_FILE% wrf_dbg12_cr.jpg set wrf_ret12_cr. wrf_ret12_cr.ANGLE=0 wrf_ret12_cr.ANGLE_ERR=1.2 wrf_ret12_cr.DEBUG_FILE=wrf_a_dbg.miff wrf_ret12_cr.DODGY=0 wrf_ret12_cr.SCORE=0.004911 wrf_ret12_cr.X=-72 wrf_ret12_cr.XY=-72-42 wrf_ret12_cr.XYneg=+72+42 wrf_ret12_cr.Y=-42 |
|
call %PICTBAT%whatRotFft ^ wt_src1.png wt_src3.png wrf_ret13_cr. if ERRORLEVEL 1 goto :error %IM%convert %wrf_ret13.DEBUG_FILE% wrf_dbg13_cr.jpg set wrf_ret13_cr. wrf_ret13_cr.ANGLE=100.8 wrf_ret13_cr.ANGLE_ERR=1.2 wrf_ret13_cr.DEBUG_FILE=wrf_a_dbg.miff wrf_ret13_cr.DODGY=1 wrf_ret13_cr.SCORE=0.1216 wrf_ret13_cr.X=-75 wrf_ret13_cr.XY=-75-41 wrf_ret13_cr.XYneg=+75+41 wrf_ret13_cr.Y=-41 |
|
call %PICTBAT%whatRotFft ^ wt_src1.png wt_src4.png wrf_ret14_cr. if ERRORLEVEL 1 goto :error %IM%convert %wrf_ret14.DEBUG_FILE% wrf_dbg14_cr.jpg set wrf_ret14_cr. wrf_ret14_cr.ANGLE=261.6 wrf_ret14_cr.ANGLE_ERR=1.2 wrf_ret14_cr.DEBUG_FILE=wrf_a_dbg.miff wrf_ret14_cr.DODGY=0 wrf_ret14_cr.SCORE=0.123699 wrf_ret14_cr.X=-73 wrf_ret14_cr.XY=-73-44 wrf_ret14_cr.XYneg=+73+44 wrf_ret14_cr.Y=-44 |
This has found the correct solutions, more or less.
We see by eye that the results are improved. But the scores have not improved by much, so they they are not a reliable indicator of whether there was a problem or whether cropping the spectrum has solved it.
A possible solution: blur or downsize the image and examine the standard deviaton. If it is below a certain threshold, we have this problem. I haven't worked out the details.
Unset the cropping:
set wrCROP_PC=
Here are crops from two photographs of a panorama sequence. The crops are of the overlapping area, more or less.
%IM%convert ^ -delay 100 -loop 0 ^ ( op_3-4_ra_sm.png ^ -bordercolor Red -border 2 ^ ) ^ ( op_3-4_rb_sm.png ^ -bordercolor Green -border 2 ^ ) ^ -layers optimize ^ wrf_clds.gif |
The flicker comparison shows that the first image (with red border) needs rotating slightly clockwise to align with the second (with green border). The axis of rotation is not at the centre of the images, but somewhat lower and to the left.
We can find the rotation and translation to align the images.
call %PICTBAT%whatRotFft op_3-4_ra_sm.png op_3-4_rb_sm.png wrf_clds. if ERRORLEVEL 1 goto error set wrf_clds.
wrf_clds.ANGLE=0 wrf_clds.ANGLE_ERR=0.2 wrf_clds.DEBUG_FILE=wrf_a_dbg.miff wrf_clds.DODGY=0 wrf_clds.SCORE=0.0281182 wrf_clds.X=-71 wrf_clds.XY=-71-19 wrf_clds.XYneg=+71+19 wrf_clds.Y=-19
The script thinks the best alignment is with zero rotation. The problem is that the very soft image detail is swamped by the edge effects. We could soften the edges further, but there is no obvious way of calculating the required degree of softening. Instead, we will increase the contrast in the images.
We normalise both images so all channels have a mean of 0.5 and standard deviation of 0.17. (SD=0.17 is a reasonable value for "ordinary" photographs.)
call %PICTBAT%imgMnSd ^ op_3-4_ra_sm.png ^ 0.5 0.17 ^ wrf_clds_na.miff |
|
call %PICTBAT%imgMnSd ^ op_3-4_rb_sm.png ^ 0.5 0.17 ^ wrf_clds_nb.miff |
|
%IM%convert ^ -delay 100 -loop 0 ^ ( wrf_clds_na.miff ^ -bordercolor Red -border 2 ^ ) ^ ( wrf_clds_nb.miff ^ -bordercolor Green -border 2 ^ ) ^ -layers optimize ^ wrf_clds_nab.gif |
Find the rotation and translation of those normalised images:
call %PICTBAT%whatRotFft wrf_clds_na.miff wrf_clds_nb.miff wrf_clds_n. if ERRORLEVEL 1 goto error set wrf_clds_n.
wrf_clds_n.ANGLE=8.4 wrf_clds_n.ANGLE_ERR=0.2 wrf_clds_n.DEBUG_FILE=wrf_a_dbg.miff wrf_clds_n.DODGY=0 wrf_clds_n.SCORE=0.0492858 wrf_clds_n.X=8 wrf_clds_n.XY=+8+10 wrf_clds_n.XYneg=-8-10 wrf_clds_n.Y=10
The numbers look more sensible. A blink-comparison shows the result. As well as writing the animated GIF, we write a multi-image MIFF.
-repage has "!" to add extra offset to the offset caused by -rotate. %IM%convert ^ -delay 100 -loop 0 ^ ( \prose\pictures\wrf_clds_na.miff ^ -bordercolor Red -border 2 ^ -rotate %wrf_clds_n.ANGLE% ^ -repage "%wrf_clds_n.XY%^!" ^ ) ^ ( \prose\pictures\wrf_clds_nb.miff ^ -bordercolor Green -border 2 ^ ) ^ -layers trim-bounds ^ +write wrf_clds2.miff ^ wrf_clds2.gif |
The result looks good. It can't be improved by further rotation or translation. Would scaling improve the result? We use whatScale.bat on crops of the multi-image MIFF.
%IM%convert ^ wrf_clds2.miff ^ -layers coalesce ^ -gravity Center -crop 50%%x50%%+0+0 +repage ^ wrf_clds2c.miff call %PICTBAT%whatScale wrf_clds2c.miff[0] wrf_clds2c.miff[1] if ERRORLEVEL 1 goto error set ws
wsDODGY=0 wsFACT=1.01104669 wsSCALE=0.998978955 wsSCORE=0.0376923 wsSCORE_SUB=0.0549892
The result, wsSCALE, is more or less 1.0. (wsSCALE/wsFACT <= 1.0 and wsSCALE*wsFACT >= 1.0.) So scaling won't improve the result.
For ordinary but "de-edged" photographs, the method works well. After finding the rotation x°, we also need to find the translation in order to resolve whether the angle is x or (x+180)°.
The examples on this page are more difficult; the input images of stony concrete slabs and clouds have very little low-frequency detail. The clouds have very little high-frequency data either. For the slabs, this is corrected by removing low-level detail (with wrCROP=25). For the clouds, the images are first normalised to a more sensible standard deviation with the script imgMnSd.bat.
Further tests suggest that setting wrCROP=25 doesn't harm accuracy (though it does reduce precision).
Here's a tricky problem: given two rectangles that overlap by scale, rotation and translation, suppose we want each output pixel to come from one or other input. Where should the minimum error boundary cuts (MEBCs), aka darkpaths, be?
The edges of the two rectangles intersect at up to eight locations. We might have eight darkpaths between these locations and some central location.
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 angle of rotation for %1 to best match %2, and a score. @rem @rem Also uses: @rem wrDO_SUPER if ==0, no supersampling will be done. @rem wrDO_SCORE_SUB if ==0, wrSCORE_SUB will not be found. @rem wrMETRIC default RMSE @rem wrDEBUG if 1, creates images for debugging @rem wrDELTEMP if not 0, will remove temporary file. @rem wrCROP_PC percentage for central crops before unrolling [100]. @rem @rem Returns: @rem wrANGLE (degrees) rotate first image by this to match second. @rem wrANGLE_ERR (degrees) margin of error @rem wrSCORE (0.0 to 1.0, more or less). @rem wrSCORE_SUB (0.0 to 1.0, more or less). @rem wrDODGY 0 or 1. 1 means the result is unreliable. @rem @rem Updated: @rem 19-August-2016 Added wrCROP_PC. @rem 10-August-2022 for IM v7. @rem @if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave if "%wrCROP_PC%"=="" set wrCROP_PC=100 set TMPEXT=.miff call %PICTBAT%setInOut %1 wr set IN_A=%INFILE% set TEMP_A=%TEMP%\%~n1_wr_x2%TMPEXT% call %PICTBAT%setInOut %2 wr set IN_B=%INFILE% set TEMP_B=%TEMP%\%~n2_wr%TMPEXT% if %IN_B%==%IN_A% set TEMP_B=%TEMP%\%~n2_B_wr%TMPEXT% set DEBUG_FILE=wr_%~n1_%~n2_dbg%EXT% if not "%wrDELTEMP%"=="0" ( del %TEMP_A% 2>nul del %TEMP_B% 2>nul ) if "%wrMETRIC%"=="" set wrMETRIC=RMSE for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "WW1=%%[fx:w*%wrCROP_PC%/100]\nHH1=%%[fx:h*%wrCROP_PC%/100]\nIS_SMALL=%%[fx:max(w,h)*%wrCROP_PC%/100>1000?0:1]" ^ %IN_A%`) do set %%L if "%wrDO_SCORE_SUB%"=="" set wrDO_SCORE_SUB=%IS_SMALL% if "%wrDO_SUPER%"=="" set wrDO_SUPER=%IS_SMALL% if %wrDO_SUPER%==1 ( set SUPER_UP=-set option:distort:scale 4 set SUPER_DN=-resize 100x25%% set SUPER_FACT=4 ) else ( set SUPER_UP= set SUPER_DN= set SUPER_FACT=1 ) echo %0: SUPER_FACT=%SUPER_FACT% WW1=%WW1% HH1=%HH1% if %wrCROP_PC%==100 ( set sCROP= ) else ( set sCROP=-gravity center -crop %wrCROP_PC%%%x%wrCROP_PC%%%+0+0 +repage ) if not exist %TEMP_A% %IMG7%magick ^ %IN_A% ^ %sCROP% ^ %SUPER_UP% ^ -distort DePolar 0 ^ %SUPER_DN% ^ ^( +clone ^) +append +repage ^ +depth ^ %TEMP_A% if not exist %TEMP_B% %IMG7%magick convert ^ %IN_B% ^ %sCROP% ^ %SUPER_UP% ^ -distort DePolar 0 ^ %SUPER_DN% ^ +depth ^ %TEMP_B% call %PICTBAT%srchImg %TEMP_A% %TEMP_B% if ERRORLEVEL 1 exit /B 1 set DODGY=0 for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 9 ^ -format "ANGLE=%%[fx:%siCOMP_XW%*360/(%SUPER_FACT%*%WW1%)]\nANGLE_ERR=%%[fx:360/(%SUPER_FACT%*%WW1%)]" ^ xc:`) do set %%L if %ANGLE%==360 set ANGLE=0 if %wrDO_SCORE_SUB%==1 ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "DIM_2=%%[fx:int(%WW1%<%HH1%?%WW1%/2:%HH1%/2)]" ^ xc:`) do set %%L for /F "usebackq" %%L in (`%IMG7%magick ^ %IN_A% ^ -rotate %ANGLE% +repage ^ %IN_B% ^ -gravity Center ^ -crop !DIM_2!x!DIM_2!+0+0 +repage ^ -metric %wrMETRIC% -format "ScoreSub=%%[distortion]" -compare ^ info:`) do set %%L ) if "%wrDEBUG%"=="1" ( for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "DIM_2=%%[fx:int(%WW1%<%HH1%?%WW1%/2:%HH1%/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 ^ ^) ^ -compose Over -composite ^ -virtual-pixel None ^ +distort SRT 1,%ANGLE% ^ ^( %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 !DIM_2!x!DIM_2!+0+0 ^ -alpha Transparent ^ -shave 1x1 ^ -bordercolor None ^ -mattecolor #ff0 -frame 1x1 ^ -channel A -evaluate Multiply 0.75 ^ ^) ^ -layers merge ^ +repage ^ %DEBUG_FILE% ) if not "%wrDELTEMP%"=="0" ( del %TEMP_A% 2>nul del %TEMP_B% 2>nul ) call echoRestore endlocal & set wrANGLE=%ANGLE%& set wrANGLE_ERR=%ANGLE_ERR%& set wrSCORE=%siCOMP_FLT%& set wrSCORE_SUB=%ScoreSub%& set wrDODGY=%DODGY%
rem From input image %1, rem makes outputs %2 (must contain XX) rem _mag _ph and _spec. rem Also creates environment variable prefix %3 with suffixes rem _ww input image width rem _hh input image height rem _sqdim width and height of outputs. rem _max maximum value of magnitude component rem _scale scale rem %4 Assumed maximum value of magnitude. rem %5 Assumed mean value of magnitude. @rem @rem Also uses: @rem fftPOST_SPEC eg -resize "600x600>" @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 fft set OUTFILE=%2 if "%OUTFILE%"=="." set OUTFILE= if "%OUTFILE%"=="" set OUTFILE=%BASENAME%_fft_XX%EXT% set ENV_PREF=%3 if "%ENV_PREF%"=="." set ENV_PREF= if "%ENV_PREF%"=="" set ENV_PREF=fft set ASS_MAX=%4 if "%ASS_MAX%"=="." set ASS_MAX= set ASS_MEAN=%5 if "%ASS_MEAN%"=="." set ASS_MEAN= set OUT_MAG=%OUTFILE:XX=mag% set OUT_PH=%OUTFILE:XX=ph% set OUT_SPEC=%OUTFILE:XX=spec% if "%OUT_MAG%"=="%OUT_PH%" ( echo Bad OUTFILE [%OUTFILE%] -- should contain XX exit /B 1 ) set WW= for /F "usebackq" %%L in (`%IM%identify ^ -format "WW=%%w\nHH=%%h" ^ %INFILE%`) do set %%L if "%WW%"=="" exit /B 1 %IMDEV%convert ^ %INFILE% ^ -fft ^ -define quantum:format=floating-point ^ ( -clone 0 -write %OUT_MAG% +delete ) ^ -delete 0 ^ %OUT_PH% if ERRORLEVEL 1 ( echo No FFT exit /B 1 ) for /F "usebackq" %%L in (`%IMDEV%convert ^ %OUT_MAG% ^ -precision 19 ^ -format "SCALE=%%[fx:exp(log(mean)/log(0.5))]\nWH=%%w\nMEAN=%%[fx:mean]\nMIN=%%[fx:minima]\nMAX=%%[fx:maxima]" ^ info:`) do set %%L if not "%ASS_MAX%"=="" set MAX=%ASS_MAX% if not "%ASS_MEAN%"=="" set MEAN=%ASS_MEAN% echo %0: SCALE=%SCALE% MIN=%MIN% MEAN=%MEAN% MAX=%MAX% for /F "usebackq" %%L in (`%IMDEV%identify ^ -precision 19 ^ -format "SCALE=%%[fx:exp(log(%MEAN%/%MAX%)/log(0.5))]" ^ xc:`) do set %%L echo %0: SCALE=%SCALE% MIN=%MIN% MEAN=%MEAN% MAX=%MAX% %IMDEV%convert ^ %OUT_MAG% ^ -evaluate divide %MAX% ^ -evaluate log %SCALE% ^ %fftPOST_SPEC% ^ %OUT_SPEC% call echoRestore endlocal & set %ENV_PREF%_ww=%WW%&^ set %ENV_PREF%_hh=%HH%&^ set %ENV_PREF%_sqdim=%WH%&^ set %ENV_PREF%_max=%MAX%&^ set %ENV_PREF%_scale=%SCALE%&^ set %ENV_PREF%_mean=%MEAN%
rem Given %1 is an ordinary image, rem makes output %2 rem with edges fade to colour %3 [gray(50%)], rem up to distance %4 from edges [4c]. rem %5 is any of LTRB, any case. [LTRB] @rem @rem %4 is pixels, @rem or can be suffixed with p or % or c, for proportion or percentage of minimum dimension. @rem @rem Also uses: @rem @rem deefGRAD_MOD a modifier for the gradients, eg for cosine-bell rolloff: @rem -function sinusoid 0.5,-90,0.5,0.5 @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 deff if not "%2"=="" if not "%2"=="." set OUTFILE=%2 set EDGE_COL=%3 if "%EDGE_COL%"=="." set EDGE_COL= if "%EDGE_COL%"=="" set EDGE_COL=gray(50%%) set EDGE_DIST=%4 if "%EDGE_DIST%"=="." set EDGE_DIST= if "%EDGE_DIST%"=="" set EDGE_DIST=4c set EDGES=%5 if "%EDGES%"=="." set EDGES= if "%EDGES%"=="" set EDGES=LTRB call %PICTBAT%getLtrb %EDGES% if %ltrbERR%==1 exit /B 1 set WW= for /F "usebackq" %%L in (`%IM%convert ^ %INFILE% ^ -format "WW=%%w\nHH=%%h\nMID_DIM=%%[fx:min(w,h)]" ^ info:`) do set %%L if "%WW%"=="" exit /B 1 set ED_LAST=%EDGE_DIST:~-1% if "%ED_LAST%"=="^%" set ED_LAST=c if /I "%ED_LAST%"=="c" ( for /F "usebackq" %%L in (`%IM%identify ^ -format "nED=%%[fx:%MID_DIM%*%EDGE_DIST:~0,-1%/100]" ^ xc:`) do set %%L ) else if /I "%ED_LAST%"=="p" ( for /F "usebackq" %%L in (`%IM%identify ^ -format "nED=%%[fx:%MID_DIM%*%EDGE_DIST:~0,-1%]" ^ xc:`) do set %%L ) else ( set nED=%EDGE_DIST% ) set sL= set sT= set sR= set sB= if %ltrbL%==1 set sL=( gradient:white-black -rotate -90 %deefGRAD_MOD% ) -gravity West -composite if %ltrbT%==1 set sT=( gradient:white-black %deefGRAD_MOD% ) -gravity North -composite if %ltrbR%==1 set sR=( gradient:white-black -rotate 90 %deefGRAD_MOD% ) -gravity East -composite if %ltrbB%==1 set sB=( gradient:black-white %deefGRAD_MOD% ) -gravity South -composite %IM%convert ^ %INFILE% ^ ( -size %WW%x%HH% ^ xc:%EDGE_COL% ^ ) ^ ( -size %WW%x%HH% ^ xc:Black ^ -compose Lighten ^ -size %WW%x%nED% ^ %sT% ^ %sB% ^ -size %HH%x%nED% ^ %sL% ^ %sR% ^ ) ^ -compose Over -composite ^ %OUTFILE% if ERRORLEVEL 1 exit /B 1 call echoRestore @endlocal
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. @rem 10-August-2022 for IM v7. @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 (`%IMG7%magick 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% %IMG7%magick ^ %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%
rem Given same-size images %1 and %2, rem finds angle of rotation for %1 to best match %2, and a score, by FFT. rem Also calls whatRot.bat to disambiguate the angle and returns those results. rem %3 is prefix for environment variables for numeric results. [wt] @rem @rem Also uses: @rem wrfDEBUG if 1, creates image for debugging @rem @rem Returns: @rem All the returns from whatTrans.bat. @if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave set ENV_PREF=%3 if "%ENV_PREF%"=="." set ENV_PREF= if "%ENV_PREF%"=="" set ENV_PREF=wrf. set TMPEXT=.miff set TMP_DIR=wrftmp if not exist %TMP_DIR% md %TMP_DIR% call %PICTBAT%setInOut %1 wrf set IN_A=%INFILE% set TEMP_A=%TMP_DIR%\%~n1_wrf_x2%TMPEXT% set FFT_A=%TMP_DIR%\%~n1_wrf_XX_x2%TMPEXT% call %PICTBAT%setInOut %2 wrf set IN_B=%INFILE% set TEMP_B=%TMP_DIR%\%~n2_wrf%TMPEXT% if %IN_B%==%IN_A% set TEMP_B=%TMP_DIR%\%~n2_B_wrf%TMPEXT% set FFT_B=%TMP_DIR%\%~n2_wrf_XX%TMPEXT% if %IN_B%==%IN_A% set FFT_B=%TMP_DIR%\%~n2_B_wrf_XX%TMPEXT% set SPEC_A=%FFT_A:XX=spec% set SPEC_B=%FFT_B:XX=spec% rem set DEBUG_FILE=wrf_%~n1_%~n2_dbg%EXT% if not "%wrDELTEMP%"=="0" ( del %TEMP_A% 2>nul del %TEMP_B% 2>nul ) call %PICTBAT%deEdgeForFft3 %IN_A% %TEMP_A% if ERRORLEVEL 1 exit /B 1 call %PICTBAT%deEdgeForFft3 %IN_B% %TEMP_B% if ERRORLEVEL 1 exit /B 1 call %PICTBAT%fwdFft ^ %TEMP_A% ^ %FFT_A% ^ wrf_s1_ if ERRORLEVEL 1 exit /B 1 call %PICTBAT%fwdFft ^ %TEMP_B% ^ %FFT_B% ^ wrf_s2_ ^ %wrf_s1__max% ^ %wrf_s1__mean% if ERRORLEVEL 1 exit /B 1 set siDIV_DIM=100 set wrDEBUG=0 call %PICTBAT%whatRot %SPEC_A% %SPEC_B% if ERRORLEVEL 1 exit /B 1 set siDIV_DIM= echo %0: wrANGLE=%wrANGLE% wrANGLE_ERR=%wrANGLE_ERR% wrSCORE=%wrSCORE% wrSCORE_SUB=%wrSCORE_SUB% wrDODGY=%wrDODGY% for /F "usebackq" %%L in (`%IM%identify ^ -format "angm180=%%[fx:%wrANGLE%-180]" ^ xc:`) do set %%L set wtDEBUG=%wrfDEBUG% set wtDEBUG_OUTFILE=wrf_a_dbg.miff call %PICTBAT%whatTrans %IN_A% %IN_B% 1 %wrANGLE% wrfA. if ERRORLEVEL 1 exit /B 1 set wtDEBUG_OUTFILE=wrf_b_dbg.miff call %PICTBAT%whatTrans %IN_A% %IN_B% 1 %angm180% out. if ERRORLEVEL 1 exit /B 1 for /F "usebackq" %%L in (`%IM%identify ^ -format "IsFirst=%%[fx:%wrfA.SCORE%<%out.SCORE%?1:0]" ^ xc:`) do set %%L rem FIXME: keep the first debug file. if %IsFirst%==1 ( set out.X=%wrfA.X% set out.Y=%wrfA.Y% set out.XY=%wrfA.XY% set out.XYneg=%wrfA.XYneg% set out.SCORE=%wrfA.SCORE% set out.DODGY=%wrfA.DODGY% set out.DEBUG_FILE=wrf_a_dbg.miff set out.ANGLE=%wrANGLE% ) else ( set out.DEBUG_FILE=wrf_b_dbg.miff set out.ANGLE=%angm180% ) set out.ANGLE_ERR=%wrANGLE_ERR% set out. call echoRestore endlocal &set %ENV_PREF%X=%out.X%&^ set %ENV_PREF%Y=%out.Y%&^ set %ENV_PREF%XY=%out.XY%&^ set %ENV_PREF%XYneg=%out.XYneg%&^ set %ENV_PREF%SCORE=%out.SCORE%&^ set %ENV_PREF%DODGY=%out.DODGY%&^ set %ENV_PREF%ANGLE=%out.ANGLE%&^ set %ENV_PREF%ANGLE_ERR=%out.ANGLE_ERR%&^ set %ENV_PREF%DEBUG_FILE=%out.DEBUG_FILE%
All images on this page were created by the commands shown, using:
%IM%identify -version
Version: ImageMagick 6.9.9-50 Q16 x64 2018-06-02 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 gslib heic jng jp2 jpeg lcms lqr lzma openexr pangocairo png ps raw rsvg tiff webp xml zlib
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 whatrotfft.h1. To re-create this web page, run "procH1 whatrotfft".
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 16-August-2016.
Page created 11-Aug-2022 01:15:55.
Copyright © 2022 Alan Gibson.