Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Super resolution

notebooks/super_resolution.livemd

Super resolution

Mix.install([
  {:tflite_elixir, "~> 0.3.0"},
  {:evision, "0.1.31"},
  {:req, "~> 0.3.0"},
  {:kino, "~> 0.9.0"}
])

Introduction

The task of recovering a high resolution (HR) image from its low resolution counterpart is commonly referred to as Single Image Super Resolution (SISR).

The model used here is Enhanced Super-Resolution Generative Adversarial Networks (ESRGAN). And we are going to use TensorFlow Lite to run inference on the pretrained model.

https://www.tensorflow.org/lite/examples/super_resolution/overview

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
alias TFLiteElixir, as: TFLite
alias TFLiteElixir.TFLiteTensor

Download data files

  • model using ESRGAN-TF2
  • test image from tensorflow examples
# /data is the writable portion of a Nerves system
downloads_dir =
  if Code.ensure_loaded?(Nerves.Runtime), do: "/data/livebook", else: System.tmp_dir!()

download = fn url ->
  save_as = Path.join(downloads_dir, URI.encode_www_form(url))
  unless File.exists?(save_as), do: Req.get!(url, output: save_as)
  save_as
end

data_files =
  [
    model: "https://tfhub.dev/captain-pool/lite-model/esrgan-tf2/1?lite-format=tflite",
    test_img:
      "https://raw.githubusercontent.com/tensorflow/examples/master/lite/examples/super_resolution/android/app/src/main/assets/lr-1.jpg"
  ]
  |> Enum.map(fn {key, url} -> {key, download.(url)} end)
  |> Map.new()

data_files
|> Enum.map(fn {k, v} -> [name: k, location: v] end)
|> Kino.DataTable.new(name: "Data files")
test_image_input = Kino.Input.image(" ")

Generate a super resolution image using TensorFlow Lite

test_image_mat =
  case Kino.Input.read(test_image_input) do
    %{data: data, height: height, width: width} ->
      Cv.Mat.from_binary(data, {:u, 8}, height, width, 3)
      |> Cv.cvtColor(Cv.Constant.cv_COLOR_BGR2RGB())

    nil ->
      Cv.imread(data_files.test_img)
  end

lr =
  test_image_mat
  |> Cv.Mat.to_nx()
  |> Nx.new_axis(0)
  |> Nx.as_type({:f, 32})

# Load TFLite model and allocate tensors.
{:ok, interpreter} = TFLite.Interpreter.new(data_files.model)

# Get input and output tensors.
{:ok, input_tensors} = TFLite.Interpreter.inputs(interpreter)
{:ok, output_tensors} = TFLite.Interpreter.outputs(interpreter)

# Run the model
TFLite.Interpreter.input_tensor(interpreter, 0, Nx.to_binary(lr))
TFLite.Interpreter.invoke(interpreter)

# Extract the output and postprocess it
{:ok, output_data} = TFLite.Interpreter.output_tensor(interpreter, 0)
%TFLiteTensor{} = out_tensor = TFLite.Interpreter.tensor(interpreter, Enum.at(output_tensors, 0))
[1 | shape] = TFLite.TFLiteTensor.dims(out_tensor)
type = TFLite.TFLiteTensor.type(out_tensor)

sr =
  output_data
  |> Nx.from_binary(type)
  |> Nx.reshape(List.to_tuple(shape))
  |> Nx.clip(0, 255)
  |> Nx.as_type({:u, 8})

Visualize the result

[
  ["Low resolution", test_image_mat],
  ["High resolution", Cv.Mat.from_nx_2d(sr)]
]
|> Enum.map(fn [label, img] ->
  Kino.Layout.grid([img, Kino.Markdown.new("**#{label}**")], boxed: true)
end)
|> Kino.Layout.grid(columns: 2)