post Image
Unity上でTensorFlowのCNNを動かす。

Unity上でTensorFlowのCNNを動かす。
この記事でTensorFLowSharpは導入済みであると仮定している.
ここを記事を見て、手持ちの環境に合わせてTensorFlowSharpをビルドしておく。

unityの導入

文法的にC#6でないと動かないと所があるので、ここからから最新版(Unity 2017.1以上)を落としておく。
https://store.unity.com/download?ref=personal

プロジェクトの作成及び準備

プロジェクトを作成後は、Assetsの下にDllsというフォルダを作り、TensorFlowSharp\TensorFlowSharp\bin\Debug の下のTensorFlowSharp.dllとSystem.ValueTuple.dllをコピーしておく。

スクリーンショット 2017-06-09 00.33.19.png

Scripting Runtime VersionをExperiental(.NET4.6 Equvalent)に変更する
スクリーンショット 2017-06-09 00.34.51.pngスクリーンショット 2017-06-09 00.35.32.png

変更後、unityの再起動する。

数字の入力用のパッドを作成する

Scriptsを作って、paint.csを作成する。
スクリーンショット 2017-06-09 00.41.00.png
スクリーンショット 2017-06-09 00.42.56.png

paint.cs
// 参考
// http://nn-hokuson.hatenablog.com/entry/2016/12/08/200133
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class paint : MonoBehaviour {

    Texture2D drawTexture;
    Color[] buffer;

    // Use this for initialization
    void Start () {
        Texture2D mainTexture = (Texture2D)GetComponent<Renderer>().material.mainTexture;
        Color[] pixels = mainTexture.GetPixels();

        buffer = new Color[pixels.Length];
        pixels.CopyTo(buffer, 0);

        drawTexture = new Texture2D(mainTexture.width, mainTexture.height, TextureFormat.RGBA32, false);
        drawTexture.filterMode = FilterMode.Point;
    }
    // ブラシの太さを変える
    public void Draw(Vector2 p)
    {
        //buffer.SetValue(Color.black, (int)p.x + 256 * (int)p.y);

        //太字
        for (int x = 0; x < 256; x++)
        {
            for (int y = 0; y < 256; y++)
            {
                if ((p - new Vector2(x, y)).magnitude < 5)
                {
                    buffer.SetValue(Color.black, x + 256 * y);
                }
            }
        }
    }

    // 毎フレーム、テクスチャ上のすべてのピクセルをチェックして、マウスが乗っている座標からの距離が8以下なら黒く塗りつぶします。
    void Update () {
        if (Input.GetMouseButton(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 100.0f))
            {
                Draw(hit.textureCoord * 256);
            }

            drawTexture.SetPixels(buffer);
            drawTexture.Apply();
            GetComponent<Renderer>().material.mainTexture = drawTexture;
        }
    }
        // テクスチャをjpgとして保存
    public void SaveTexture()
    {
        byte[] data = drawTexture.EncodeToJPG();
        File.WriteAllBytes(Application.dataPath + "/saveImage.jpg", data);
    }
}

入力パッドとなるplaneを追加する。
スクリーンショット 2017-06-09 00.50.24.png
スクリーンショット 2017-06-09 00.50.43.png

planeの位置を以下のように変更する。
スクリーンショット 2017-06-09 00.52.54.png

Main cameraを操作する。
x:1, y:5, z:-9 がちょうどいい
スクリーンショット 2017-06-09 00.56.28.png

↓テクスチャ画像


texture.jpg


Assetsの直下にtexture.jpgを配置する。
スクリーンショット 2017-06-09 00.59.39.png

texture.jpgをplaneにドラッグアンドドロップして、アタッチする。
スクリーンショット 2017-06-09 01.01.12.png

Albedoをクリックすると、texture.jpgがアタッチされていることが確認できる
スクリーンショット 2017-06-09 01.02.00.png

Shader をStandard から Unit->Textureに変更する
スクリーンショット 2017-06-09 01.07.07.png

次にtexture.jpgを選択して、Read/Write Enabledにチェックを入れる。
スクリーンショット 2017-06-09 01.04.37.png

planeにpaint.csをアタッチする
スクリーンショット 2017-06-09 1.10.16.png

すると、planeに書き込めるようになる
スクリーンショット 2017-06-09 01.13.07.png

入力された数字画像の保存と認識をするためのボタンを作成する

次はボタンを追加する。
スクリーンショット 2017-06-09 01.13.57.png

ボタンのtextをRrecognitionにする
スクリーンショット 2017-06-09 01.15.40.png

ボタンのonclickの設定を行う。
Inspectorの on Click()の[+]を押して、リスナーのメソッドを追加する。paint.SaveTexture()を追加する。
スクリーンショット 2017-06-09 01.16.15.png

paint.csはplaneにアタッチしてあるので、planeを選択する。
スクリーンショット 2017-06-09 01.16.27.png

メソッドは paint -> SaveTexture() を選択する。
スクリーンショット 2017-06-09 01.16.44.png

そして、planeに何かを書き込み後、ボタンを押すとAssetsの直下に画像が保存される。

数字の認識のための準備を行う。

paint.csにコードを加筆する。

paint.cs
public void SaveTexture()
{
    ・・・
    Debug.Log("Environment.CurrentDirectory");
    Debug.Log("TensorFlow version: " + TFCore.Version);

    var t = new SampleTest.MainClass();
    t.MNSIT_read_model();
}

このような構成にして、スクリプトや学習モデルを配置する。学習モデルは、前回作成したものを使用する。

Assets
 ├── Scripts
 │   ├── DataConverter.cs
 │    ├── Datasets
 │    │    ├── Helper.cs
 │   │    └── MNIST.cs
 │   ├── SampleTest.cs
 │    └── paint.cs
 └── models
     ├── Auto_model.pb
     └── labels.txt

