snibgo's ImageMagick pages

Find four corners

Three simple methods.

Suppose we have a white shape on a black background, and the shape is approximately a quadrangle. (That is, it has four approximately straight lines, and four angles.) We want to find the coordinates of its corners.

Sample inputs

In most of these cases, the white shape is entirely surrounded by a black background. If this isn't true (for example, when a white corner is at an image edge), these methods may not work. For use on general images, it may be helpful to add a black border before running scripts.

An exact rectangle.

%IMG7%magick ^
  -size 300x200 xc:Black ^
  -fill White ^
  -draw "polygon 30,40 230,40 230,140 30,140" ^
  f4c_rect.png
f4c_rect.png

A general quadrangle.

%IMG7%magick ^
  -size 300x200 xc:Black ^
  -fill White ^
  -draw "polygon 50,50 250,60 230,140 60,150" ^
  f4c_quad.png
f4c_quad.png

A more general shape.

is_easy_mainland.png

is_easy_mainland.png

A more distorted quadrangle.

%IMG7%magick ^
  -size 300x200 xc:Black ^
  -fill White ^
  -draw "polygon 30,30 250,60 220,130 60,150" ^
  f4c_quadf.png
f4c_quadf.png

This is a large image, reduced for the web.

set PHOTO_SRC=pbp_threshpap.tiff

set WEB_SIZE=-resize 600x600

%IMG7%magick ^
  %PHOTO_SRC% ^
  %WEB_SIZE% ^
  f4c_phsrc_sm.jpg
f4c_phsrc_sm.jpg

Method: sub-image searching

The script find4cornSub.bat does a subimage-search for a small image that represents a corner.

To get a reasonble performance on large images, it shrinks the image to 1/20 of the full size, searches that, and uses the result to search a small crop of the full-size image. If the result is a poor match, it repeats repeats with a less aggressive shrinkage, until the result is good or the shrinkage factor reaches 1.0.

call %PICTBAT%find4cornSub ^
  f4c_rect.png F4C_rect_sub f4c_rect_sub_out.png
if ERRORLEVEL 1 goto :error

set F4C_rect_sub 
F4C_rect_sub.BL.x=30
F4C_rect_sub.BL.y=141
F4C_rect_sub.BR.x=231
F4C_rect_sub.BR.y=141
F4C_rect_sub.TL.x=30
F4C_rect_sub.TL.y=40
F4C_rect_sub.TR.x=231
F4C_rect_sub.TR.y=40
f4c_rect_sub_out.png
call %PICTBAT%find4cornSub ^
  f4c_quad.png F4C_quad_sub f4c_quad_sub_out.png
if ERRORLEVEL 1 goto :error

set F4C_quad_sub 
F4C_quad_sub.BL.BAD=1
F4C_quad_sub.BL.x=60
F4C_quad_sub.BL.y=151
F4C_quad_sub.BR.BAD=1
F4C_quad_sub.BR.x=231
F4C_quad_sub.BR.y=141
F4C_quad_sub.TL.x=50
F4C_quad_sub.TL.y=50
F4C_quad_sub.TR.BAD=1
F4C_quad_sub.TR.x=251
F4C_quad_sub.TR.y=60
f4c_quad_sub_out.png
call %PICTBAT%find4cornSub ^
  is_easy_mainland.png F4C_iem_sub f4c_iem_sub_out.png
if ERRORLEVEL 1 goto :error

set F4C_iem_sub 
F4C_iem_sub.BL.BAD=1
F4C_iem_sub.BL.x=21
F4C_iem_sub.BL.y=174
F4C_iem_sub.BR.BAD=1
F4C_iem_sub.BR.x=295
F4C_iem_sub.BR.y=167
F4C_iem_sub.TL.x=18
F4C_iem_sub.TL.y=25
F4C_iem_sub.TR.x=298
F4C_iem_sub.TR.y=44
f4c_iem_sub_out.png
call %PICTBAT%find4cornSub ^
  f4c_quadf.png F4C_quadf_sub f4c_quadf_sub_out.png
if ERRORLEVEL 1 goto :error

set F4C_quadf_sub 
F4C_quadf_sub.BL.BAD=1
F4C_quadf_sub.BL.x=60
F4C_quadf_sub.BL.y=151
F4C_quadf_sub.BR.BAD=1
F4C_quadf_sub.BR.x=221
F4C_quadf_sub.BR.y=131
F4C_quadf_sub.TL.BAD=1
F4C_quadf_sub.TL.x=30
F4C_quadf_sub.TL.y=30
F4C_quadf_sub.TR.BAD=1
F4C_quadf_sub.TR.x=251
F4C_quadf_sub.TR.y=60
f4c_quadf_sub_out.png
call %PICTBAT%find4cornSub ^
  %PHOTO_SRC% F4C_ph_sub f4c_ph_sub_out.miff ". 200 20"
if ERRORLEVEL 1 goto :error

%IMG7%magick ^
  f4c_ph_sub_out.miff ^
  %WEB_SIZE% ^
  f4c_ph_sub_out_sm.jpg

set F4C_ph_sub 
F4C_ph_sub.BL.x=401
F4C_ph_sub.BL.y=6832
F4C_ph_sub.BR.x=2570
F4C_ph_sub.BR.y=4433
F4C_ph_sub.TL.x=1072
F4C_ph_sub.TL.y=2444
F4C_ph_sub.TR.x=4532
F4C_ph_sub.TR.y=798

