君だけを

はじめに

 やよいちゃんの画像が欲しかったので、なんとか作ってみました。この記事は、その方法を忘れないようにするための私的メモです。

 

 

1.やよいちゃんの画像を用意する

先ず、やよいちゃんの画像を用意します。今回は「星彩ステッパー」を踊るやよいちゃんを405枚用意しました。例↓

f:id:touchP:20190620232351p:plain

かわいい。

 

2.やよいちゃんのシルエットを作る

 画像の中でやよいちゃんが映っている場所のシルエットを作ります。私はGIMPで作りました。例↓

f:id:touchP:20190620232920p:plain

かっこいい!

 

3.Google Driveにアップロードする

Google Driveにアップロードします。ディレクトリの名前と階層構造は

data    ___ img

              |_ silhouete

としました。

 

4.深層学習でニューラルネットワーク(名前:yayoAI)に「やよいちゃん」を学ばせる

 今回深層学習に使用したフレームワークはChainerです。Chainerは訓練についての便利な関数が沢山あって私のような素人でも使えるのが嬉しいですね。

それと、Colaboratoryでは無料でGPUを使わせて貰えるのでありがたいですね。

では、プログラムを書いていきます。コピペミスが無ければColaboratoryに貼り付けて貰えれば動くと思います。“ びゅーてふるらいん ” からは程遠いコードはこちら↓

4-1.Google Driveをマウントしよう

from google.colab import drive
drive.mount('/content/drive')

4-2.データセットの準備をしよう

必要なモジュールをインポートしよう
import os
import sys

from PIL import Image
import matplotlib.pyplot as plt

import numpy as np
問題データ画像のパスリストを用意しよう
def get_img_path_list(data_dir_path):
    img_dir_path = os.path.join(data_dir_path, "img")
    img_name_list = os.listdir(img_dir_path)
    img_name_list = sorted(img_name_list)
    
    img_path_list = []
    for img_name in img_name_list:
        img_path = os.path.join(img_dir_path, img_name)
        img_path_list.append(img_path)
    
    return img_path_list


data_dir_path = input("data directory path >>")
img_path_list = get_img_path_list(data_dir_path)
print
("data number of a img_path_list : {}".format(len(img_path_list)))
解答データ画像(シルエット)パスリストを用意しよう
def get_silhouette_path_list(data_dir_path):
    silhouette_dir_path = os.path.join(data_dir_path, "silhouette")
    silhouette_name_list = os.listdir(silhouette_dir_path)
    silhouette_name_list = sorted(silhouette_name_list)
    
    silhouette_path_list = []
    for silhouette_name in silhouette_name_list:
        silhouette_path = os.path.join(silhouette_dir_path, silhouette_name)
        silhouette_path_list.append(silhouette_path)
    
    return silhouette_path_list


silhouette_path_list = get_silhouette_path_list(data_dir_path)
print("data number of a silhouette_path_list : {}".format(len(silhouette_path_list)))
二つのリストを見比べてファイル名が同じものの組をつくろう
def get_path_data_set(img_path_list, silhouette_path_list):
    path_data_set = []
    for img_path in img_path_list:
        img_dir_path, img_name = os.path.split(img_path)
        for silhouette_path in silhouette_path_list:
            silhouette_dir_path, silhouette_name = os.path.split(silhouette_path)
            if img_name == silhouette_name:
                path_data = (img_path, silhouette_path)
                path_data_set.append(path_data)
    
    return path_data_set


path_data_set = get_path_data_set(img_path_list, silhouette_path_list)
print("data number of a path_data_set : {}".format(len(path_data_set)))
つくったパスの組をもとに画像をピックアップしてデータセットをつくろう
def get_img_data_set(path_data_set):
    img_data_set = []
    for i, path_data in enumerate(path_data_set):
        #この↓の2行と最後のprint()はファイルを読み込んでるよアピールなのでなくてもいいよ(あると出力がかっこいいよ)
        sys.stdout.write("\r Now Loading : {:.2f}% ".format(1/len(path_data_set)))
        sys.stdout.flush()
        
        img_path, silhouette_path = path_data
        img = np.asarray(Image.open(img_path))
        silhouette = np.asarray(Image.open(silhouette_path))
        img_data = (img, silhouette)
        img_data_set.append(img_data)
    
    print()
    return img_data_set


img_data_set = get_img_data_set(path_data_set)
print("data number of a img_data_set : {}".format(len(img_data_set)))

plt.figure()
plt.imshow(img_data_set[0][0])
plt.title('img example')

plt.figure()
plt.imshow(img_data_set[0][1])
plt.title('silhouette example')

