I love GoPro cameras, including the fisheye effect (heavy barrel distortion). If we want, we can make the image rectilinear (removing this distortion).
Two techniques are explored here. The first uses "-distort Barrel". This is simple to understand, but not easy to get good results for image with massive distortion.
The second technique ( De-fisheye) is more complicated but gives excellent results.
This example photo was taken through a window looking directly at the sun. The bottom of the windowsill and the brick surround can be seen. In real life, they are both straight. The camera is reflected in the glass window. The original is 4000x3000 pixels. For web purposes, we shrink the image. The values for the parameters don't depend on the size.
set SRC=db_source.png if not exist %SRC% %IMG7%magick ^ %PICTLIB%20140115\GOPR0077.jpg ^ -resize 300x400 ^ -strip ^ %SRC% |
All images on this page are made from this small image. Naturally, creating images from the full-size original would give better quality.
To remove barrel distortion we can use -distort Barrel with three or four parameters. The output size will be the same as the input size. For each pixel in the output image, IM calculates the radius (Rdst) from the centre of the image, normalised so Rdst=1 at the centre of the longest sides. To find which source pixel should be copied here, it applies the formula:
Rsrc = A.Rdst4 + B.Rdst3 + C.Rdst2 + D.Rdst
The values A, B and C must be supplied to -distort Barrel. If D is not supplied, it defaults to 1 - (A + B + C).
Suitable values for A, B and C can be found automatically from calibrate_lens_gui.exe, part of the Hugin toolset. (However, that tool optimises for zero distortion at the centre of the long edge, D = 1 - (A + B + C).) Or they can be found by trial-and-error.
For the GoPro Hero 3 Black, reasonable values are A = 0.10, B = -0.32, C = 0. This gives the default value for D = 1.22.
%IMG7%magick ^ %SRC% ^ -distort Barrel 0.10,-0.32,0 ^ db_plain.png |
|
Another set of parameters that work: %IMG7%magick ^ %SRC% ^ -distort Barrel 0.001,0,-0.31 ^ db_plain2.png |
|
Yet another set of parameters that work: %IMG7%magick ^ %SRC% ^ -distort Barrel 0.007,-0.007,-0.33 ^ db_plain3.png |
|
Yet another set of parameters, this time from Hugin optimizer: %IMG7%magick ^ %SRC% ^ -distort Barrel -0.081,0.192,-0.393 ^ db_plain4.png The result is lousy. |
At this default value for D:
Rsrc = A.Rdst4 + B.Rdst3 + C.Rdst2 + D.Rdst Rsrc = A.Rdst4 + B.Rdst3 + C.Rdst2 + (1 - (A + B + C)).Rdst
When Rdst==1:
Rsrc = A.14 + B.13 + C.12 + (1 - (A + B + C)).1 Rsrc = A + B + C + (1 - (A + B + C)) Rsrc = 1
So pixels at Rdst=1 won't move. By taking the difference, we see a black circle where the image not been distorted.
%IMG7%magick ^ %SRC% ^ db_plain.png ^ -compose Difference -composite ^ -auto-level -auto-gamma ^ db_plain_diff.png |
By choosing a suitable value for D, we can set this radius of non-distortion (Rnd) at any desired value.
Rnd = A.Rnd4 + B.Rnd3 + C.Rnd2 + D.Rnd 1 = A.Rnd3 + B.Rnd2 + C.Rnd + D D = 1 - (A.Rnd3 + B.Rnd2 + C.Rnd)
For no distortion at the centre, we need Rnd = 0, hence D = 1.
%IMG7%magick ^ %SRC% ^ ( +clone ^ -virtual-pixel Black ^ -distort Barrel 0.10,-0.32,0,1 ^ -write db_cent.png ^ ) ^ -compose Difference -composite ^ -auto-level -auto-gamma ^ db_cent_diff.png |
Compared to db_plain.png above, this image is "zoomed in", and we have lost pixels around the edges.
Another special case is with zero distortion at the centre of the short edges. This image has an aspect ratio of 4:3, so Rnd = 4/3 = 1.3333.
Substituting values A = 0.10, B = -0.32, C = 0:
D = 1 - (A.Rnd3 + B.Rnd2 + C.Rnd) D = 1 - (0.1*(4/3)3 - 0.32*(4/3)2 + 0) D = 1.3319
To highlight the added border, I use "-virtual-pixel Black". This darkens pixels at the edge of the boundary. If we are cropping within the boundary, it is better to use "-virtual-pixel Edge".
%IMG7%magick ^ %SRC% ^ ( +clone ^ -virtual-pixel Black ^ -distort Barrel 0.10,-0.32,0,1.3319 ^ -write db_centSht.png ^ ) ^ -compose Difference -composite ^ -auto-level -auto-gamma ^ db_centSht_diff.png |
|
%IMG7%magick ^ %SRC% ^ ( +clone ^ -virtual-pixel Black ^ -distort Barrel 0.02,-0.03,-0.45,1.6059 ^ -write db_centSht2.png ^ ) ^ -compose Difference -composite ^ -auto-level -auto-gamma ^ db_centSht2_diff.png |
As a final special case, we may want zero distortion in the corners. This gives all the pixels from the source image, but with extra virtual pixels. Pixels near centre will be heavily squashed. (Or, if the image is enlarged, pixels near the corners will be heavily stretched.) With an aspect ratio of 4:3, the diagonal is 5 (we have a Pythagorean triangle 3:4:5), so Rnd = 5/3 = 1.6667.
D = 1 - (A.Rnd3 + B.Rnd2 + C.Rnd) D = 1 - (0.1*(5/3)3 - 0.32*(5/3)2 + 0) D = 1.4259
%IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -distort Barrel 0.10,-0.32,0,1.4259 ^ db_corn.png |
However, lines that should be straight are now curved. Huh?
Multiply A, B and C by 1.4259/1.22:
%IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -distort Barrel 0.1169,-0.3740,0,1.4259 ^ db_corn2.png |
But then D no longer keeps zero distortion in the corners. Should be 1.497. Then A = 0.1 * 1.497/1.22 = 0.1227 and B = 0.32 * 1.497/1.22 = 0.3927. Then D = 1.5228. Etc.
%IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -distort Barrel 0.1227,-0.3927,0,1.5229 ^ db_corn3.png |
%IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -distort Barrel 0.40,-0.95,0,1.787 ^ db_corn4.png |
|
D such that corners don't move %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -distort Barrel 0.155,-0.26,-0.55,1.9213 ^ db_corn5.png |
|
D such that A+B+C+D=1 %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -distort Barrel 0.155,-0.26,-0.55,1.655 ^ db_cent5.png |
Blah ...
We can de-barrel and and enlarge in a single operation. This is quicker and higher quality than doing two separate operations.
From db_corn5.png above, scaled by 1.5. %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -define distort:scale=1.5 ^ -distort Barrel 0.155,-0.26,-0.55,1.9213 ^ db_corn5_sc1.png |
If we choose scale to the same value as D, the scale at the centre will be one, i.e. the same as the undistorted source. Pixels near the corners will be heavily stretched.
From db_corn5.png above, scaled by 1.9213. %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -background Khaki ^ -define distort:scale=1.9213 ^ -distort Barrel 0.155,-0.26,-0.55,1.9213 ^ db_corn5_sc2.png |
Or we can shrink.
From db_corn5.png above, multiplied by 0.5. %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -background Khaki ^ -define distort:scale=0.5 ^ -distort Barrel 0.155,-0.26,-0.55,1.9213 ^ db_corn5_sc3.png |
We can apply a viewport to the distort. This is like "-crop", but gravity has no effect, nor does "%". Whatever the order, "distort:viewport" is applied first, then "distort:scale".
From db_corn5.png above, scaled by 1.5, with viewport. %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -gravity East ^ -define distort:viewport=50%%x50%% ^ -define distort:scale=1.5 ^ -distort Barrel 0.155,-0.26,-0.55,1.9213 ^ db_corn5_sc4.png |
An alternative method of resizing is to use "-extent". We make it (n) times larger by using "-extent" at n*100%, and dividing each of A, B, C and D by (n). In this example, virtual pixels won't be used, so the border colour is taken from "-background".
From db_corn5.png above, multiplied by 1.5. %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -background Khaki ^ -gravity Center -extent 150%% ^ -distort Barrel 0.10333,-0.17333,-0.36667,1.28087 ^ db_corn5_exp.png |
If we choose (n) to be the value of D (so the new value of D will be one), the scale at the centre will be one, i.e. the same as the undistorted source. Pixels near the corners will be heavily stretched.
From db_corn5.png above, multiplied by 1.9213. %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -background Khaki ^ -gravity Center -extent 192.13%% ^ -distort Barrel 0.080675,-0.13533,-0.28626,1 ^ db_corn5_one.png |
Although "-extent" less than 100% is possible, it doesn't give the desired result.
We can crop the result in the usual way. One crop in particular is useful: a letterbox crop from the de-barrel with zero distortion at the centre of the short lines, removing the two groups of virtual pixels. The result takes the greatest advantage of the camera's wide angle.
If a script hard-codes the ABCD parameters, it might as well also hard-code the crop parameters.
In the general case, we need to find the crop parameters from arbitrary ABCD.
To find the crop parameters, we can walk along each pixel in the longest centre-line, from the edge towards the centre, calculating (r) and hence Rsrc. We take a note when Rsrc becomes less than the semi-width or semi-height. This gives us the required crop width and height. For the letterbox crop, we are interested only when Rsrc becomes less than half the shortest dimension. The housekeeping is a bit messy, so we use a script, deBarrelCrop.bat. It returns the crop width and height, in pixels.
call %PICTBAT%deBarrelCrop db_centSht2.png 0.02,-0.03,-0.45,1.6059 echo %dbcWIDTHpix% %dbcHEIGHTpix% %dbcOFFS_X% %dbcOFFS_Y% 300 183 0 21 %IMG7%magick ^ db_centSht2.png ^ -gravity Center -crop %dbcWIDTHpix%x%dbcHEIGHTpix%+0+0 +repage ^ db_centSht2_cr.png |
When we have the crop parameters from deBarrelCrop, we can use this in a viewport for distort.
%IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -define distort:viewport=%dbcWIDTHpix%x%dbcHEIGHTpix%+%dbcOFFS_X%+%dbcOFFS_Y% ^ -distort Barrel 0.02,-0.03,-0.45,1.6059 ^ +repage ^ db_centSht2_cr2.png Compare this to the result from "-crop": %IMG7%magick compare -metric RMSE db_centSht2_cr.png db_centSht2_cr2.png NULL: cmd /c exit /B 0 0 (0) |
For the basics, see my main page on Displacement maps.
We can create a relative displacement map for a given debarrel operation, and apply the same map to many images.
Create the map. In a single command, we create a null absolute map, apply barrel distortion to this, and convert it to a relative displacement map.
set DEBARREL=-distort Barrel 0.02,-0.03,-0.45,1.6059 FOR /F "usebackq" %%L IN (`%IMG7%magick identify -format "WW=%%w\nHH=%%h\nWm1=%%[fx:w-1]\nHm1=%%[fx:h-1]" %SRC%`) DO set %%L %IMG7%magick ^ ( -size %HH%x%WW% gradient: -rotate 90 ) ^ ( -size %WW%x%HH% gradient: -flip ) ^ -size %WW%x%HH% xc:Black ^ -combine ^ -evaluate Divide 2 ^ -set colorspace sRGB ^ ( +clone -virtual-pixel Edge %DEBARREL% ) ^ -compose ModulusSubtract -composite ^ -evaluate AddModulus 50%% ^ db_map_rel.miff
Apply the map. We use "+depth" for precision in the comparison.
%IMG7%magick ^ %SRC% ^ db_map_rel.miff ^ -compose Displace ^ -set option:compose:args %Wm1%x%Hm1% ^ -composite ^ +depth ^ db_deb_by_map.png |
"-virtual-pixel" doesn't seem to work. The map needs tweaking? The centres of the short sides are naff.
For comparison, debarrel directly ...
%IMG7%magick ^ %SRC% ^ %DEBARREL% ^ +depth ^ db_deb_direct.png |
... and compare ...
%IMG7%magick compare ^ -metric RMSE ^ db_deb_by_map.png ^ db_deb_direct.png ^ NULL: cmd /c exit /B 0
290.913 (0.00443905)
If we want to use a constant colour (such as black) for virtual pixels, using a displacement map, with proper anti-aliasing, the method becomes more complicated. The mask has the same colors as before, but with an anti-aliased alpha channel.
set DEBARREL=-distort Barrel 0.02,-0.03,-0.45,1.6059 FOR /F "usebackq" %%L IN (`%IMG7%magick identify ^ -format "WW=%%w\nHH=%%h\nWm1=%%[fx:w-1]\nHm1=%%[fx:h-1]" ^ %SRC%`) DO set %%L %IMG7%magick ^ ( -size %HH%x%WW% gradient: -rotate 90 ) ^ ( -size %WW%x%HH% gradient: -flip ) ^ -size %WW%x%HH% xc:Black ^ -combine ^ -evaluate Divide 2 ^ -set colorspace sRGB ^ +write x0.png ^ ( +clone -virtual-pixel None %DEBARREL% -write mpr:MASK +write x1.png +delete ) ^ ( +clone -virtual-pixel Edge %DEBARREL% ) ^ -compose ModulusSubtract -composite ^ -evaluate AddModulus 50%% ^ mpr:MASK ^ -compose CopyOpacity -composite ^ db_map_rel2.miff set sSPARSE=^ 0,0,#000,^ %Wm1%,0,#f00,^ 0,%Hm1%,#0f0,^ %Wm1%,%Hm1%,#ff0 %IMG7%magick ^ -size %WW%x%HH% xc:Black ^ ( +clone -virtual-pixel None %DEBARREL% -write mpr:MASK +write x1.png +delete ) ^ -sparse-color Bilinear %sSPARSE% ^ +write x0.png ^ ( +clone -virtual-pixel Edge %DEBARREL% ) ^ -compose Mathematics -define compose:args=0,0.5,-0.5,0.5 -composite ^ mpr:MASK ^ -alpha set ^ -compose CopyOpacity -composite ^ db_map_rel2.miff
Apply the map. We displace according to the colours in the usual way, but then we copy opacity from the map and flatten against the required background colour.
%IMG7%magick ^ %SRC% ^ db_map_rel2.miff ^ ( -clone 0-1 ^ -compose Displace ^ -set option:compose:args %Wm1%x%Hm1% ^ -composite ^ ) ^ -delete 0 +swap ^ -compose CopyOpacity -composite ^ -background #000 -compose Over -flatten ^ +depth ^ db_deb_by_map2.png |
Applying this map is expensive. We displace and copy opacity and flatten. An alternative would be to create a mask of the black for the vitual pixels and transparent elsewhere, and compose that over the image, saving one operation, at the cost of a reading an extra image.
For comparison, debarrel directly ...
%IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ %DEBARREL% ^ +depth ^ db_deb_direct2.png |
... and compare ...
%IMG7%magick compare ^ -metric RMSE ^ db_deb_by_map2.png ^ db_deb_direct2.png ^ NULL: cmd /c exit /B 0
268.784 (0.00410139)
The "-barrel" distortions above actually create pincusion results, moving the corners outwards. Even when the radius of no distortion, Rnd, is set to 1.0 so the short dimension remains the same, we might want to see what happens in the corners. To do this, we can use a viewport. This takes the usual WxH+X+Y geometry argument, where WxH will be the desired output size, generally larger than the input, and the offsets should be half the input dimension minus the output dimension. (Thus, the offsets will usually be negative.)
The script barrelTest.bat calls the script barrelSize.bat with the image dimensions and the barrel parameters. barrelSize.bat makes a dummy white image, guesses how large the output should be, distorts the white image, and trims the result. The size of the result tells us what viewport dimensions we need, and barrelTest.bat uses this to distort the actual image.
call %PICTBAT%barrelTest ^ %SRC% ^ db_plain_bt.jpg ^ "0.10,-0.32,0" ^ 2 |
|
call %PICTBAT%barrelTest ^ %SRC% ^ db_plain_bt2.jpg ^ "0.10,-0.32,0" ^ 9 |
Each destination pixel is derived from a single source pixel. However, each source pixel is distorted to up to four pixels.
call %PICTBAT%barrelTest ^ %SRC% ^ db_plain_bt3.jpg ^ "-0.10,-0.32,0" ^ 2 |
|
call %PICTBAT%barrelTest ^ %SRC% ^ db_plain_bt4.jpg ^ "-0.10,-0.32,-0.19" ^ 5 |
We can "-distort Depolar" to unwrap the image so the y-dimension is the radius, then distort in the y-dimension, then "-distort Polar". The basic scheme could work like this:
FOR /F "usebackq" %%L ^ IN (`%IMG7%magick identify ^ -format "WW=%%w\nHH=%%h\nWm1=%%[fx:w-1]\nHm1=%%[fx:h-1]" ^ %SRC%`) ^ DO set %%L %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -distort Depolar -1 ^ -write db_depol.png ^ ( ^ -size 1x%HH% ^ gradient:black-white ^ ( +clone -gamma 1 ) ^ -compose Mathematics ^ -define compose:args=0,0.5,-0.5,0.5 ^ -composite ^ -scale "%WW%x%HH%^!" ^ ) ^ -compose Displace ^ -set option:compose:args 0x%Hm1% ^ -composite ^ -background Pink -virtual-pixel Background ^ -distort Polar -1 ^ db_depol_pol.png %IMG7%magick compare -metric RMSE %SRC% db_depol_pol.png NULL: cmd /c exit /B 0 1336.53 (0.0203941) |
The example above uses "-gamma 1" as a no-op. Any transformation could be used. The displacement map is black at the top (representing a zero distance from the centre) to white at the bottom (representing the maximum distance, from the centre to the corners).
In the depolared image, pixels near the centre of the source have been spread out, while those near the edge have been squished together. We can improve the quality by supersampling with "distort:scale" at each end and (crudely) resizing the map. The intermediate result is large, so we don't save or show it.
FOR /F "usebackq" %%L ^ IN (`%IMG7%magick identify ^ -format "WW=%%w\nHH=%%h\nWm1=%%[fx:w-1]\nHm1=%%[fx:h-1]" ^ %SRC%`) ^ DO set %%L %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -define distort:scale=4 ^ -distort Depolar -1 ^ ( ^ -size 1x%HH% ^ gradient:black-white ^ ( +clone -gamma 1 ) ^ -compose Mathematics ^ -define compose:args=0,0.5,-0.5,0.5 ^ -composite ^ -scale "%WW%x%HH%^!" ^ -resize 400%% ^ ) ^ -compose Displace ^ -set option:compose:args 0x%Hm1% ^ -composite ^ -background Pink -virtual-pixel Background ^ -define distort:scale=0.25 ^ -distort Polar -1 ^ db_depol_pol2.png %IMG7%magick compare -metric RMSE %SRC% db_depol_pol2.png NULL: cmd /c exit /B 0 564.913 (0.00862001) |
The basic fourth-order polynomial de-barrel equation is:
Rsrc = A.Rdst4 + B.Rdst3 + C.Rdst2 + D.Rdst
At the radius of non-distortion, Rsrc = Rdst = Rnd.
Rnd = A.Rnd4 + B.Rnd3 + C.Rnd2 + D.Rnd 1 = A.Rnd3 + B.Rnd2 + C.Rnd + D D = 1 - (A.Rnd3 + B.Rnd2 + C.Rnd)
So:
Rsrc = A.r4 + B.r3 + C.r2 + r - r.(A.Rnd3 + B.Rnd2 + C.Rnd) = Rdst.[A.Rdst3 + B.Rdst2 + C.Rdst + 1 - A.Rnd3 - B.Rnd2 - C.Rnd] = Rdst.[A.(Rdst3-Rnd3) + B.(Rdst2-Rnd2) + C.(Rdst-Rnd) + 1]
Suppose Rdst = t.Rnd. Then:
Rsrc = Rdst.[A.(Rdst3-Rnd3) + B.(Rdst2-Rnd2) + C.(Rdst-Rnd) + 1] = t.Rnd.[A.Rnd3.(t3-1) + B.Rnd2.(t2-1) + C.Rnd.(t-1) + 1]
At t==0, Rsrc=0. At t==1, Rsrc=Rnd.
Inside the circle of non-distortion, t < 1. Outside, t > 1. When t < 1, all Rnd terms are < 0, so Rsrc < t.Rnd. When t > 1, all Rnd terms are > 0, so Rsrc > t.Rnd.
We usually want 0 <= Rnd <= semi-diagonal/semi-short-side.
After some experimentation, it turns out that the lens is close to an ideal equidistant fisheye (see external page PanoTools Fisheye Projection), so we use arctan to return the projection to rectilinear.
FOR /F "usebackq" %%L ^ IN (`%IMG7%magick identify ^ -format "WW4=%%[fx:4*w]\nHH4=%%[fx:4*h]\nW4m1=%%[fx:4*w-1]\nH4m1=%%[fx:4*h-1]" ^ %SRC%`) ^ DO set %%L set RAT=3.1 %IMG7%magick ^ -size 1x%HH4% ^ gradient:black-white ^ ( +clone ^ -fx "atan(%RAT%*u)/atan(%RAT%)" ^ ) ^ -compose Mathematics -define compose:args=0,0.5,-0.5,0.5 -composite ^ -scale "%WW4%x%HH4%^!" ^ pol_map_rel4.png %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -define distort:scale=4 ^ -distort Depolar -1 ^ pol_map_rel4.png ^ -compose Displace ^ -set option:compose:args 0x%H4m1% ^ -composite ^ -background Pink -virtual-pixel Background ^ -define distort:scale=0.25 ^ -distort Polar -1 ^ db_depol_pol3.png Repeat the distortion in RGB space to see if there is a difference. %IMG7%magick ^ %SRC% ^ -colorspace RGB -set colorspace sRGB ^ -virtual-pixel Black ^ -define distort:scale=4 ^ -distort Depolar -1 ^ pol_map_rel4.png ^ -compose Displace ^ -set option:compose:args 0x%H4m1% ^ -composite ^ -background Pink -virtual-pixel Background ^ -define distort:scale=0.25 ^ -distort Polar -1 ^ -set colorspace RGB -colorspace sRGB ^ db_depol_pol4.png %IMG7%magick compare -metric RMSE db_depol_pol3.png db_depol_pol4.png NULL: cmd /c exit /B 0 662.394 (0.0101075) No significant difference. |
As usual (see Displacement Maps), we can create an identity (mis-named "null") displacement map, give it the depolar/atan/polar transformation, and apply that to many photographs.
We implement this in a script, rectGoPro.bat.
Finding the inner border is easy. When Rsrc is the distance from the centre in fisheye space; r is the distance from the centre in rectlinear space:
Rsrc = atan (k.r) / atan (k)
where r is one at the corners, not at the centre of the long sides, so:
r = tan (Rsrc.atan(k)) / k
For this image, aspect ratio 4:3, the centres of the long sides are at Rsrc = 3/5 of the semi-diagonal. Taking k as 3.1:
r = tan(3/5 * atan(3.1))/3.1 = 0.303695
or r = 759.2.7 pixels from the centre for a 4000x3000 image. Hence the maximum height available with no virtual pixels is 1518 pixels.
The centres of the short sides are at Rsrc = 4/5, so:
r = tan(4/5 * atan(3.1))/3.1 = 0.51021
or r = 1275.5 pixels from the centre for a 4000x3000 image. Hence the maximum width available with no virtual pixels is 2551 pixels.
atan(3.1) is 72.1°, so the camera records 144.2° from corner to opposite corner. If the lens is a true equidistant (linear) fisheye then the horizontal angle is 4000/5000 * 144.2 deg = 115.4°, and the vertical angle is 144.2 * 3000/5000 = 86.5°.
There are four main transformations for removing barrel distortion, but an infinite number of others.
Here are the summary results for my GoPro Hero 3 Black.
Rnd | Unchanged scale at... | Extent | A | B | C | D | Code | Image | Crop |
---|---|---|---|---|---|---|---|---|---|
0 | ... image centre. | none | 0.10 | -0.32 | 0 | 1 | |||
1 | ... centre of long edges. | none | 0.007 | -0.007 | -0.33 | ||||
longest/shortest = 4/3 | ... centre of short edges. | none | 0.02 | -0.03 | -0.45 | 1.6059 | |||
diagonal/shortest = 5/3 | ... corners. | none | 0.155 | -0.26 | -0.55 | 1.9213 | |||
0 | ... image centres. Keeps all image pixels. | 192.13% | 0.080675 | -0.13533 | -0.28626 | 1 |
The depolar/displace-arctan/polar method gives the best result. It is fast, provided the displacement map has already been created. We use a script, rectGoPro.bat.
call %PICTBAT%rectGoPro %SRC% if ERRORLEVEL 1 goto error %IMG7%magick ^ db_source_rgp.png ^ db_rgp1.jpg |
|
call %PICTBAT%rectGoPro %SRC% 0 Black if ERRORLEVEL 1 goto error %IMG7%magick ^ db_source_rgp.png ^ db_rgp2.jpg |
|
call %PICTBAT%rectGoPro %SRC% 1 if ERRORLEVEL 1 goto error %IMG7%magick ^ db_source_rgp.png ^ db_rgp3.jpg |
|
call %PICTBAT%rectGoPro %SRC% 1 0 1 if ERRORLEVEL 1 goto error %IMG7%magick ^ db_source_rgp.png ^ db_rgp4.jpg |
Scaling: I mostly want the output image to be a certain size, or the best quality. If I cared about keeping a certain scale at the centre, then I can calculate that atan(0.0001*3.1)/atan(3.1)/0.0001 = 2.4627524 so the default script settings will shrink the centre by a factor of about 2.46. I could enlarge the result by that amount or (better) set rgpSUP_SAMP=4 before running the script then enlarge by 2.46/4.
The script assumes that images can be enlarged by rgpSUP_SAMP and then reduced by the same, giving the original number of pixels. The default value of 2 is generally fine. A value of 4 gives better quality, but takes longer and also eats time, memory and disk, as the images are 16000x12000 pixels.
A smooth transition can be made between the fisheye image made by the camera and the rectilinear version by varying rgpRAT between a small positive value (eg 0.0001) and the usual value of 3.1.
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
rem For image %1, debarrel ABCD parameters %2 %3 %4 %5, rem find crop parameters. @rem @rem Updated: @rem 21-August-2022 Upgraded for IM v7. @rem @if "%5"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal call echoOffSave call %PICTBAT%setInOut %1 dbc set A=%2 set B=%3 set C=%4 set D=%5 for /F "usebackq" %%L ^ in (`%IMG7%magick identify -format "WW=%%w\nHH=%%h\nIsPort=%%[fx:w>h?0:1]\nLL_2=%%[fx:w>h?w/2:h/2]\nSS_2=%%[fx:w<h?w/2:h/2]" %INFILE%`) ^ do set %%L for /F "usebackq" %%L ^ in (`%IMG7%magick identify -format "LL_2i=%%[fx:int(%LL_2%)]\nSS_2i=%%[fx:int(%SS_2%)]" %INFILE%`) ^ do set %%L for /F "usebackq" %%L ^ in (`%IMG7%magick identify -format "LL_2F=%%[fx:%LL_2%/%SS_2%]\nODD_LL=%%[fx:%LL_2%%%2>0?1:0]\nODD_SS=%%[fx:%SS_2%%%2>0?1:0]" xc:`) ^ do set %%L echo ODD_LL=%ODD_LL% ODD_SS=%ODD_SS% rem Beware fractional semi-length. set OFFS_SS=0 set OFFS_LL=0 for /L %%I in (%LL_2%,-1,1) do ( if !OFFS_SS!==0 ( for /F "usebackq" %%L ^ in (`%IMG7%magick identify -format "rDest=%%[fx:%%I/%SS_2%]" %SRC%`) ^ do set %%L for /F "usebackq" %%L ^ in (`%IMG7%magick identify ^ -format "rSrc=%%[fx:!rDest!*((%A%)*!rDest!*!rDest!*!rDest!+(%B%)*!rDest!*!rDest!+(%C%)*!rDest!+(%D%))]" ^ %SRC%`) ^ do set %%L echo I=%%I rDest=!rDest! rSrc=!rSrc! for /F "usebackq" %%L ^ in (`%IMG7%magick identify -format "LSS_LL=%%[fx:!rSrc!<%LL_2F%?1:0]\nLSS_SS=%%[fx:!rSrc!<1?1:0]" %SRC%`) ^ do set %%L if !LSS_LL!==1 if !OFFS_LL!==0 set OFFS_LL=%%I if !LSS_SS!==1 if !OFFS_SS!==0 set OFFS_SS=%%I ) ) echo OFFS_LL=%OFFS_LL% OFFS_SS=%OFFS_SS% if %IsPort%==1 ( set /A OFFS_X=%SS_2i%-%OFFS_SS% set /A OFFS_Y=%LL_2i%-%OFFS_LL% set /A WIDTHpix=%OFFS_SS%+%OFFS_SS%+%ODD_SS% set /A HEIGHTpix=%OFFS_LL%+%OFFS_LL%+%ODD_LL% ) else ( set /A OFFS_X=%LL_2i%-%OFFS_LL% set /A OFFS_Y=%SS_2i%-%OFFS_SS% set /A WIDTHpix=%OFFS_LL%+%OFFS_LL%+%ODD_LL% set /A HEIGHTpix=%OFFS_SS%+%OFFS_SS%+%ODD_SS% ) call echoRestore endlocal & set dbcOFFS_X=%OFFS_X%& set dbcOFFS_Y=%OFFS_Y%& set dbcWIDTHpix=%WIDTHpix%& set dbcHEIGHTpix=%HEIGHTpix% rem %IMG7%magick %1 -gravity Center -crop %dbcWIDTHpix%x%dbcHEIGHTpix%+0+0 d.png
rem For image %1, from a GoPro, makes rectilinear version. @rem %2 is whether to crop within virtual pixels. Default = 0 = don't crop @rem %3 is whether to overlay virtual pixels (and what colour). Default = 0 = no overlay; none=make them transparent, otherwise colour. @rem %4 is whether to leave at supersampled size. (Supersamples by rgpSUP_SAMP.) Default = 0 = reduce size down. @rem %5 is output file. @rem @rem Updated: @rem 28-October-2017 Added parameter 5 and more error-checking. @rem 21-August-2022 Upgraded for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal call echoOffSave call %PICTBAT%setInOut %1 rgp set MASK_DIR=%PICTBAT%rectGoPro\ set DISP_DIR=%PICTBAT%rectGoPro\ set WORK_EXT=mpc if not exist %MASK_DIR% md %MASK_DIR% if not exist %DISP_DIR% md %DISP_DIR% set DO_CROP=%2 if "%DO_CROP%"=="." set DO_CROP= if "%DO_CROP%"=="" set DO_CROP=0 set BORD_COL=%3 if "%BORD_COL%"=="." set BORD_COL= if "%BORD_COL%"=="" set BORD_COL=0 set SUP_SAMP=%4 if "%SUP_SAMP%"=="." set SUP_SAMP= if "%SUP_SAMP%"=="" set SUP_SAMP=0 if not "%5"=="" if not "%5"=="." set OUTFILE=%5 if "%rgpSUP_SAMP%"=="" set rgpSUP_SAMP=2 set /A SUP_SAMPpc = %rgpSUP_SAMP%*100 FOR /F "usebackq" %%L ^ IN (`%IMG7%magick identify ^ -format "WW=%%w\nHH=%%h\nIS_PORT=%%[fx:w<h?1:0]\nSUP_SAMPinvPc=%%[fx:100/%rgpSUP_SAMP%]\nSUP_SAMPinv=%%[fx:1/%rgpSUP_SAMP%]" ^ %INFILE%`) ^ DO set %%L if %IS_PORT%==0 ( set ROT_FOR= set ROT_REV= ) else ( set ROT_FOR=-rotate 90 set ROT_REV=-rotate -90 set t=%WW% set WW=%HH% set HH=!t! ) rem We need to find good values of RAT for other image sizes. (3 still and 9 video sizes.) if not %WW%==4000 ( echo Dimension needs to be 4000x3000 or 3000x4000. rem exit /B 1 ) if not %HH%==3000 ( echo Dimension needs to be 4000x3000 or 3000x4000. rem exit /B 1 ) if "%rgpRAT%"=="" ( set RAT=3.1 ) else ( set RAT=%rgpRAT% ) set sRAT=%RAT:.=-% set SUFFIX=%WW%_%HH%_%sRAT% set /A WWss=%rgpSUP_SAMP%*%WW% set /A HHss=%rgpSUP_SAMP%*%HH% set /A Hm1=%HH%-1 set /A Wm1=%WW%-1 set /A Wssm1=%WWss%-1 set /A Hssm1=%HHss%-1 rem Make displacement maps, if we need them and don't have them. set sBIG_MAP=%DISP_DIR%rgp_disp_map_%SUFFIX%_%rgpSUP_SAMP%.%WORK_EXT% set sSML_MAP=%DISP_DIR%rgp_disp_map_%SUFFIX%.%WORK_EXT% set MAKE_BIG_MAP=0 set MAKE_SML_MAP=0 if not exist %sSML_MAP% if %SUP_SAMP%==0 set MAKE_SML_MAP=1 if not exist %sBIG_MAP% ( if %SUP_SAMP%==1 set MAKE_BIG_MAP=1 if %MAKE_SML_MAP%==1 set MAKE_BIG_MAP=1 ) if %MAKE_BIG_MAP%==1 %IMG7%magick ^ ( -size %HHss%x%WWss% gradient: -rotate 90 ) ^ ( -size %WWss%x%HHss% gradient: -flip ) ^ -size %WWss%x%HHss% xc:Black ^ -combine ^ -set colorspace sRGB ^ ( +clone ^ -virtual-pixel Edge ^ -distort Depolar -1 ^ ( ^ -size 1x%HHss% ^ gradient:black-white ^ ( +clone ^ -fx "atan(%RAT%*u)/atan(%RAT%)" ^ ) ^ -compose Mathematics -define compose:args=0,0.5,-0.5,0.5 -composite ^ -scale "%WWss%x%HHss%^!" ^ ) ^ -compose Displace ^ -set option:compose:args 0x%Hssm1% ^ -composite ^ -write drx0.jpg ^ -background Pink -virtual-pixel Background ^ -distort Polar -1 ^ ) ^ -compose Mathematics -define compose:args=0,0.5,-0.5,0.5 -composite ^ +depth ^ %sBIG_MAP% if ERRORLEVEL 1 exit /B 1 if %MAKE_SML_MAP%==1 %IMG7%magick ^ %sBIG_MAP% ^ -scale %SUP_SAMPinvPc%%% ^ +depth ^ %sSML_MAP% if ERRORLEVEL 1 exit /B 1 rem Make borders to compose over virtual pixels, if we need them and don't have them. set sBIG_BORD=%MASK_DIR%rgp_bord_%SUFFIX%_%rgpSUP_SAMP%.%WORK_EXT% set sSML_BORD=%MASK_DIR%rgp_bord_%SUFFIX%.%WORK_EXT% set MAKE_BIG_BORD=0 set MAKE_SML_BORD=0 if not exist %sSML_BORD% if %SUP_SAMP%==0 set MAKE_SML_BORD=1 rem FIXME: large border rem FIXME: polmap_rel.png if %MAKE_SML_BORD%==1 ( %IMG7%magick ^ -size %WW%x%HH% ^ xc:#fff ^ -virtual-pixel Black ^ -define distort:scale=%rgpSUP_SAMP% ^ -distort Depolar -1 ^ ^( ^ -size 1x%HHss% ^ gradient:black-white ^ ^( +clone ^ -fx "atan(%RAT%*u)/atan(%RAT%)" ^ ^) ^ -compose Mathematics -define compose:args=0,0.5,-0.5,0.5 -composite ^ -scale "%WWss%x%HHss%^!" ^ ^) ^ -compose Displace ^ -set option:compose:args 0x%Hssm1% ^ -composite ^ ^( +clone ^ -define distort:scale=1 ^ -distort Polar -1 ^ -negate -alpha Copy -evaluate Set 0 ^ -write %sBIG_BORD% ^ +delete ^ ^) ^ -define distort:scale=%SUP_SAMPinv% ^ -distort Polar -1 ^ -negate -alpha Copy -evaluate Set 0 ^ +depth ^ %sSML_BORD% if ERRORLEVEL 1 exit /B 1 ) if %SUP_SAMP%==0 ( set fBORD=%sSML_BORD% set CW=%WW% set CH=%HH% ) else ( set fBORD=%sBIG_BORD% set CW=%WWss% set CH=%HHss% ) rem Set crop. if %DO_CROP%==1 ( for /F "usebackq" %%L ^ in (`%IMG7%magick identify -format "DIAG=%%[fx:sqrt(%CW%*%CW%+%CH%*%CH%)]" xc:`) ^ do set %%L for /F "usebackq" %%L ^ in (`%IMG7%magick identify -format "C_WI=%%[fx:int(!DIAG!*tan(%CW%/!DIAG!*atan(%RAT%))/%RAT%)]\nC_HT=%%[fx:int(!DIAG!*tan(%CH%/!DIAG!*atan(%RAT%))/%RAT%)]" xc:`) ^ do set %%L set sCROP=-gravity Center -crop !C_WI!x!C_HT!+0+0 +repage ) else ( set sCROP= ) rem Set overlay. For non-black, quicker to pre-process. if /I %BORD_COL%==0 ( set sBORD= ) else if /I %BORD_COL%==none ( set sBORD="(" %fBORD% -channel A -negate ")" -compose CopyOpacity -composite ) else if /I %BORD_COL%==black ( set sBORD=%fBORD% -compose Over -composite ) else ( set sBORD="(" %fBORD% -alpha off -fill %BORD_COL% -opaque #000 -alpha on ")" -compose Over -composite ) if %SUP_SAMP%==1 ( %IMG7%magick ^ %INFILE% ^ %ROT_FOR% ^ -resize %SUP_SAMPpc%%% ^ %sBIG_MAP% ^ -virtual-pixel Black ^ -compose Displace ^ -set option:compose:args %Wssm1%x%Hssm1% ^ -composite ^ %sBORD% ^ %sCROP% ^ %ROT_REV% ^ %COMPR% ^ +depth ^ %OUTFILE% ) else ( %IMG7%magick ^ %INFILE% ^ %ROT_FOR% ^ %sSML_MAP% ^ -virtual-pixel Black ^ -compose Displace ^ -set option:compose:args %Wm1%x%Hm1% ^ -composite ^ %sBORD% ^ %sCROP% ^ %ROT_REV% ^ %COMPR% ^ +depth ^ %OUTFILE% ) if ERRORLEVEL 1 exit /B 1 %IMG7%magick identify %OUTFILE% call echoRestore
rem Given %1 is image, rem %2 is output file rem %3 is (quoted) set of barrel parameters rem %4 is multiple of size for first guess, integer, [default 3] @rem @rem Updated: @rem 21-August-2022 Upgraded for IM v7. @rem setlocal set SRC=\prose\pictures\db_source.png set SRC=%1 set OUTFILE=%2 set DB=%3 for /F "usebackq" %%L in (`%IMG7%magick ^ %SRC% -format "WW=%%[fx:w]\nHH=%%[fx:h]" ^ info:`) do set %%L if "%WW%"=="" exit /B 1 call %PICTBAT%barrelSize %WW% %HH% %DB% %4 if ERRORLEVEL 1 exit /B 1 %IMG7%magick ^ %SRC% ^ -virtual-pixel Black ^ -define distort:viewport=%bsVP% ^ -distort barrel %DB% ^ %OUTFILE% endlocal
rem Given %1 and %2 are width and height eg 267, 233 rem %3 is (quoted) set of barrel parameters rem %4 is multiple of size for first guess, integer, [default 3] rem finds output width and height that contains all the pixel data. @rem @rem Updated: @rem 21-August-2022 Upgraded for IM v7. @rem setlocal set WW=%1 set HH=%2 set DP=%3 set MULT_SZ=%4 if "%MULT_SZ%"=="." set MULT_SZ= if "%MULT_SZ%"=="" set MULT_SZ=3 set /A W_MULT=%MULT_SZ%*%WW% set /A H_MULT=%MULT_SZ%*%HH% set /A VX=(%W_MULT%-%WW%)/2 set /A VY=(%H_MULT%-%HH%)/2 echo %0: %W_MULT%x%H_MULT%-%VX%-%VY% for /F "usebackq" %%L in (`%IMG7%magick ^ -size %WW%x%HH% ^ xc:White ^ -virtual-pixel Black ^ -define distort:viewport^=%W_MULT%x%H_MULT%-%VX%-%VY% ^ -distort barrel %DP% ^ -trim +repage ^ -format "VW=%%w\nVH=%%h\nVX=%%[fx:int((w-%WW%)/2+0.5)]\nVY=%%[fx:int((h-%HH%)/2+0.5)]" ^ info:`) do set %%L if %VW%==%W3% exit /B 1 if %VH%==%H3% exit /B 1 endlocal & set bsVP=%VW%x%VH%-%VX%-%VY%
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)
Source file for this web page is debarrel.h1. To re-create this web page, execute "procH1 debarrel".
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 23-Feb-2014.
Page created 22-Aug-2022 00:08:25.
Copyright © 2022 Alan Gibson.