Two calculated corners are bad.

f4c_ph_sub_out_sm.jpg

Method: blurred edges

The script find4cornEdgeBlr.bat finds edges, making them light while other pixels are black. Then it blurs this image so the lighter pixels show where corners occur in the edge; lightness represents the energy of the edge. In each quarter of the image, the lightest pixel is at the corner.

We could use any edge-detector. slopeMag.bat doesn't introduce stair-casing.

The blur needs to be fairly large to capture the corners of is_easy_mainland.png. However, large blurs also calculate the position of the corners to be slightly inside their true positions.

call %PICTBAT%find4cornEdgeBlr ^
  f4c_rect.png F4C_rect_be f4c_rect_be_out.png
if ERRORLEVEL 1 goto :error

set F4C_rect_be 
F4C_rect_be.BL.x=32
F4C_rect_be.BL.y=138
F4C_rect_be.BR.x=228
F4C_rect_be.BR.y=138
F4C_rect_be.TL.x=32
F4C_rect_be.TL.y=42
F4C_rect_be.TR.x=228
F4C_rect_be.TR.y=42
f4c_rect_be_out.png
call %PICTBAT%find4cornEdgeBlr ^
  f4c_quad.png F4C_quad_be f4c_quad_be_out.png
if ERRORLEVEL 1 goto :error

set F4C_quad_be 
F4C_quad_be.BL.x=62
F4C_quad_be.BL.y=148
F4C_quad_be.BR.x=229
F4C_quad_be.BR.y=138
F4C_quad_be.TL.x=53
F4C_quad_be.TL.y=52
F4C_quad_be.TR.x=247
F4C_quad_be.TR.y=62
f4c_quad_be_out.png
call %PICTBAT%find4cornEdgeBlr ^
  is_easy_mainland.png F4C_iem_be f4c_iem_be_out.png
if ERRORLEVEL 1 goto :error

set F4C_iem_be 
F4C_iem_be.BL.x=25
F4C_iem_be.BL.y=174
F4C_iem_be.BR.x=277
F4C_iem_be.BR.y=181
F4C_iem_be.TL.x=19
F4C_iem_be.TL.y=26
F4C_iem_be.TR.x=278
F4C_iem_be.TR.y=29
f4c_iem_be_out.png
call %PICTBAT%find4cornEdgeBlr ^
  f4c_quadf.png F4C_quadf_be f4c_quadf_be_out.png
if ERRORLEVEL 1 goto :error

set F4C_quadf_be 
F4C_quadf_be.BL.x=62
F4C_quadf_be.BL.y=148
F4C_quadf_be.BR.x=218
F4C_quadf_be.BR.y=129
F4C_quadf_be.TL.x=33
F4C_quadf_be.TL.y=33
F4C_quadf_be.TR.x=246
F4C_quadf_be.TR.y=62
f4c_quadf_be_out.png
call %PICTBAT%find4cornEdgeBlr ^
  %PHOTO_SRC% F4C_ph_be f4c_ph_be_out.miff ". 200 20"
if ERRORLEVEL 1 goto :error

%IMG7%magick ^
  f4c_ph_be_out.miff ^
  %WEB_SIZE% ^
  f4c_ph_be_out_sm.jpg

set F4C_ph_be 
F4C_ph_be.BL.x=2457
F4C_ph_be.BL.y=4545
F4C_ph_be.BR.x=3154
F4C_ph_be.BR.y=4559
F4C_ph_be.TL.x=229
F4C_ph_be.TL.y=3566
F4C_ph_be.TR.x=4384
F4C_ph_be.TR.y=808

Three calculated corners are bad.

f4c_ph_be_out_sm.jpg

Method: skeleton junctions

The script find4cornSkelJcn.bat finds slope magnitudes, thins that to a skeleton, and finds junctions.

call %PICTBAT%find4cornSkelJcn ^
  f4c_rect.png F4C_rect_sj f4c_rect_sj_out.png
if ERRORLEVEL 1 goto :error

set F4C_rect_sj 
F4C_rect_sj=dummy
f4c_rect_sj_out.png
call %PICTBAT%find4cornSkelJcn ^
  f4c_quad.png F4C_quad_sj f4c_quad_sj_out.png
if ERRORLEVEL 1 goto :error

set F4C_quad_sj 
F4C_quad_sj=dummy
f4c_quad_sj_out.png
call %PICTBAT%find4cornSkelJcn ^
  is_easy_mainland.png F4C_iem_sj f4c_iem_sj_out.png
if ERRORLEVEL 1 goto :error

set F4C_iem_sj 
F4C_iem_sj=dummy
f4c_iem_sj_out.png
call %PICTBAT%find4cornSkelJcn ^
  f4c_quadf.png F4C_quadf_sj f4c_quadf_sj_out.png
if ERRORLEVEL 1 goto :error

set F4C_quadf_sj 
F4C_quadf_sj=dummy
f4c_quadf_sj_out.png

This method is slow and useless for photos.

goto skipPhSj
call %PICTBAT%find4cornSkelJcn ^
  %PHOTO_SRC% F4C_ph_sj f4c_ph_sj_out.miff ". 200 20"
if ERRORLEVEL 1 goto :error

%IMG7%magick ^
  f4c_ph_sj_out.miff ^
  %WEB_SIZE% ^
  f4c_ph_sj_out_sm.jpg

set F4C_ph_sj 

:skipPhSj
@f4c_ph_sj.lis

