Evision Example - Warp Polar and Reverse
Mix.install([
{:evision, "~> 0.2"},
{:kino, "~> 0.7"},
{:req, "~> 0.5"}
], system_env: [
# optional, defaults to `true`
# set `EVISION_PREFER_PRECOMPILED` to `false`
# if you prefer `:evision` to be compiled from source
# note that to compile from source, you may need at least 1GB RAM
{"EVISION_PREFER_PRECOMPILED", true},
# optional, defaults to `true`
# set `EVISION_ENABLE_CONTRIB` to `false`
# if you don't need modules from `opencv_contrib`
{"EVISION_ENABLE_CONTRIB", true},
# optional, defaults to `false`
# set `EVISION_ENABLE_CUDA` to `true`
# if you wish to use CUDA related functions
# note that `EVISION_ENABLE_CONTRIB` also has to be `true`
# because cuda related modules come from the `opencv_contrib` repo
{"EVISION_ENABLE_CUDA", false},
# required when
# - `EVISION_ENABLE_CUDA` is `true`
# - and `EVISION_PREFER_PRECOMPILED` is `true`
#
# set `EVISION_CUDA_VERSION` to the version that matches
# your local CUDA runtime version
#
# current available versions are
# - 118
# - 121
{"EVISION_CUDA_VERSION", "118"},
# require for Windows users when
# - `EVISION_ENABLE_CUDA` is `true`
# set `EVISION_CUDA_RUNTIME_DIR` to the directory that contains
# CUDA runtime libraries
{"EVISION_CUDA_RUNTIME_DIR", "C:/PATH/TO/CUDA/RUNTIME"}
])
:ok
Define Some Helper Functions
defmodule Helper do
def download!(url, save_as, overwrite? \\ false) do
unless File.exists?(save_as) do
Req.get!(url, http_errors: :raise, into: File.stream!(save_as), cache: not overwrite?)
end
:ok
end
end
{:module, Helper, <<70, 79, 82, 49, 0, 0, 10, ...>>, {:download!, 3}}
Load the Test Image
test_image_path = Path.join(__DIR__, "warp_polar.png")
Helper.download!(
"https://raw.githubusercontent.com/cocoa-xu/evision/main/test/testdata/warp_polar.png",
test_image_path
)
# Read the test image
%Evision.Mat{shape: {rows, cols, 3}} = src = Evision.imread(test_image_path)
%Evision.Mat{
channels: 3,
dims: 2,
type: {:u, 8},
raw_type: 16,
shape: {412, 561, 3},
ref: #Reference<0.1535381621.3478519834.195361>
}
The centre point (relative to the source image) of the circle locates at {x=224, y=206}
.
Of course, you might ask “how do we find the centre of that circle by code” or maybe first you wanna ask “how do we detect if there is any circle in any given image”. And these questions fall outside the scope of this livebook, and there are really plenty of ways to do it.
Max Radius
max_radius
decides the bounding circle. If some part of the bounding circle is outside the range of the source image, then we need to ask OpenCV to fill these outliners by adding another flag Evision.Constant.cv_WARP_FILL_OUTLIERS()
.
We can first plot the bounding circle for some visualisation.
centre = {x = 224, y = 206}
max_radius = 0.93 * min(x, y)
red_color = {0, 0, 255}
# convert max_radius to an integer
# because Evision.circle expects an integer input for the
# radius parameter
int_max_radius = trunc(max_radius)
Evision.circle(src, centre, int_max_radius, red_color, thickness: 2)
# please click the "Image" tab in the output below to see the visualised result
%Evision.Mat{
channels: 3,
dims: 2,
type: {:u, 8},
raw_type: 16,
shape: {412, 561, 3},
ref: #Reference<0.1535381621.3478519828.194184>
}
The processed image will look like this:
Evision.warpPolar(
src,
{0, 0},
centre,
int_max_radius,
Evision.Constant.cv_INTER_LINEAR() + Evision.Constant.cv_WARP_FILL_OUTLIERS()
)
# please click the "Image" tab in the output below to see the visualised result
%Evision.Mat{
channels: 3,
dims: 2,
type: {:u, 8},
raw_type: 16,
shape: {600, 191, 3},
ref: #Reference<0.1535381621.3478519828.194187>
}
if the max radius is too large, then we will have some outliners, as you can see that the red circle (in the output below) is not entirely inside the source image
int_max_radius = trunc(1.2 * min(x, y))
Evision.circle(src, centre, int_max_radius, red_color, thickness: 2)
# please click the "Image" tab in the output below to see the visualised result
%Evision.Mat{
channels: 3,
dims: 2,
type: {:u, 8},
raw_type: 16,
shape: {412, 561, 3},
ref: #Reference<0.1535381621.3478519828.194190>
}
And in such cases, OpenCV will have to fill outliners (in this example, filled outliners reside in the black areas on the right hand side of the output image)
Evision.warpPolar(
src,
{0, 0},
centre,
int_max_radius,
Evision.Constant.cv_INTER_LINEAR() + Evision.Constant.cv_WARP_FILL_OUTLIERS()
)
# please click the "Image" tab in the output below to see the visualised result
%Evision.Mat{
channels: 3,
dims: 2,
type: {:u, 8},
raw_type: 16,
shape: {776, 247, 3},
ref: #Reference<0.1535381621.3478519828.194193>
}
dsize
The next thing is dsize={width, height}
, and there are three options:
-
if both values in
dsize <=0
(default), the destination image will have (almost) same area of source bounding circle$\begin{array}{l} dsize.area \leftarrow (maxRadius^2 \cdot \Pi) \ dsize.width = \texttt{round}(maxRadius) \ dsize.height = \texttt{round}(maxRadius \cdot \Pi) \ \end{array}$
-
if only
dsize.height <= 0
, the destination image area will be proportional to the bounding circle area but scaled byKx * Kx
:$\begin{array}{l} dsize.height = \texttt{round}(dsize.width \cdot \Pi) \ \end{array}$
-
if both values in
dsize > 0
, the destination image will have the given size therefore the area of the bounding circle will be scaled todsize
.
dsize - option 1
We can first try dsize={0, 0}
.
dsize = {0, 0}
max_radius = 0.93 * min(x, y)
Evision.warpPolar(
src,
dsize,
centre,
max_radius,
Evision.Constant.cv_INTER_LINEAR() + Evision.Constant.cv_WARP_FILL_OUTLIERS()
)
# please click the "Image" tab in the output below to see the visualised result
%Evision.Mat{
channels: 3,
dims: 2,
type: {:u, 8},
raw_type: 16,
shape: {602, 192, 3},
ref: #Reference<0.1535381621.3478519828.194205>
}
Looks good. What about dsize = {240, 800}
dsize - option 2
only dsize.height <= 0
dsize = {150, -1}
max_radius = 0.93 * min(x, y)
Evision.warpPolar(
src,
dsize,
centre,
max_radius,
Evision.Constant.cv_INTER_LINEAR() + Evision.Constant.cv_WARP_FILL_OUTLIERS()
)
# please click the "Image" tab in the output below to see the visualised result
%Evision.Mat{
channels: 3,
dims: 2,
type: {:u, 8},
raw_type: 16,
shape: {471, 150, 3},
ref: #Reference<0.1535381621.3478519828.194208>
}
dsize - option 3
both values in dsize > 0
dsize = {240, 400}
max_radius = 0.93 * min(x, y)
Evision.warpPolar(
src,
dsize,
centre,
max_radius,
Evision.Constant.cv_INTER_LINEAR() + Evision.Constant.cv_WARP_FILL_OUTLIERS()
)
# please click the "Image" tab in the output below to see the visualised result
%Evision.Mat{
channels: 3,
dims: 2,
type: {:u, 8},
raw_type: 16,
shape: {400, 240, 3},
ref: #Reference<0.1535381621.3478519828.194211>
}
Remaps to Semilog-Polar Coordinates Space
dsize = {0, 0}
max_radius = 0.93 * min(x, y)
log_polar_img =
Evision.warpPolar(
src,
dsize,
centre,
max_radius,
Evision.Constant.cv_INTER_LINEAR() + Evision.Constant.cv_WARP_FILL_OUTLIERS() + Evision.Constant.cv_WARP_POLAR_LOG()
)
# please click the "Image" tab in the output below to see the visualised result
%Evision.Mat{
channels: 3,
dims: 2,
type: {:u, 8},
raw_type: 16,
shape: {602, 192, 3},
ref: #Reference<0.1535381621.3478519828.194229>
}
Reverse the Transformation
From Semilog-Polar Coordinates Space
log_polar_img
is the result image in the cell above.
shape = Evision.Mat.shape(src)
dsize = {elem(shape, 1), elem(shape, 0)}
Evision.warpPolar(
log_polar_img,
dsize,
centre,
max_radius,
Evision.Constant.cv_INTER_LINEAR() + Evision.Constant.cv_WARP_FILL_OUTLIERS() +
Evision.Constant.cv_WARP_POLAR_LOG() + Evision.Constant.cv_WARP_INVERSE_MAP()
)
# please click the "Image" tab in the output below to see the visualised result
%Evision.Mat{
channels: 3,
dims: 2,
type: {:u, 8},
raw_type: 16,
shape: {412, 561, 3},
ref: #Reference<0.1535381621.3478519828.194247>
}
From Polar Coordinates Space
dsize = {0, 0}
max_radius = 0.93 * min(x, y)
linear_polar_img =
Evision.warpPolar(
src,
dsize,
centre,
max_radius,
Evision.Constant.cv_INTER_LINEAR() + Evision.Constant.cv_WARP_FILL_OUTLIERS()
)
# reverse the transformation
shape = Evision.Mat.shape(src)
dsize = {elem(shape, 1), elem(shape, 0)}
Evision.warpPolar(
linear_polar_img,
dsize,
centre,
max_radius,
Evision.Constant.cv_INTER_LINEAR() + Evision.Constant.cv_WARP_FILL_OUTLIERS() +
Evision.Constant.cv_WARP_INVERSE_MAP()
)
# please click the "Image" tab in the output below to see the visualised result
%Evision.Mat{
channels: 3,
dims: 2,
type: {:u, 8},
raw_type: 16,
shape: {412, 561, 3},
ref: #Reference<0.1535381621.3478519828.194251>
}