﻿

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``` A general quadrangle. ```%IMG7%magick ^ -size 300x200 xc:Black ^ -fill White ^ -draw "polygon 50,50 250,60 230,140 60,150" ^ f4c_quad.png``` A more general shape. 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``` 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```

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``` ```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``` ```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``` ```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``` ```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.

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``` ```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``` ```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``` ```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``` ```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.

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` ```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` ```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` ```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` 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``` ```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``` ```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```

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```

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```

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.

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

```F4C_quad_sub.BL.BAD=1

Do the perspective transformation:

 ```%IMG7%magick ^ f4c_quad.png ^ -distort perspective %F4C_quad_sub.persp% ^ 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```

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```

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

:: 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

:: 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

:: 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

:: 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%"

)

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

:loop

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

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

goto loop

:finished

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 %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 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

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 ^
xc:`) do (
set %%L
)

call echoRestore

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
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.