[No image]

Method: polar distance

The script find4cornPolDist.bat trims to the shape, and crops the result into quarters. For each image quarter, it finds the point in the shape that is furthest from the centre.

To do this, it unrolls the trimmed image and crops horizontally into four pieces. In each piece, the x- and y-coordinates of the first white pixel give the polar coordinates (r,θ) of the point on the shape that is furthest from the centre.

From those polar coordinates (r,θ), it calculates the cartesian coordinates (x,y) within the trimmed image. Adding the canvas offsets of the trimmed image gives the cartesian coordinates within the input image.

With no supersampling, the results can be a few pixels out.

set SUPSAMP=300
call %PICTBAT%find4cornPolDist ^
  f4c_rect.png F4C_rect f4c_rect_out.png 1 %SUPSAMP%
if ERRORLEVEL 1 goto :error

set F4C_rect. 
F4C_rect.BL.x=30.10339695945033611
F4C_rect.BL.y=140.3764296963525453
F4C_rect.BR.x=230.3714560536350291
F4C_rect.BR.y=141.4198233212149773
F4C_rect.TL.x=30.36461144579273252
F4C_rect.TL.y=40.10118949384974485
F4C_rect.TR.x=231.1550924224147252
F4C_rect.TR.y=41.14730492899043668
f4c_rect_out.png
call %PICTBAT%find4cornPolDist ^
  f4c_quad.png F4C_quad f4c_quad_out.png 1 %SUPSAMP%
if ERRORLEVEL 1 goto :error

set F4C_quad. 
F4C_quad.BL.x=60.40053187367030318
F4C_quad.BL.y=150.0448281292977981
F4C_quad.BR.x=230.2853798997745685
F4C_quad.BR.y=140.1368977719358213
F4C_quad.TL.x=50.36461144579273252
F4C_quad.TL.y=50.10118949384974485
F4C_quad.TR.x=249.7286454069590604
F4C_quad.TR.y=61.72598611760867726
f4c_quad_out.png
call %PICTBAT%find4cornPolDist ^
  is_easy_mainland.png F4C_iem f4c_iem_out.png 1 %SUPSAMP%
if ERRORLEVEL 1 goto :error

set F4C_iem. 
F4C_iem.BL.x=20.49171353879049207
F4C_iem.BL.y=170.9534399432815235
F4C_iem.BR.x=299.253845752350685
F4C_iem.BR.y=156.4885074272688996
F4C_iem.TL.x=18.82702474247449231
F4C_iem.TL.y=25.80758398269517784
F4C_iem.TR.x=305.9016625050160201
F4C_iem.TR.y=72.29780734008974719
f4c_iem_out.png

The calculated top-right corner might not be the point that a human would choose.

In the following case, the bottom-right corner of the trimmed image contains the corner but the corner is not the point that is furthest from the centre.

call %PICTBAT%find4cornPolDist ^
  f4c_quadf.png F4C_quadf f4c_quadf_out.png 1 %SUPSAMP%
if ERRORLEVEL 1 goto :error

set F4C_quadf. 
F4C_quadf.BL.x=60.45381768096409303
F4C_quadf.BL.y=148.5990244994725913
F4C_quadf.BR.x=236.2632823965844011
F4C_quadf.BR.y=92.54227259236938608
F4C_quadf.TL.x=31.76557650798778809
F4C_quadf.TL.y=31.11131062417762649
F4C_quadf.TR.x=249.4124781883534752
F4C_quadf.TR.y=61.17866320227075505
f4c_quadf_out.png

The large photograph doesn't need the precision that supersampling would provide. Anyhow, it would eat all my memory.

set SUPSAMP=
call %PICTBAT%find4cornPolDist ^
  %PHOTO_SRC% F4C_phot f4c_phot_out.miff 1 %SUPSAMP% . ". 200 20"
if ERRORLEVEL 1 goto :error

%IMG7%magick ^
  f4c_phot_out.miff ^
  %WEB_SIZE% ^
  f4c_phot_out_sm.jpg

set F4C_phot. 
F4C_phot.BL.x=-1.441603801038127131
F4C_phot.BL.y=6998.557236573578848
F4C_phot.BR.x=4697.462514071969053
F4C_phot.BR.y=6806.11171921731875
F4C_phot.TL.x=6.489768650049882126
F4C_phot.TL.y=713.303920764495615
F4C_phot.TR.x=4606.788750352454372
F4C_phot.TR.y=809.0386471499668914
f4c_phot_out_sm.jpg

Two calculated corners are good, but two are bad. The script could easily identify one of the corners as bad, but not the other.

Conclusions

For the clean images of quadrangles, the subimage-searching and blurred edges methods (find4cornSub.bat and find4cornEdgeBlr.bat) work well, correctly identifying the corners. These methods also give plausible results for is_easy_mainland that has no "correct" answers.

The polar distance method (find4cornPolDist.bat) misidentifies a corner in f4c_quadf.png, and produces poorer results for is_easy_mainland.

None of the methods give good results for the photo of the book page. That needs a more sophisticated method, such as by finding the page edges and extrapolating the linear regression of those.

Addendum: perspective distortions

The script corn2limit.bat processes environment variables with the given prefix. From those with suffix ".BL.x" etc, it calculates various limits for left, top, right and bottom. In addition, it sets variable suffixes ".dst.L", ".dst.T", ".dst.R", ".dst.B" to the required limit type, and ".persp" to the comma-list of eight integers suitable for a perspective transformation from the input coordinates to the destination coordinates.

