View Source find_and_draw_contours

Find and Draw Contours in an Image

Mix.install([
  {:evision, "~> 0.1"},
  {:kino, "~> 0.7"},
  {:req, "~> 0.3"}
], 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

Helper Function

defmodule Helper do
  def download!(url, save_as, overwrite? \\ false) do
    unless File.exists?(save_as) do
      Req.get!(url, http_errors: :raise, output: save_as, cache: not overwrite?)
    end

    :ok
  end
end
{:module, Helper, <<70, 79, 82, 49, 0, 0, 10, ...>>, {:download!, 3}}
Helper.download!("https://docs.opencv.org/4.x/pca_test1.jpg", "pca_test.jpg")
:ok

Load Image As Gray Scale

gray = Evision.imread("pca_test.jpg", flags: Evision.Constant.cv_IMREAD_GRAYSCALE())
%Evision.Mat{
  channels: 1,
  dims: 2,
  type: {:u, 8},
  raw_type: 0,
  shape: {600, 800},
  ref: #Reference<0.2172638371.2268463131.71223>
}

Get A Binary Image

# import Bitwise so that we can use `|||` (bitwise or)
import Bitwise

# binarization
{_, bw} =
  Evision.threshold(gray, 50, 255, Evision.Constant.cv_THRESH_BINARY() ||| Evision.Constant.cv_THRESH_OTSU())

bw
%Evision.Mat{
  channels: 1,
  dims: 2,
  type: {:u, 8},
  raw_type: 0,
  shape: {600, 800},
  ref: #Reference<0.2172638371.2268463122.70922>
}

Find All Contours in the Binary Image

# Find all the contours in the thresholded image
{contours, _} = Evision.findContours(bw, Evision.Constant.cv_RETR_LIST(), Evision.Constant.cv_CHAIN_APPROX_NONE())

IO.puts("Find #{Enum.count(contours)} contour(s)")
Find 7 contour(s)
:ok

Ignore Contours That Are Too Small or Too Large

minimal_area = 100
maximal_area = 100_000

contours =
  Enum.reject(contours, fn c ->
    # Calculate the area of each contour
    area = Evision.contourArea(c)
    # Ignore contours that are too small or too large
    # (return true to reject)
    area < minimal_area or area > maximal_area
  end)

IO.puts("#{Enum.count(contours)} contour(s) remains")
6 contour(s) remains
:ok

Draw All Contours

# color in {Blue, Green, Red}, range from 0-255
edge_color = {0, 0, 255}

# draw all contours by setting `index` to `-1`
index = -1

# Load image in color
src = Evision.imread("pca_test.jpg")

# draw all contours on the color image
Evision.drawContours(src, contours, index, edge_color, thickness: 2)
%Evision.Mat{
  channels: 3,
  dims: 2,
  type: {:u, 8},
  raw_type: 16,
  shape: {600, 800, 3},
  ref: #Reference<0.2172638371.2268463122.70944>
}