├── test ├── test_helper.exs └── image_classifier_test.exs ├── priv ├── retrained_graph.pb └── retrained_labels.txt ├── .formatter.exs ├── .gitignore ├── README.md ├── LICENSE ├── config └── config.exs ├── mix.exs ├── mix.lock └── lib └── image_classifier.ex /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /priv/retrained_graph.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/velimir/image_classifier/HEAD/priv/retrained_graph.pb -------------------------------------------------------------------------------- /priv/retrained_labels.txt: -------------------------------------------------------------------------------- 1 | headphones 2 | hi fi audio speakers 3 | tools 4 | tv audio accessories 5 | tvs 6 | 7 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], 4 | line_length: 80 5 | ] 6 | -------------------------------------------------------------------------------- /test/image_classifier_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ImageClassifierTest do 2 | use ExUnit.Case 3 | doctest ImageClassifier 4 | 5 | test "greets the world" do 6 | assert ImageClassifier.hello() == :world 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | image_classifier-*.tar 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageClassifier 2 | 3 | Library that helps use Tensorflow for image classification in Elixir. 4 | 5 | Read for details in [blog post](https://www.erlang-solutions.com/blog/how-to-build-a-machine-learning-project-in-elixir.html "How to build a machine learning project in Elixir"). 6 | 7 | ## Installation 8 | 9 | The [package](https://hex.pm/packages/image_classifier) can be installed 10 | by adding `image_classifier` to your list of dependencies in `mix.exs`: 11 | 12 | ```elixir 13 | def deps do 14 | [ 15 | {:image_classifier, "~> 1.0.0"} 16 | ] 17 | end 18 | ``` 19 | 20 | The docs can be found at 21 | [https://hexdocs.pm/image_classifier](https://hexdocs.pm/image_classifier). 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Grigory Starinkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # third-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :image_classifier, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:image_classifier, :key) 18 | # 19 | # You can also configure a third-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env()}.exs" 31 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ImageClassifier.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :image_classifier, 7 | description: "Use Tensorflow for image classification in Elixir", 8 | version: "0.1.0", 9 | elixir: "~> 1.8", 10 | start_permanent: Mix.env() == :prod, 11 | package: [ 12 | maintainers: ["Grigory Starinkin"], 13 | licenses: ["MIT"], 14 | links: %{ 15 | "GitHub" => "https://github.com/velimir/image_classifier", 16 | "Post" => "https://www.erlang-solutions.com/blog/how-to-build-a-machine-learning-project-in-elixir.html" 17 | } 18 | ], 19 | deps: deps() 20 | ] 21 | end 22 | 23 | # Run "mix help compile.app" to learn about applications. 24 | def application do 25 | [ 26 | extra_applications: [:logger] 27 | ] 28 | end 29 | 30 | # Run "mix help deps" to learn about dependencies. 31 | defp deps do 32 | [ 33 | {:imgutils, "~> 0.1.1"}, 34 | {:jaypeg, "~> 0.1.0"}, 35 | {:tensorflex, 36 | git: "git@github.com:velimir/tensorflex.git", branch: "matrix-ext"}, 37 | {:elixir_make, "~> 0.5.2", runtime: false}, 38 | {:ex_doc, "~> 0.20.2", only: :dev, runtime: false} 39 | ] 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, 3 | "elixir_make": {:hex, :elixir_make, "0.5.2", "96a28c79f5b8d34879cd95ebc04d2a0d678cfbbd3e74c43cb63a76adf0ee8054", [:mix], [], "hexpm"}, 4 | "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, 5 | "imgutils": {:hex, :imgutils, "0.1.1", "52a0d0c58c2dc8a6b961ea27bfc539b64881aff838996aba43497f49b983ace9", [:make, :mix], [{:elixir_make, "~> 0.5.2", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, 6 | "jaypeg": {:hex, :jaypeg, "0.1.0", "3d439ca7c9d7e8e230cc37b1fff09d1a79f65d2bf4681bff073d247d6c9e2289", [:make, :mix], [{:elixir_make, "~> 0.5.2", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, 7 | "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 8 | "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, 9 | "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, 10 | "tensorflex": {:git, "git@github.com:velimir/tensorflex.git", "3ccd14a75082b5f0cc6619f6317562817e834b75", [branch: "matrix-ext"]}, 11 | } 12 | -------------------------------------------------------------------------------- /lib/image_classifier.ex: -------------------------------------------------------------------------------- 1 | defmodule ImageClassifier do 2 | @moduledoc """ 3 | Image classification using Tensorflow. 4 | """ 5 | 6 | @doc """ 7 | Returns the most probable label along with an accuracy for a given image. 8 | 9 | ## Examples 10 | 11 | iex> ImageClassifier.label(File.read!("file/t54wjgedk1kd3d8s.jpg")) 12 | {0.49980872869491577, "tvs"} 13 | 14 | """ 15 | def label(image) do 16 | label( 17 | image, 18 | app_file("retrained_graph.pb"), 19 | app_file("retrained_labels.txt") 20 | ) 21 | end 22 | 23 | @doc """ 24 | Returns the most probable label along with an accuracy for a given image. 25 | 26 | ## Example 27 | 28 | iex(1)> image = File.read!("tv.jpeg") 29 | <<255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, ...>>> 30 | iex(2)> {:ok, graph} = Tensorflex.read_graph("retrained_graph.pb") 31 | {:ok, 32 | %Tensorflex.Graph{ 33 | def: #Reference<0.1322660680.104464391.77632>, 34 | name: "retrained_graph.pb" 35 | }} 36 | iex(3)> labels = ImageClassifier.read_labels("retrained_labels.txt") 37 | ["headphones", "hi fi audio speakers", "tools", "tv audio accessories", "tvs"] 38 | iex(4)> ImageClassifier.label(image, graph, labels) 39 | {0.9993681311607361, "tvs"} 40 | """ 41 | def label(image, graph_path, labels) when is_binary(graph_path) do 42 | {:ok, graph} = Tensorflex.read_graph(graph_path) 43 | label(image, graph, labels) 44 | end 45 | 46 | def label(image, graph, labels_path) when is_binary(labels_path) do 47 | labels = read_labels(labels_path) 48 | label(image, graph, labels) 49 | end 50 | 51 | def label(image, graph, labels) do 52 | image 53 | |> classify_image(graph, labels) 54 | |> find_label(labels) 55 | end 56 | 57 | @doc """ 58 | Read all labels separated by a new line from a given file. 59 | 60 | ## Examples 61 | 62 | iex> ImageClassifier.read_labels("dir/retrained_labels.txt") 63 | ["headphones", "hi fi audio speakers", "tools", "tv audio accessories", "tvs"] 64 | 65 | """ 66 | def read_labels(path) do 67 | path 68 | |> File.read!() 69 | |> String.split("\n", trim: true) 70 | end 71 | 72 | def classify_image(image, graph, labels) do 73 | {:ok, decoded, properties} = Jaypeg.decode(image) 74 | in_width = properties[:width] 75 | in_height = properties[:height] 76 | channels = properties[:channels] 77 | height = width = 224 78 | 79 | {:ok, resized} = 80 | ImgUtils.resize(decoded, in_width, in_height, channels, width, height) 81 | 82 | {:ok, input_tensor} = 83 | Tensorflex.binary_to_matrix(resized, width, height * channels) 84 | |> Tensorflex.divide_matrix_by_scalar(255) 85 | |> Tensorflex.matrix_to_float32_tensor({1, width, height, channels}) 86 | 87 | {:ok, output_tensor} = 88 | Tensorflex.create_matrix(1, 2, [[length(labels), 1]]) 89 | |> Tensorflex.float32_tensor_alloc() 90 | 91 | Tensorflex.run_session( 92 | graph, 93 | input_tensor, 94 | output_tensor, 95 | "Placeholder", 96 | "final_result" 97 | ) 98 | end 99 | 100 | defp find_label(probes, labels) do 101 | List.flatten(probes) 102 | |> Enum.zip(labels) 103 | |> Enum.max() 104 | end 105 | 106 | defp app_file(name) do 107 | Application.app_dir(:image_classifier, ["priv", name]) 108 | end 109 | end 110 | --------------------------------------------------------------------------------