4-3.訓練するNeural Networkを定義しよう

いよいよChainerを使います。

必要なモジュールのインポート
import chainer
import chainer.functions as F
import chainer.links as L
from chainer import Chain
Neural Networkを定義しよう

今回はテストなのでテキトウにNetworkを作りました。

class yayo_ai(Chain):
    def __init__(self):
        super(yayo_ai, self).__init__()
        
        with self.init_scope():
            
            self.l0 = L.Linear(None, 4096)
            self.l1 = L.Linear(4096, 4096)
            self.l2 = L.Linear(4096, 2)
        

    def __call__(self, x):
        
        f = F.relu(self.l0(x))
        f = F.relu(self.l1(f))
        y = self.l2(f)

        return y

画像認識なのにMLPって。テキトウにも程があるだろ・・・。

4-4.yayoAIを訓練しよう

yayoAIのお勉強のお時間です。

ここからは大体Chainerのチュートリアル通りです。

必要なモジュールをインポートしよう
from chainer import optimizers, iterators, training, serializers
from chainer.training import Trainer, extensions

#↓の3つのモジュールは訓練に直接必要なモジュールではないけれど、今回の仕様では必要でした
from chainer.dataset import concat_examples
import random
import cv2
定義したNetworkのインスタンスをつくろう
gpu_id = 0 #GPUを使いたいときは0以上の整数にしよう
model = L.Classifier(yayo_ai())
model.to_gpu(gpu_id) #GPUを使うオプションを付けるよ
使いたい最適化手法を選択しよう
optimaizer = optimizers.MomentumSGD()
optimaizer.setup(model)
データセットイテレータ―にセットしよう

イテレータ―はかってにイテラブルオブジェクトを作ってくれるとっても便利なやつです。 
今回は訓練に使われる画像が全部違うようにするのでvalidation用は無くてもいいかなぁって?

batchsize = 16

train_iter = iterators.SerialIterator(img_data_set, batchsize, repeat=True, shuffle=True)
 アップデーターを用意しよう

今回は、メモリ節約の為、訓練にデータセットを直接使うのではなく、アップデーターでごにょごにょしてから訓練します。

・ごにょごにょに使う関数たち

解答ラベルとシルエットの色(RGB)を対応付ける

def get_target_color(label):
    if label == 0:
        target_color = (255,0,0)
    else:
        target_color = (0,0,255)
    
    return target_color

シルエット画像の指定された色の場所から適当に一ヶ所選ぶ

def view_pointer(img, target_color):
    img = cv2.inRange(img, target_color, target_color)
    nonzero_index = np.array(np.nonzero(img))
    view_point_list = np.transpose(nonzero_index, (1,0))
    i = random.randint(0, len(view_point_list) - 1)
    view_point = view_point_list[i]

    return view_point

yayoAIの網膜に映る像を作る

#yayoAIの目の性能を決めるパラメータを指定しておくよ
retina_size = (64, 64) #網膜の細胞数だよ (height, width)で指定
focal_lenght = 20 #焦点距離だよ

def yayo_ai_eye(img, retina_size, view_point, focal_lenght):
    img = np.asarray(img)
    h, w = retina_size
    img_h, img_w, img_ch = img.shape
    view_point = np.asarray(view_point)
    view_img = np.zeros((h, w, img_ch)).astype("uint8") #astype("uint8")は少しでも計算を軽くするため
    for i in range(h):
        for j in range(w):
            r = np.asarray([i - (h/2), j - (w/2)])
            d = np.sqrt(np.dot(r,r.T))
            R = r * np.exp(d/focal_lenght) + view_point
            k, l = R.astype("int")
            if 0 < k < img_h and 0 < l < img_w:
                view_img[i][j] = img[k][l]

    return view_img

これを使うと霊長類の網膜に映る像に近い画像が作れるそうですよ 。使った例↓

f:id:touchP:20190621011333p:plain 真ん中はよく見えて、全体を把握できる。

アップデーターで使うコンバーターを用意しよう

これはとっても有益なQiitaの記事「Chainerで画像を読み込む際のTips - Qiita」を参考にしました。

def my_converter(batch, device=None, padding=None):
    train_batch = []
    for img_data in batch:
        label = random.randint(0,1) #このデータのラベルを適当に決める
        
        img = img_data[0]
        silhouette = img_data[1]
        
        target_color = get_target_color(label) #ラベルに対応する色を取得
        view_point = view_pointer(silhouette, target_color) #色に対応するシルエット画像の場所を適当に一か所取得
        view_img = yayo_ai_eye(img, dsize, view_point, magnification_ratio) #その場所を見た時の像を取得
        
        img_array = np.asarray(view_img).transpose(2,0,1).astype(np.float32) / 255. #画像をChainer形式に変換
        train_batch.append((img_array,label))
    
    return concat_examples(train_batch, device=device, padding=padding)

 ・イテラブルオブジェクトをアップデーターにセットしよう