call %PICTBAT%corn2limit F4C_quad_sub mean

set F4C_quad_sub 
F4C_quad_sub.BL.BAD=1
F4C_quad_sub.BL.x=60
F4C_quad_sub.BL.y=151
F4C_quad_sub.BR.BAD=1
F4C_quad_sub.BR.x=231
F4C_quad_sub.BR.y=141
F4C_quad_sub.dst.B=146
F4C_quad_sub.dst.L=55
F4C_quad_sub.dst.R=241
F4C_quad_sub.dst.T=55
F4C_quad_sub.max.B=151
F4C_quad_sub.max.L=60
F4C_quad_sub.max.R=251
F4C_quad_sub.max.T=60
F4C_quad_sub.mean.B=146
F4C_quad_sub.mean.L=55
F4C_quad_sub.mean.R=241
F4C_quad_sub.mean.T=55
F4C_quad_sub.min.B=141
F4C_quad_sub.min.L=50
F4C_quad_sub.min.R=231
F4C_quad_sub.min.T=50
F4C_quad_sub.persp=50,50,55,55,251,60,241,55,231,141,241,146,60,151,55,146
F4C_quad_sub.TL.x=50
F4C_quad_sub.TL.y=50
F4C_quad_sub.TR.BAD=1
F4C_quad_sub.TR.x=251
F4C_quad_sub.TR.y=60

Do the perspective transformation:

%IMG7%magick ^
  f4c_quad.png ^
  -distort perspective %F4C_quad_sub.persp% ^
  f4c_persp.png
f4c_persp.png

As expected, the four input points have become the corners of a rectangle.

An alternative method is to draw blue and green lines, with cyan at the intersections. We could do this by hand, or use corn2lines.bat to do the job from the environment variables.

call %PICTBAT%corn2lines ^
  F4C_quad.png F4C_quad_sub ^
  f4c_lines.png
f4c_lines.png

Then we can use the method shown in Straightening two lines: two dimensions, to make the blue lines straight and horizontal, and the green lines straight and vertical.

call %PICTBAT%str4lines ^
  f4c_lines.png F4C_quad.png ^
  f4c_persp2.png ^
  . mean
f4c_persp2.png

Again, the four input points have become the corners of a rectangle.

Scripts

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

find4cornSub.bat

rem Given %1 is image, whitish quadrangle on blackish background,
rem finds the four corners by subimage search.
rem Writes enviroment variable prefix %2
rem and output image %3.
rem %4 is three (quoted) parameters for drawCircs.bat: colour, radius, strokewidth.
@rem
@rem Updated:
@rem   25-August-2022 for IM v7.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 f4cs


set ENV_PREF=%2
if "%ENV_PREF%"=="." set ENV_PREF=
if "%ENV_PREF%"=="" set ENV_PREF=f4csENV

set OUTFILE=%3
if "%OUTFILE%"=="." set OUTFILE=
if /I "%OUTFILE%"=="NULL:" set OUTFILE=

set dcPARAMS=%~4
if "%dcPARAMS%"=="." set dcPARAMS=
if "%dcPARAMS%"=="" set dcPARAMS=. . .


set CORNER=f4cs_dcCorner.miff

set QUARTERS=f4cs_dcQuarters.miff


set SRCH_FACT=2
set SRCH_FACT=20
set SRCH_FACT=10
set SRCH_FACT=20

%IMG7%magick ^
  %INFILE% ^
  -crop 2x2@ ^
  %QUARTERS%

:: Top-left
%IMG7%magick ^
  -size 4x4 xc:Black ^
  -fill White ^
  -draw "rectangle 2,2 3,3" ^
  %CORNER%

set subSemiW=2
set subSemiH=2

call %PICTBAT%subSrchFactRep %QUARTERS%[0] %CORNER% %SRCH_FACT% F4Csub

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "%ENV_PREF%.TL.x=%%X+%%[fx:int(%F4Csub.x%+%subSemiW%+0.5)]\n%ENV_PREF%.TL.y=%%Y+%%[fx:int(%F4Csub.y%+%subSemiH%+0.5)]" ^
  %QUARTERS%[0]`) do set /A %%L

set %ENV_PREF%.TL.BAD=%F4Csub.BAD%


:: Top-right
%IMG7%magick ^
  %CORNER% ^
  -rotate 90 ^
  %CORNER%

call %PICTBAT%subSrchFactRep %QUARTERS%[1] %CORNER% %SRCH_FACT% F4Csub

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "%ENV_PREF%.TR.x=%%X+%%[fx:int(%F4Csub.x%+%subSemiW%+0.5)]\n%ENV_PREF%.TR.y=%%Y+%%[fx:int(%F4Csub.y%+%subSemiH%+0.5)]" ^
  %QUARTERS%[1]`) do set /A %%L

set %ENV_PREF%.TR.BAD=%F4Csub.BAD%


:: Bottom-right
%IMG7%magick ^
  %CORNER% ^
  -rotate 90 ^
  %CORNER%

