Evision Example - Warp Perspective
Mix.install(
[
{:evision, "~> 0.1.31"},
{:kino, "~> 0.9.0"},
{: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
# - 111
# - 114
# - 118
{"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"}
]
)
Introduction
This notebook will demonstrate how to perform perspective transformation.
It’s useful to alias the module as something shorter when we make extensive use of the functions from certain modules.
alias Evision, as: Cv
Define Some Helper Functions
Let’s prepare helper functions for preparing resources.
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
Read the Test Image
test_image_source =
"https://raw.githubusercontent.com/cocoa-xu/evision/main/test/testdata/warp_perspective.png"
test_image_path = Path.join(__DIR__, Path.basename(test_image_source))
Helper.download!(test_image_source, test_image_path)
test_img_mat = Cv.imread(test_image_path)
Function hypot: returns the Euclidean norm
This function calcululates the Euclidean norm, which is useful when we want to know the length of a line segment between two points (Euclidean distance).
# hypot.(list(number())) function returns the Euclidean norm
hypot = fn l -> :math.sqrt(Enum.sum(Enum.map(l, fn i -> i * i end))) end
Calculate the Output Coordinates for Corners
# specify input coordinates for corners of red quadrilateral
top_left = [136, 113]
top_right = [206, 130]
bottom_right = [173, 207]
bottom_left = [132, 196]
input_points = Nx.tensor([top_left, top_right, bottom_right, bottom_left], type: :f32)
# get top and left dimensions and set to output dimensions of red rectangle
output_width =
hypot.([
Nx.to_number(Nx.subtract(input_points[[0, 0]], input_points[[1, 0]])),
Nx.to_number(Nx.subtract(input_points[[0, 1]], input_points[[1, 1]]))
])
|> round()
output_height =
hypot.([
Nx.to_number(Nx.subtract(input_points[[0, 0]], input_points[[3, 0]])),
Nx.to_number(Nx.subtract(input_points[[0, 1]], input_points[[3, 1]]))
])
|> round()
IO.puts("width: #{output_width}, height: #{output_height}")
# set upper left coordinates for output rectangle
x = Nx.to_number(input_points[[0, 0]])
y = Nx.to_number(input_points[[0, 1]])
# specify output coordinates for corners of red quadrilateral
top_left = [x, y]
top_right = [x + output_width - 1, y]
bottom_right = [x + output_width - 1, y + output_height - 1]
bottom_left = [x, y + output_height - 1]
output_points = Nx.tensor([top_left, top_right, bottom_right, bottom_left], type: :f32)
Compute Perspective Matrix
perspective_matrix = Cv.getPerspectiveTransform(input_points, output_points)
Kino.Tree.new(perspective_matrix)
Perspective Transformation
{img_height, img_width, _} = Cv.Mat.shape(test_img_mat)
# do perspective transformation setting area outside input to black
# Note that output size is the same as the input image size
output_img_mat =
Cv.warpPerspective(
test_img_mat,
perspective_matrix,
{img_width, img_height},
flags: Cv.Constant.cv_INTER_LINEAR(),
borderMode: Cv.Constant.cv_BORDER_CONSTANT(),
borderValue: {0, 0, 0}
)
[
["Input image", test_img_mat],
["Output image", output_img_mat]
]
|> Enum.map(fn [label, img] ->
Kino.Layout.grid([img, Kino.Markdown.new("**#{label}**")], boxed: true)
end)
|> Kino.Layout.grid(columns: 2)