Assetsの直下にmodelsを作成して、中に前回の記事で作成したAuto_model.pbとlabels.txtを入れる。

TensorFlowSharpのLearnの中のDataConverter.csとDatasets/Helper.cs, Datasets/MNIST.csをScriptsの下に配置する。

Sample.cs
using System.Collections;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TensorFlow;
using System.IO;
using System.Collections.Generic;
using Learn.Mnist;
using System.Linq;
using UnityEngine;

namespace SampleTest
{
    class MainClass
    {

        // Convert the image in filename to a Tensor suitable as input to the Inception model.
        static TFTensor CreateTensorFromImageFile(string file)
        {
            var contents = File.ReadAllBytes(file);

            // DecodeJpeg uses a scalar String-valued tensor as input.
            var tensor = TFTensor.CreateString(contents);

            TFGraph graph;
            TFOutput input, output;

            // Construct a graph to normalize the image
            ConstructGraphToNormalizeImage(out graph, out input, out output);

            // Execute that graph to normalize this one image
            using (var session = new TFSession(graph))
            {
                var normalized = session.Run(
                    inputs: new[] { input },
                    inputValues: new[] { tensor },
                    outputs: new[] { output });

                return normalized[0];
            }
        }

        //開始モデルは、非常に特定の正規化されたフォーマット(特定の画像サイズ、入力テンソルの形状、正規化されたピクセル値など)
        //でテンソルによって記述された画像を入力として取ります。
        //このファンクションは、入力としてJPEGでエンコードされた文字列を取り込み、
        //入力モデルとしての入力として適したテンソルを戻すTensorFlow操作のグラフを作成します。
        static void ConstructGraphToNormalizeImage(out TFGraph graph, out TFOutput input, out TFOutput output)
        {
            // - モデルは28x28ピクセルにスケーリングされた画像で訓練されました。
            // - モノクロなので表される色は1色のみ。(値 - 平均)/ スケールを使用してfloatに変換して使用する。
            // 画素値を0-255 から 0-1 の範囲にするので、変換値 = (Mean - 画素値) / Scale の式から,
            // Mean = 255, Scale = 255 となる。

            const int W = 28;
            const int H = 28;
            const float Mean = 255;
            const float Scale = 255;
            const int channels = 1;

            graph = new TFGraph();
            input = graph.Placeholder(TFDataType.String);

            output = graph.Div(
                x: graph.Sub(
                  x: graph.Const(Mean, "mean"),
                    y: graph.ResizeBilinear(
                        images: graph.ExpandDims(
                            input: graph.Cast(graph.DecodeJpeg(contents: input, channels: channels), DstT: TFDataType.Float),
                            dim: graph.Const(0, "make_batch")),
                        size: graph.Const(new int[] { W, H }, "size"))),
                y: graph.Const(Scale, "scale"));
        }
        // pythonで作成したモデルの読込を行う
        public void MNSIT_read_model()
        {
            var graph = new TFGraph();

            //var model = File.ReadAllBytes("tensorflow_inception_graph.pb");

            // シリアル化されたGraphDefをファイルからロードします。
            var model = File.ReadAllBytes(Application.dataPath + "/models/Auto_model.pb");
            graph.Import(model, "");

            using (var session = new TFSession(graph))
            {
                var labels = File.ReadAllLines(Application.dataPath + "/models/labels.txt");

                var file = Application.dataPath + "/saveImage.jpg";

                //画像ファイルに対して推論を実行する
                //複数のイメージの場合、session.Run()はループで(同時に)呼び出すことができます。
                //あるいは、モデルが画像データのバッチを入力として受け入れるので、画像をバッチ処理することができる。
                var tensor = CreateTensorFromImageFile(file);

                var runner = session.GetRunner();
                // 学習モデルのグラフを指定する。
                // 入出力テンソルの名前をsessionに登録する
                // 手動で変換したモデルの読込のときは、.AddInput(graph["dropout"][0], 0.5f)はいらない。
                runner.AddInput(graph["input"][0], tensor).AddInput(graph["dropout"][0], 0.5f).Fetch(graph["output"][0]);
                var output = runner.Run();

                // output[0].Value()は、「バッチ」内の各画像のラベルの確率を含むベクトルです。 バッチサイズは1であった。
                //最も可能性の高いラベルインデックスを見つけます。
                var result = output[0];
                var rshape = result.Shape;
                if (result.NumDims != 2 || rshape[0] != 1)
                {
                    var shape = "";
                    foreach (var d in rshape)
                    {
                        shape += $"{d} ";
                    }
                    shape = shape.Trim();
                    Debug.Log($"Error: expected to produce a [1 N] shaped tensor where N is the number of labels, instead it produced one with shape [{shape}]");
                    Environment.Exit(1);
                }

                var bestIdx = 0;
                float best = 0;
                // 尤も確率が高いものを調べて表示する
                var probabilities = ((float[][])result.GetValue(true))[0];
                for (int i = 0; i < probabilities.Length; i++)
                {
                    if (probabilities[i] > best)
                    {
                        bestIdx = i;
                        best = probabilities[i];
                    }
                }
                Debug.Log($"{file} best match: [{bestIdx}] {best * 100.0}% {labels[bestIdx]}");
            }
        }
    }
}

実行する

△の90°傾けたボタンを押し、入力パッドに数字を入力後、Rrecognitionボタンを押すと、結果が表示される。

実行画面
スクリーンショット 2017-06-11 00.34.16.png
スクリーンショット 2017-06-11 00.35.30.png

プロジェクトを上げておく。
https://github.com/MaruTama/tensorflow_for_unity

参考
Unityでテクスチャにお絵描きしよう


『 機械学習 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。