call %PICTBAT%subSrchFactRep %QUARTERS%[3] %CORNER% %SRCH_FACT% F4Csub

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "%ENV_PREF%.BR.x=%%X+%%[fx:int(%F4Csub.x%+%subSemiW%+0.5)]\n%ENV_PREF%.BR.y=%%Y+%%[fx:int(%F4Csub.y%+%subSemiH%+0.5)]" ^
  %QUARTERS%[3]`) do set /A %%L

set %ENV_PREF%.BR.BAD=%F4Csub.BAD%


:: Bottom-left
%IMG7%magick ^
  %CORNER% ^
  -rotate 90 ^
  %CORNER%

call %PICTBAT%subSrchFactRep %QUARTERS%[2] %CORNER% %SRCH_FACT% F4Csub

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "%ENV_PREF%.BL.x=%%X+%%[fx:int(%F4Csub.x%+%subSemiW%+0.5)]\n%ENV_PREF%.BL.y=%%Y+%%[fx:int(%F4Csub.y%+%subSemiH%+0.5)]" ^
  %QUARTERS%[2]`) do set /A %%L

set %ENV_PREF%.BL.BAD=%F4Csub.BAD%


:: Create a debugging image

if not "%OUTFILE%"=="" call %PICTBAT%drawCircs ^
  %INFILE% ^
  %OUTFILE% ^
  %dcPARAMS% ^
  %ENV_PREF%.TL %ENV_PREF%.TR %ENV_PREF%.BL %ENV_PREF%.BR

set %ENV_PREF%.

set ENV_LIST=%BASENAME%_f4csEnv.lis
set %ENV_PREF% >%ENV_LIST%

call echoRestore

@endlocal & for /F %%L in (%ENV_LIST%) do @set %%L

find4cornEdgeBlr.bat

rem Given %1 is image, whitish quadrangle on blackish background,
rem finds the four corners by blurred edges.
rem Writes enviroment variable prefix %2
rem and output image %3.
rem %4 is three (quoted) parameters for drawCircs.bat: colour, radius, strokewidth.
@rem
@rem Updated:
@rem   25-August-2022 for IM v7.
@rem


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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 f4ceb


set ENV_PREF=%2
if "%ENV_PREF%"=="." set ENV_PREF=
if "%ENV_PREF%"=="" set ENV_PREF=f4cebENV

set OUTFILE=%3
if "%OUTFILE%"=="." set OUTFILE=
if /I "%OUTFILE%"=="NULL:" set OUTFILE=

set dcPARAMS=%~4
if "%dcPARAMS%"=="." set dcPARAMS=
if "%dcPARAMS%"=="" set dcPARAMS=. . .

set TMP_FILE=%BASENAME%_f4ceb.miff

call %PICTBAT%slopeMag %INFILE% %TMP_FILE%

set nFnd=0
for /F "usebackq tokens=1-4 delims=, " %%A in (`%IM7DEV%magick ^
  %TMP_FILE% ^
  -blur 0x5 ^
  -crop 2x2@ ^
  -format "offs: %%s %%X,%%Y\n"
  +write info: ^
  -process midlightest ^
  NULL: 2^>^&1`) do (
  rem echo %%A %%B %%C %%D
  if "%%A"=="offs:" (
    set /A C[%%B].offsX=%%C
    set /A C[%%B].offsY=%%D
  ) else (
    set C[!nFnd!].x=%%A
    set C[!nFnd!].y=%%B
    set /A nFnd+=1
  )
)

if not %nFnd%==4 exit /B 1

for /L %%I in (0,1,3) do (
  set /A C[%%I].x+=!C[%%I].offsX!
  set /A C[%%I].y+=!C[%%I].offsY!
)

