本記事はDEMO動画にある、
「Unity+AIを用いた手書き数字識別システム」の実装方法について説明する。
前回の記事
www.hiro877.com
本記事のテーマ
「csv読み込みとObjectsへの画像貼り付け」
を実装する。
下記DEMO動画内の1:34付近。
CSVから読み込んだ画像データをPlaneObjectsに貼り付ける処理
Demo
www.youtube.com
実装動画
本記事の実装を録画した動画です。
www.youtube.com
実装環境
- Ubuntu20.04
- UnityHub2.4.2
- Unity 2019.4.21f1
ソースコード
Githubで管理
・paste-images-into-planeブランチ
ブランチの切り替えは下記画像の赤枠をクリックして変更できる。

github.com
CSVの読み込み
今回はResources.Load(file_path)で行う。
TextAsset csv = Resources.Load(file_path) as TextAsset;
この関数はAssets/Resourcesディレクトリ内のファイルをResourcesディレクトリ内からの相対パスで検索します。
ですので、まず、Assets下にResourcesディレクトリを作成してその中に読み込みたいファイルを配置してください。
今回はMNISTの手書き数字をもちいます。
GithubのAssets/Resourcesにfor_saving.zipという名前で置いていますのでDownloadしてご使用ください。
Unity-htm.core-MNIST/Unity-Learning-MNIST/Assets/Resorces at paste-images-into-plane · hiro877/Unity-htm.core-MNIST · GitHub
下記関数でコンマ区切りのCSVファイルが読み込める。
行っていることは文字列(数字)のCSVデータを1行づつ読み込んで、Byte型に変換し、1行づつの2次元配列を作成しています。
※今回用意したデータは28*28=784このデータが30行分です。
csvReaderPlaneImages = read_mnist_images(csvReaderPlane_imeges_path);
List<List<byte>> read_mnist_images(string file_path)
{
TextAsset csv = Resources.Load(file_path) as TextAsset;
StringReader reader = new StringReader(csv.text);
List<List<byte>> data = new List<List<byte>>();
while(reader.Peek() != -1)
{
string[] str_line = reader.ReadLine().Split(',');
List<byte> line = new List<byte>();
foreach(string str in str_line)
{
line.Add(byte.Parse(str));
}
data.Add(line);
}
return data;
}
CSVデータ(1次元)から画像データ(2次元)へ変換
引数に1次元のList<byte>をとり、縦28, 横28のList<List<byte>>に変換して返します。
List<List<byte>> convert_data( List<byte> data_array,
bool is_texture_axis=false)
{
List<List<byte>> d = new List<List<byte>>();
for (int i = 0; i < IMG_HEIGHT; i++) d.Add(new List<byte>());
for(int y = 0; y < IMG_HEIGHT; y++)
{
for(int x = 0; x < IMG_WIDTH; x++)
{
if (is_texture_axis)
{
d[IMG_HEIGHT - y -1].Add(data_array[y * IMG_HEIGHT + x]);
}
else
{
d[y].Add(data_array[y * IMG_HEIGHT + x]);
}
}
}
return d;
}
画像(List<List<byte>>)をPlaneObjectsに貼り付ける
List<List<byte>>をTextureに変換してObjectsに貼り付けます。
まずはデータを一つずつColorクラスに変換してSetPixelでattach_textureに追加していきます。
attach_texture.Apply()でSetPixelで追加したデータがTextureに反映されます。
最後に貼り付けたいtargetオブジェクトのmainTextureに代入して完了です。
public void AttachImage(GameObject target, List<List<byte>> byte2D,
int width, int height){
Texture2D attach_texture = new Texture2D(width, height);
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
attach_texture.SetPixel(x, y, new Color(byte2D[y][x], byte2D[y][x], byte2D[y][x]));
}
}
attach_texture.Apply();
target.GetComponent<MeshRenderer>().material.mainTexture = attach_texture;
}
Spaceキーで画像を表示
今回はわかりやすいために、Spaceキーを押したときに画像が出来るようにしました。
void Update()
{
if(Input.GetKey("space")){
Debug.Log("Pushed Tab");
if (!testDisplayFlag){
List<byte> img = csvReaderPlaneImages[0];
List<List<byte>> img_byte2D = convert_data(img, true);
AttachImage(csvReaderPlane, img_byte2D, IMG_WIDTH, IMG_HEIGHT);
testDisplayFlag = true;
}
}
}
ソースコード
▶クリックでソースコードを展開
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
public class SmallCubeDirector : MonoBehaviour
{
// Fungus
public Fungus.Flowchart flowchart = null;
// Display Test Image
public GameObject csvReaderPlane;
private List<List> csvReaderPlaneImages;
private string csvReaderPlane_imeges_path = "test_images30";
private bool testDisplayFlag = false;
// MNIST Image
private const int IMG_WIDTH = 28;
private const int IMG_HEIGHT = 28;
void Awake() {
csvReaderPlane = GameObject.FindGameObjectWithTag("csvReaderPlane");
}
void Start()
{
csvReaderPlaneImages = read_mnist_images(csvReaderPlane_imeges_path);
}
// Update is called once per frame
void Update()
{
if(Input.GetKey("space")){
Debug.Log("Pushed Tab");
if (!testDisplayFlag){
List img = csvReaderPlaneImages[0];
List<List> img_byte2D = convert_data(img, true);
AttachImage(csvReaderPlane, img_byte2D, IMG_WIDTH, IMG_HEIGHT);
testDisplayFlag = true;
}
}
}
public void StartFungus(string message){
flowchart.SendFungusMessage(message);
}
public void StartFungusFromEventPlane(){
flowchart.SendFungusMessage("start_question");
}
List<List> read_mnist_images(string file_path)
{
TextAsset csv = Resources.Load(file_path) as TextAsset;
StringReader reader = new StringReader(csv.text);
List<List> data = new List<List>();
while(reader.Peek() != -1)
{
string[] str_line = reader.ReadLine().Split(',');
List line = new List();
foreach(string str in str_line)
{
line.Add(byte.Parse(str));
}
data.Add(line);
}
return data;
}
List<List> convert_data( List data_array,
bool is_texture_axis=false)
{
List<List> d = new List<List>();
for (int i = 0; i < IMG_HEIGHT; i++) d.Add(new List());
for(int y = 0; y < IMG_HEIGHT; y++)
{
for(int x = 0; x < IMG_WIDTH; x++)
{
if (is_texture_axis)
{
d[IMG_HEIGHT - y -1].Add(data_array[y * IMG_HEIGHT + x]);
}
else
{
d[y].Add(data_array[y * IMG_HEIGHT + x]);
}
}
}
return d;
}
public void AttachImage(GameObject target, List<List> byte2D,
int width, int height){
Texture2D attach_texture = new Texture2D(width, height);
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
attach_texture.SetPixel(x, y, new Color(byte2D[y][x], byte2D[y][x], byte2D[y][x]));
}
}
attach_texture.Apply();
target.GetComponent().material.mainTexture = attach_texture;
}
}
以上で実装は終了になります。
これでSpaceキーを押した後に画像がPlaneObjectsに表示されます。
もし、動かない場合はコメント欄にて報告して頂けますと助かります。
関連記事
www.hiro877.com