updater = training.StandardUpdater(train_iter, optimaizer, device=gpu_id, converter=my_converter)
アップデーターをトレーナーにセットしよう
max_epoch = 40
result_output_dir_path = input("train result output directory path >>") output_dir_path = os.path.join(result_output_dir_path, "result") trainer = Trainer(updater, stop_trigger=(max_epoch, 'epoch'), out=output_path)
トレーナーに機能を追加しよう

validationが無いので、いんたーねっとでよく見るものより寂しくなっています。

trainer.extend(extensions.LogReport())
trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'main/accuracy', 'elapsed_time']))
trainer.extend(extensions.PlotReport(['main/loss'], x_key='epoch', file_name='loss.png'))
trainer.extend(extensions.PlotReport(['main/accuracy'], x_key='epoch', file_name='accuracy.png'))
trainer.extend(extensions.dump_graph('main/loss'))
trainer.extend(extensions.snapshot(filename='snapshot'))
訓練開始!

trainer.run()

Chainerが勝手にかっこいい表を作ってくれるので、ワクワクしながら終わるのを待ちましょう。

 訓練したモデルを保存しよう
model_basename = input("model basename >>")
output_model_path = os.path.join(output_dir_path, model_basename, ".model")
serializers.save_npz(output_model_path, model)

 これでyayoAIのお勉強はおしまいです。

 

 5.訓練したNeural Network(yayoAI)で推論しよう

 yayoAIに勉強の成果を発揮してもらいましょう!

5-1.推論する画像を準備しよう

 必要なモジュールのインポート
import sys

from PIL import Image
import matplotlib.pyplot as plt

import numpy as np
推論する画像を取得しよう
img_path = input("Please input test img path >>")
img = np.asarray(Image.open(img_path))
plt.imshow(img)
画像のチェックして欲しい場所を指定しよう
def get_view_point_list(img, check_interval_h, check_interval_w):
    img_h, img_w = img.shape[:2]
    view_point_list = []
    for h in range(int(check_interval_h/2), img_h, check_interval_h):
        for w in range(int(check_interval_w/2), img_w, check_interval_w):
            view_point = (h, w)
            view_point_list.append(view_point)
    print("view_point = (height, width)")
    return view_point_list

#今回は10ピクセルおきにチェックしてもらうよ
check_interval_h = 10
check_interval_w = 10
view_point_list = get_view_point_list(img, check_interval_h, check_interval_w)
print("point number of a view_point_list : {}".format(len(view_point_list)))
指定した各点をyayoAIが見た時の画像を取得しよう
#これも訓練のときと同じがいいと思います。
retina_size = (64,64)
focal_lenght = 20

def
yayo_ai_eye(img, retina_size, view_point, focal_lenght): img = np.asarray(img) h, w = retina_size img_h, img_w, img_ch = img.shape view_point = np.asarray(view_point) view_img = np.zeros((h, w, img_ch)).astype("uint8") #astype("uint8")は少しでも計算を軽くするため for i in range(h): for j in range(w): r = np.asarray([i - (h/2), j - (w/2)]) d = np.sqrt(np.dot(r,r.T)) R = r * np.exp(d/focal_lenght) + view_point k, l = R.astype("int") if 0 < k < img_h and 0 < l < img_w: view_img[i][j] = img[k][l] return view_img

def
get_view_img_list(img, retina_size, view_point_list, focal_lenght): view_img_list = [] for i, view_point in enumerate(view_point_list): view_img = yayo_ai_eye(img, retina_size, view_point, focal_lenght) view_img_list.append(view_img) sys.stdout.write("\r Now Loading {:.2f}%".format((i+1) / len(view_point_list) * 100)) sys.stdout.flush() print() return view_img_list


view_img_list = get_view_img_list(img, retina_size, view_point_list, focal_lenght)

print
("img number of a view_img_list : {}".format(len(view_img_list)))

plt.figure()
plt.imshow(view_img_list[int(len(view_img_list)/2 + (192/2))])
plt.title('view_img example')
plt.imsave('view_img example', view_img_list[int(len(view_img_list)/2 + (192/2))])
 各点の画像をChainer用の配列にしよう
