Ultra-lightweight face detection
File.cd!(__DIR__)
# for windows JP
System.shell("chcp 65001")
Mix.install(
[
{:axon_interp, "~> 0.1.0"},
{:cimg, "~> 0.1.14"},
{:postdnn, "~> 0.1.5"},
{:kino, "~> 0.7.0"}
],
config: [
nx: [default_defn_options: [compiler: EXLA]]
]
# system_env: [{"NNINTERP", "Axon"}]
)
1.Implementation with OnnxInterp in Elixir
defmodule UltraFace do
@width 320
@height 240
alias AxonInterp, as: NNInterp
use NNInterp,
model: "./model/version-slim-320_simplified.onnx",
url:
"https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB/raw/master/models/onnx/version-slim-320_simplified.onnx",
inputs: [f32: {1, 3, @height, @width}],
outputs: [f32: {1, 4420, 2}, f32: {1, 4420, 4}]
@width 320
@height 240
def apply(img) do
# preprocess
input0 =
img
|> CImg.resize({@width, @height})
|> CImg.to_binary([{:range, {-1.0, 1.0}}, :nchw])
# prediction
outputs =
session()
|> NNInterp.set_input_tensor(0, input0, [:binary])
|> NNInterp.invoke()
conf = NNInterp.get_output_tensor(outputs, 0) |> Nx.reshape({:auto, 2})
loc = NNInterp.get_output_tensor(outputs, 1) |> Nx.reshape({:auto, 4})
# postprocess
scores = decode_scores(conf)
boxes = decode_boxes(loc)
PostDNN.non_max_suppression_multi_class(
Nx.shape(scores),
Nx.to_binary(boxes),
Nx.to_binary(scores),
iou_threshold: 0.3,
score_threshold: 0.7,
boxrepr: :corner
)
# |> PostDNN.adjust2letterbox(CImg.Util.aspect(img))
end
@priorbox PostDNN.priorbox(
{@width, @height},
[{8, [10, 16, 24]}, {16, [32, 48]}, {32, [64, 96]}, {64, [128, 192, 256]}],
[:transpose, :normalize]
)
@variance Nx.tensor([0.1, 0.1, 0.2, 0.2], type: :f32) |> Nx.reshape({4, 1})
defp decode_boxes(loc) do
loc = Nx.transpose(loc)
# decode box center coordinate on {1.0, 1.0}
center =
loc[0..1]
|> Nx.multiply(@variance[0..1])
# * prior_size(x,y)
|> Nx.multiply(@priorbox[2..3])
# + grid(x,y)
|> Nx.add(@priorbox[0..1])
# decode box half size
half_size =
loc[2..3]
|> Nx.multiply(@variance[2..3])
|> Nx.exp()
# * prior_size(x,y)
|> Nx.multiply(@priorbox[2..3])
|> Nx.divide(2.0)
# decode boxes
[Nx.subtract(center, half_size), Nx.add(center, half_size)]
|> Nx.concatenate()
|> PostDNN.clamp({0.0, 1.0})
|> Nx.transpose()
end
defp decode_scores(conf) do
Nx.slice_along_axis(conf, 1, 1, axis: 1)
end
end
defmodule DemoUltraFace do
def run(path) do
img = CImg.load(path)
with {:ok, res} = UltraFace.apply(img) do
res["0"]
|> Enum.take(8)
|> draw_item(CImg.builder(img), {255, 255, 0})
|> CImg.display_kino(:jpeg)
end
end
defp draw_item(boxes, canvas, color \\ {255, 255, 255}) do
Enum.reduce(boxes, canvas, fn [_score, x1, y1, x2, y2, _index], canvas ->
[x1, y1, x2, y2] = PostDNN.clamp([x1, y1, x2, y2], {0.0, 1.0})
CImg.fill_rect(canvas, x1, y1, x2, y2, color, 0.3)
end)
end
end
# AxonInterp.stop(UltraFace)
UltraFace.start_link([])
2.Let’s try it
DemoUltraFace.run("./10.jpg")
3.TIL ;-)
Appendix
□