rem set C[


set %ENV_PREF%.TL.x=!C[0].x!
set %ENV_PREF%.TL.y=!C[0].y!
set %ENV_PREF%.TR.x=!C[1].x!
set %ENV_PREF%.TR.y=!C[1].y!
set %ENV_PREF%.BL.x=!C[2].x!
set %ENV_PREF%.BL.y=!C[2].y!
set %ENV_PREF%.BR.x=!C[3].x!
set %ENV_PREF%.BR.y=!C[3].y!

if not "%OUTFILE%"=="" call %PICTBAT%drawCircs ^
  %INFILE% ^
  %OUTFILE% ^
  %dcPARAMS% ^
  %ENV_PREF%.TL %ENV_PREF%.TR %ENV_PREF%.BL %ENV_PREF%.BR

set %ENV_PREF%.

set ENV_LIST=%BASENAME%_f4ebEnv.lis
set %ENV_PREF% >%ENV_LIST%

call echoRestore

@endlocal & for /F %%L in (%ENV_LIST%) do @set %%L

find4cornPolDist.bat

rem Given %1 is white quadrangle on black background,
rem with each quadrangle corner in corresponding quarter of the image,
rem write the four coordinate-pairs into environment variable prefix %2
rem with a polar distance method.
rem
rem Also creates debugging output %3. Blank or . or NULL: for no output image.
rem %4 is 0 (don't trim first) or 1 (do trim first). Default 1=do trim.
rem %5 is supersampling percentage, eg 300. Defalt 100=no supersampling.
rem %6 is three (quoted) parameters for drawCircs.bat: colour, radius, strokewidth.
@rem
@rem Updated:
@rem   25-August-2022 for IM v7.
@rem

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

@setlocal enabledelayedexpansion

rem @call echoOffSave


set INFILE=%1

set ENV_PREF=%2
if "%ENV_PREF%"=="." set ENV_PREF=
if "%ENV_PREF%"=="" set ENV_PREF=f4cENV

set OUTFILE=%3
if "%OUTFILE%"=="." set OUTFILE=
if /I "%OUTFILE%"=="NULL:" set OUTFILE=

set DO_TRIM=%4
if "%DO_TRIM%"=="." set DO_TRIM=
if "%DO_TRIM%"=="" set DO_TRIM=1

set SUPER=%5
if "%SUPER%"=="." set SUPER=
if "%SUPER%"=="" set SUPER=100

set dcPARAMS=%~6
if "%dcPARAMS%"=="." set dcPARAMS=
if "%dcPARAMS%"=="" set dcPARAMS=. . .

if %DO_TRIM%==1 (
  set sTRIM=-trim
) else (
  set sTRIM=
)

if %SUPER%==100 (
  set sSUPERSAMP=
) else (
  set sSUPERSAMP=-resize %SUPER%%%
)

set nFnd=0
for /F "usebackq tokens=1-6 delims=+x, " %%A in (`%IM7DEV%magick ^
  %INFILE% ^
  -precision 19 ^
  -virtual-pixel Black ^
  -format "input: %%P%%O\n" ^
  +write info: ^
  %sTRIM% ^
  -format "trimmed: %%wx%%h%%O\n" ^
  +write info: ^
  +repage ^
  -format "semidiag: %%[fx:hypot(w,h)/2]\n" ^
  +write info: ^
  %sSUPERSAMP% ^
  -distort Depolar -1 ^
  -flip ^
  -format "unrolled: %%wx%%h\n" ^
  +write info: ^
  -crop 4x1@ ^
  -process onewhite ^
  -format "offset: %%s %%wx%%h%%O  %%wx%%h\n" ^
  info: 2^>^&1`) do (
  rem echo %%A %%B %%C %%D %%E %%F
  if "%%A"=="input:" (
    set IN_WW=%%B
    set IN_HH=%%C
    set IN_XX=%%D
    set IN_YY=%%E
  ) else if "%%A"=="trimmed:" (
    set TRM_WW=%%B
    set TRM_HH=%%C
    set TRM_XX=%%D
    set TRM_YY=%%E
  ) else if "%%A"=="semidiag:" (
    set SEMIDIAG=%%B
  ) else if "%%A"=="unrolled:" (
    set UNR_WW=%%B
    set UNR_HH=%%C
  ) else if "%%A"=="offset:" (
    set OFFS[%%B].WW=%%C
    set OFFS[%%B].HH=%%D
    set OFFS[%%B].XX=%%E
    set OFFS[%%B].YY=%%F
  ) else if "%%A"=="onewhite:" (
    set POL[!nFnd!].x=%%B
    set POL[!nFnd!].y=%%C
    set /A nFnd+=1
  )
)

if not "%nFnd%"=="4" exit /B 1

rem set POL
rem set OFFS
rem set TRM_

for /L %%I in (0,1,3) do (
  call :doCalc theta "(!POL[%%I].x!+!OFFS[%%I].XX!)*2*pi/%UNR_WW%"
  call :doCalc rad "(1-!POL[%%I].y!/%UNR_HH%)*%SEMIDIAG%"

  call :doCalc C[%%I].x "%TRM_WW%/2-!rad!*sin(!theta!)+%TRM_XX%"
  call :doCalc C[%%I].y "%TRM_HH%/2-!rad!*cos(!theta!)+%TRM_YY%"

  rem echo theta=!theta! rad=!rad! x=!C[%%I].x! y=!C[%%I].y!
)


if not "%OUTFILE%"=="" call %PICTBAT%drawCircs %INFILE% %OUTFILE% %dcPARAMS% C[0] C[1] C[2] C[3]

set %ENV_PREF%.TL.x=%C[0].x%
set %ENV_PREF%.TL.y=%C[0].y%
set %ENV_PREF%.BL.x=%C[1].x%
set %ENV_PREF%.BL.y=%C[1].y%
set %ENV_PREF%.BR.x=%C[2].x%
set %ENV_PREF%.BR.y=%C[2].y%
set %ENV_PREF%.TR.x=%C[3].x%
set %ENV_PREF%.TR.y=%C[3].y%

set ENV_LIST=%BASENAME%_f4cpdEnv.lis
set %ENV_PREF% >%ENV_LIST%

call echoRestore

@endlocal & for /F %%L in (%ENV_LIST%) do @set %%L

@exit /B 0

::----------------------------------------------------
:: Subroutine

:doCalc
set %1=
for /F "usebackq" %%L in (`%IMG7%magick identify -precision 19 -format "%1=%%[fx:%~2]" xc:`) do set %%L

rem echo %1 is !%1!

if "!%1!"=="" exit /B 1

exit /B 0

SubSrchFactRep.bat

rem Repeatedly calls subSrchFact until result is good, or factor is 1.
@rem
@rem Updated:
@rem   25-August-2022 for IM v7.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 ssfr

set SML_FILE=%2

set FACT=%3
if "%FACT%"=="." set FACT=
if "%FACT%"=="" set FACT=10

set ENV_PREF=%4
if "%ENV_PREF%"=="." set ENV_PREF=
if "%ENV_PREF%"=="" set ENV_PREF=ssfrENV

set THRESH_BAD=%5
if "%THRESH_BAD%"=="." set THRESH_BAD=
if "%THRESH_BAD%"=="" set THRESH_BAD=0.1

:loop
set %ENV_PREF%.BAD=

call %PICTBAT%subSrchFact %INFILE% %SML_FILE% %FACT% %ENV_PREF% %THRESH_BAD%

if "!%ENV_PREF%.BAD!"=="" goto finished

if %FACT%==1 goto finished

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "FACT=%%[fx:%FACT%<=2?1:%FACT%/2.0]" ^
  xc:`) do set %%L

echo %0: FACT=%FACT% bad=!%ENV_PREF%.BAD!

goto loop


:finished

echo %0: FACT=%FACT% bad=!%ENV_PREF%.BAD!

set ENV_LIST=%BASENAME%_ssfrEnv.lis
set %ENV_PREF% >%ENV_LIST%

call echoRestore

@endlocal & for /F %%L in (%ENV_LIST%) do @set %%L

SubSrchFact.bat

rem Given large image %1 and very small image %2,
rem searches for small in large, via a resize of the large image, integer factor %3 (eg 2, 20).
rem Writes to environment prefix %4:
rem   .x .y .score .bad
rem %5 is threshold RMSE for badness. Default 0.1
@rem
@rem Updated:
@rem   25-August-2022 for IM v7.
@rem


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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 ssf

set ENV_PREF=%4
if "%ENV_PREF%"=="." set ENV_PREF=
if "%ENV_PREF%"=="" set ENV_PREF=ssfENV

set SML_FILE=%2

set FACT=%3
if "%FACT%"=="." set FACT=
if "%FACT%"=="" set FACT=10

set THRESH_BAD=%5
if "%THRESH_BAD%"=="." set THRESH_BAD=
if "%THRESH_BAD%"=="" set THRESH_BAD=0.1

set TMP_LRG=%BASENAME%_ssf.miff
set TMP_LRG2=%BASENAME%_ssf2.miff

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "FACT_PC=%%[fx:100/%FACT%]" ^
  xc:`) do set %%L

%IMG7%magick ^
  %INFILE% ^
  +repage ^
  -resize %FACT_PC%%% ^
  %TMP_LRG%


goto skip
%IMG7%magick compare ^
  -metric RMSE ^
  %TMP_LRG% ^
  %SML_FILE% ^
  -similarity-threshold 0 ^
  -dissimilarity-threshold 1 ^
  -subimage-search ^
  NULL:
:skip

for /F "usebackq tokens=1-4 delims=()@, " %%A in (`%IMG7%magick compare ^
  -metric RMSE ^
  %TMP_LRG% ^
  %SML_FILE% ^
  -similarity-threshold 0 ^
  -dissimilarity-threshold 1 ^
  -subimage-search ^
  NULL: 2^>^&1`) do (
  set score=%%B
  set fndX=%%C
  set fndY=%%D
)
echo %0: fndX=%fndX% fndY=%fndY% score=%score%

set CALC_SUB=^
crpX=%%[fx:int((%fndX%-2)*%FACT%+0.5)]\n^
crpY=%%[fx:int((%fndY%-2)*%FACT%+0.5)]\n^
crpH=%%[fx:int(5*%FACT%+w+1+0.5)]\n^
crpW=%%[fx:int(5*%FACT%+h+1+0.5)]

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "%CALC_SUB%" ^
  %SML_FILE%`) do set %%L

set BAD=
if %crpX% LSS 0 set BAD=1
if %crpY% LSS 0 set BAD=1

echo %0: CROP=-crop %crpW%x%crpH%+%crpX%+%crpY% +repage

%IMG7%magick ^
  %INFILE% ^
  +repage ^
  -crop %crpW%x%crpH%+%crpX%+%crpY% +repage ^
  %TMP_LRG2%

for /F "usebackq tokens=1-4 delims=()@, " %%A in (`%IMG7%magick compare ^
  -metric RMSE ^
  %TMP_LRG2% ^
  %SML_FILE% ^
  -similarity-threshold 0 ^
  -dissimilarity-threshold 1 ^
  -subimage-search ^
  NULL: 2^>^&1`) do (
  set fndScore=%%B
  set /A fndX=%%C+%crpX%
  set /A fndY=%%D+%crpY%
)
echo %0: fndX=%fndX% fndY=%fndY% fndScore=%fndScore%

if not "%BAD%"=="1" for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "BAD=%%[fx:%fndScore%>%THRESH_BAD%?1:0]" ^
  xc:`) do (
  set %%L
  if "!BAD!"=="0" set BAD=
)