def get_img_array_list(view_img_list):
    img_array_list = []
    for view_img in view_img_list:
        img_array = np.asarray(view_img).transpose(2,0,1).astype(np.float32) / 255.
        img_array_list.append(img_array)
    
    img_array_list = np.asarray(img_array_list)
    return img_array_list


img_array_list = get_img_array_list(view_img_list)
print("data number of a img_array_list : {}".format(len(img_array_list)))

5-2. 推論するNeural Networkを準備しよう

必要なモジュールのインポート
import chainer
import chainer.functions as F
import chainer.links as L
from chainer import Chain
from chainer import serializers
Neural Networkを用意しよう

訓練したものと同じ構造のNetworkにしよう。

class yayo_ai(Chain):
    def __init__(self):
        super(yayo_ai, self).__init__()
        
        with self.init_scope():
            
            self.l0 = L.Linear(None, 4096)
            self.l1 = L.Linear(4096, 4096)
            self.l2 = L.Linear(4096, 2)
        

    def __call__(self, x):
        
        f = F.relu(self.l0(x))
        f = F.relu(self.l1(f))
        y = self.l2(f)

        return y


model = L.Classifier(yayo_ai())
infer_NN = model
訓練したモデルを読み込もう
infer_NN_model_path = input("infer neural network model path >>")
serializers.load_npz(infer_NN_model_path, infer_NN)

5-3.推論させよう

必要なモジュールのインポート
import chainer.functions as F
from chainer import iterators

import cv2
テスト画像のリストをイテレーターにセットしよう

便利なのでここでも使いました。

test_batchsize = 2048
test_iter = iterators.SerialIterator(img_array_list, test_batchsize, repeat=False, shuffle=False)
推論させよう!

頑張れyayoAI!

各点における確率を予想させます。

pred_probability_list = []
for i in range(0, len(view_point_list) // test_batchsize + 1):
    sys.stdout.write("\r Now Loading {:.2f}%".format((i+1) / (len(view_point_list) // test_batchsize + 1) * 100))
    sys.stdout.flush()
    
    pred_result_batch = infer_NN.predictor(test_iter.next())
    probability_batch = F.softmax(pred_result_batch)
    pred_probability_list += list(probability_batch.data)
print()

print("data number of a pred_probability_list : {}".format(len(pred_probability_list)))
確信度別に分けよう
confidence_factor = 0.7 #確信度
pred_label_list = []
for pred_probability in pred_probability_list:
    if pred_probability[1] > confidence_factor:
        pred_label_list.append(1)
    else:
        pred_label_list.append(0)

print("data number of a pred_label_list : {}".format(len(pred_label_list)))
結果を画像で表現しよう
pred_silhouette = np.zeros(img.shape[:2]).astype("uint8")
for pred_label, point in zip(pred_label_list, view_point_list):
    h, w = point
    if pred_label == 1:
        pred_silhouette[h-int(check_interval_h/2):h+int(check_interval_h/2),w-int(check_interval_w/2):w+int(check_interval_w/2)] = 255

yayo_img = np.zeros_like(img) yayo_img[:,:,0] = cv2.bitwise_and(img[:,:,0], pred_silhouette) yayo_img[:,:,1] = cv2.bitwise_and(img[:,:,1], pred_silhouette) yayo_img[:,:,2] = cv2.bitwise_and(img[:,:,2], pred_silhouette) plt.imshow(yayo_img)
cv2.imwrite("pred_silhouette.png", pred_silhouette) #openCVを使ってますが特に意味はありません。matplotlibで白黒の扱い方が良く分からなかっただけです。
plt.imsave("yayo_img.png", yayo_img)

 

 結果

f:id:touchP:20190621013804p:plain

 賢い!

 あんなテキトウなNetworkなのに結構いい感じにやよいちゃんだけを切り取ってくれました。

 

他の結果

f:id:touchP:20190621014537p:plain

f:id:touchP:20190621014548p:plain

 おしい!どうやらやよいちゃんの背後に木があると難しいみたいです。茶色と肌色が分かりずらいのかな?

 

まだまだ精度は悪いですが、とりあえず今回はプログラムが動いてくれたので満足。少しずつでも改良していけたらと思います。

 

 

 さいごに

  深層学習というものを知ってから、ここまで来るのに一年以上経ってしまいました。インターネットを見ると、これくらいのことは数か月や数週間で出来る人達ばかりで、自分の歩みの遅さにへこんでしまいます。ですが、どんなに小さくとも一歩は一歩と思って、進んでいけたらなぁと思っています。

 いつか、やよいちゃんと顔を合わせてお話できたらいいなぁ。