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

Image/Vix interop with eVision/OpenCV

livebook/evision_interop.livemd

Image/Vix interop with eVision/OpenCV

Mix.install([
  {:image, "~> 0.9.0"},
  {:evision, "~> 0.1"}
])

Define a known tensor as the reference image

tensor =
  Nx.tensor(
    [
      [
        [10, 20, 30],
        [40, 50, 60]
      ],
      [
        [70, 80, 90],
        [100, 110, 120]
      ],
      [
        [130, 140, 150],
        [160, 170, 180]
      ]
    ],
    type: {:u, 8}
  )
tensor = Nx.reshape(tensor, Nx.shape(tensor), names: [:width, :height, :bands])

Save the tensor as a reference image

{:ok, image} = Image.from_nx(tensor)
Image.write(image, "/tmp/ref.png")

Import the tensor into eVision

{:ok, evision} = Evision.Nx.to_mat(tensor)

Render the eVision image as a tensor

The tensor as imported from Image to eVision, then exported again to Nx shows the tensor has the same data and shape.

Evision.Nx.to_nx(evision)

Image stores data as {width, height, bands}. eVision stores data as {height, width, bands}. So transpose the axes in eVision.

Let’s save that as an image even though the data is in the wrong order. It should still be a valid image.

Evision.imwrite("/tmp/evision.png", evision)

We get an error writing the image.

libpng warning: Invalid image width in IHDR
libpng warning: Image width exceeds user limit in IHDR
libpng warning: Invalid image height in IHDR
libpng warning: Image height exceeds user limit in IHDR
libpng error: Invalid IHDR data

Transpose the image data

{:ok, transposed} = Evision.Mat.transpose(evision, [1, 0, 2])

Now see what the matrix looks like after exporting the transposition to Nx. From an Nx perspective the data looks correctly transposed.

Evision.Nx.to_nx(transposed)

Save the transposed image and see what it looks like

Evision.imwrite("/tmp/transposed.png", transposed)

We get the same error:

libpng warning: Invalid image width in IHDR
libpng warning: Image width exceeds user limit in IHDR
libpng warning: Invalid image height in IHDR
libpng warning: Image height exceeds user limit in IHDR
libpng error: Invalid IHDR data

Lets look at the shape of the data in the transposed image to confirm is {2, 3, 3}

Evision.Mat.shape(transposed)

Convert from RGB to BGR

Since Image data is RGB but eVision is BGR we need to convert it.

{:ok, bgr} = Evision.cvtColor(transposed, Evision.cv_COLOR_RGB2BGR())
Evision.Nx.to_nx(bgr)

Well that’s unexpected. I would expect [10, 20, 30] to become [30, 20, 10]?? It also seems the the transposition is largely undone? The data is in the same order as that of the original tensor, just grouped in {2, 3} rather than {3, 2}.

And try writing it again

Evision.imwrite("/tmp/bgr.png", bgr)

Nope, that data is completely unrecognizable!!!!!

Try a sample image

{:ok, i} = Image.open("qrcode.png", access: :random)
{:ok, t2} = Image.to_nx(i)
{:ok, e2} = Evision.Nx.to_mat(t2)
{:ok, trans2} = Evision.Mat.transpose(e2, [1, 0, 2])
{:ok, bgr2} = Evision.cvtColor(trans2, Evision.cv_COLOR_RGB2BGR())
Evision.imwrite("/tmp/qrcode_evision.png", bgr2)

Try Image.from_nx on our original data

{:ok, i2} = Image.from_nx(t2)
Image.write(i2, "/tmp/qrcode_orig.png")

Serialize the tensor for debugging purposes

{:ok, tensor} = Image.open!("color.jpg") |> Image.to_nx()
binary = tensor |> :erlang.term_to_binary()
File.write("/tmp/color_checker.etf", binary)

Now recover the etf to confirm its ok

t3 = File.read!("/tmp/color_checker.etf") |> :erlang.binary_to_term()
{:ok, i3} = Image.from_nx(t3)
Image.write(i3, "/tmp/color_checker_roundtrip.jpg")