call echoRestore

endlocal & set %ENV_PREF%.x=%fndX%& set %ENV_PREF%.y=%fndY%& set %ENV_PREF%.score=fndScore& set %ENV_PREF%.BAD=%BAD%

corn2limit.bat

rem %1 is prefix of a set of environment variables, as created by find4cornSub.bat etc.
rem   Expected suffixes: .BL.x etc.
rem Calculates min, max and mean of left, top, right and bottom.
rem   Creates suffixes .min.L etc.
rem %2 is destination coords: min, max, mean, inner or outer.

set %1.min.L=!%1.BL.x!
if !%1.min.L! GTR !%1.TL.x! set %1.min.L=!%1.TL.x!

set %1.min.T=!%1.TL.y!
if !%1.min.T! GTR !%1.TR.y! set %1.min.L=!%1.TR.y!

set %1.min.R=!%1.BR.x!
if !%1.min.R! GTR !%1.TR.x! set %1.min.R=!%1.TR.x!

set %1.min.B=!%1.BL.y!
if !%1.min.B! GTR !%1.BR.y! set %1.min.B=!%1.BR.y!


set %1.max.L=!%1.BL.x!
if !%1.max.L! LSS !%1.TL.x! set %1.max.L=!%1.TL.x!

set %1.max.T=!%1.TL.y!
if !%1.max.T! LSS !%1.TR.y! set %1.max.T=!%1.TR.y!

set %1.max.R=!%1.BL.x!
if !%1.max.R! LSS !%1.TR.x! set %1.max.R=!%1.TR.x!

set %1.max.B=!%1.BL.y!
if !%1.max.B! LSS !%1.BR.y! set %1.max.B=!%1.BR.y!


set /A %1.mean.L=(!%1.BL.x!+!%1.TL.x!)/2
set /A %1.mean.T=(!%1.TL.y!+!%1.TR.y!)/2
set /A %1.mean.R=(!%1.BR.x!+!%1.TR.x!)/2
set /A %1.mean.B=(!%1.BL.y!+!%1.BR.y!)/2

if /I "%2"=="min" (
  set %1.dst.L=!%1.min.L!
  set %1.dst.T=!%1.min.T!
  set %1.dst.R=!%1.min.R!
  set %1.dst.B=!%1.min.B!
) else if /I "%2"=="max" (
  set %1.dst.L=!%1.max.L!
  set %1.dst.T=!%1.max.T!
  set %1.dst.R=!%1.max.R!
  set %1.dst.B=!%1.max.B!
) else if /I "%2"=="mean" (
  set %1.dst.L=!%1.mean.L!
  set %1.dst.T=!%1.mean.T!
  set %1.dst.R=!%1.mean.R!
  set %1.dst.B=!%1.mean.B!
) else if /I "%2"=="inner" (
  echo .
) else if /I "%2"=="outer" (
  echo .
) else (
  echo %0: Bad arg 2 [%2]
  exit /B 1
)

set %1.persp=^
!%1.TL.x!,!%1.TL.y!,!%1.dst.L!,!%1.dst.T!,^
!%1.TR.x!,!%1.TR.y!,!%1.dst.R!,!%1.dst.T!,^
!%1.BR.x!,!%1.BR.y!,!%1.dst.R!,!%1.dst.B!,^
!%1.BL.x!,!%1.BL.y!,!%1.dst.L!,!%1.dst.B!


set %1.

corn2lines.bat

rem %1 input image
rem %2 is prefix of a set of environment variables, as created by find4cornSub.bat etc.
rem   Expected suffixes: .BL.x etc.
rem %3 output image: input image, darkened, with added lines.

:: In next, we deliberately clamp.

%IMG7%magick ^
  %1 ^
  -evaluate Multiply 0.995 ^
  +antialias ^
  -set option:y0 %%[fx:!%2.TL.y!-!%2.TL.x!*(!%2.TR.y!-!%2.TL.y!)/(!%2.TR.x!-!%2.TL.x!)] ^
  -set option:y1 %%[fx:!%2.TR.y!+(w-!%2.TR.x!)*(!%2.TR.y!-!%2.TL.y!)/(!%2.TR.x!-!%2.TL.x!)] ^
  -format "%%[y0] %%[y1]\n" +write info: ^
  -stroke #00f ^
  -draw "line 0,%%[y0],%%[fx:w-1],%%[y1]" ^
  -set option:y0 %%[fx:!%2.BL.y!-!%2.BL.x!*(!%2.BR.y!-!%2.BL.y!)/(!%2.BR.x!-!%2.BL.x!)] ^
  -set option:y1 %%[fx:!%2.BR.y!+(w-!%2.BR.x!)*(!%2.BR.y!-!%2.BL.y!)/(!%2.BR.x!-!%2.BL.x!)] ^
  -format "%%[y0] %%[y1]\n" +write info: ^
  -draw "line 0,%%[y0],%%[fx:w-1],%%[y1]" ^
  ( +clone ^
    -alpha transparent ^
    -stroke #0f0 ^
    -strokewidth 3 ^
    -set option:x0 %%[fx:!%2.TL.x!-!%2.TL.y!*(!%2.BL.x!-!%2.TL.x!)/(!%2.BL.y!-!%2.TL.y!)] ^
    -set option:x1 %%[fx:!%2.BL.x!+(h-!%2.BL.y!)*(!%2.BL.x!-!%2.TL.x!)/(!%2.BL.y!-!%2.TL.y!)] ^
    -format "%%[x0] %%[x1]\n" +write info: ^
    -draw "line %%[x0],0,%%[x1],%%[fx:h-1]" ^
    -set option:x0 %%[fx:!%2.TR.x!-!%2.TR.y!*(!%2.BR.x!-!%2.TR.x!)/(!%2.BR.y!-!%2.TR.y!)] ^
    -set option:x1 %%[fx:!%2.BR.x!+(h-!%2.BR.y!)*(!%2.BR.x!-!%2.TR.x!)/(!%2.BR.y!-!%2.TR.y!)] ^
    -format "%%[x0] %%[x1]\n" +write info: ^
    -draw "line %%[x0],0,%%[x1],%%[fx:h-1]" ^
  ) ^
  -compose Plus -composite ^
  %3

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

%IMG7%magick -version
Version: ImageMagick 7.1.0-49 Q16-HDRI x64 7a3f3f1:20220924 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 (193331630)

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 find4corn.h1. To re-create this web page, run "procH1 find4corn".


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 9-July-2016.

Page created 27-Sep-2022 18:41:19.

Copyright © 2022 Alan Gibson.