3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

どうびじゅの連載が終了し心に深い傷を負うも素晴らしい最終回に胸をうたれ未来への希望を明日に見出すきららMAX読者のための機械学習を使ったキャラクター分類入門

Last updated at Posted at 2019-04-14

#はじめに

あっ...(落涙)
前回から1ヶ月の間、僕は胃痛と不眠と吐き気に悩まされ続けていました。
雑念を振り払うように言語処理100本ノックに打ち込み胃痛と戦う日々を過ごし、あっという間に1ヶ月が過ぎ去り5月号の発売日が来てしまいました。
内容は言うまでもなく、この1ヶ月の僕の苦しみを救済するような本当に素晴らしい最終回でした。お陰で僕は廃人同然の生活から脱却し、今では機械学習のデータ集めと称して毎日どうびじゅの画像を眺めることを心の拠り所にする毎日を送っています。今回はTensorflowのretrain.pyを使って画像認識の転移学習を使って、主要キャラ5人の判定器を作りたいと思います。

#環境

# python3 --version                                                                                                               
Python 3.7.2+
# pip3 show tensorflow 
Name: tensorflow
Version: 1.13.1
# pip3 show tensorflow-hub
Name: tensorflow-hub
Version: 0.3.0
------------------------------------------------------
# pip3 show pillow                                       
Name: Pillow
Version: 5.4.1
# pip3 python -m tkinter
This is Tcl/Tk version 8.6
# pip3 show matplotlib 
Name: matplotlib
Version: 2.2.2

#前提
はじめに書いた通り、Tensorflowのretrain.pyを使って画像認識の転移学習を使って、どうして私が美術科に!?の主要キャラ5人を判別します。
主要キャラは桃音、黄奈子、蒼、紫苑、翠玉の5人です。

retrain.pyを使い学習を行い、生成された分類器に判定したい画像をlabel_imageに引数で与えると各ラベルとの類似度を返すのでその値でどのキャラクターなのかを判定を行います。

説明を楽にするため、登場するディレクトリを以下のように仮定します。

作業ディレクトリ:~/doubiju_train/
分類前の画像:~/Picture/data_src/
分類後の画像:~/doubiju_train/data/

また、retrain.pyを今回使いますが、何か問題が起きたら、公式サイトをみて下さい。僕もいろいろなサイトを参考にして、手詰まりになりましたが公式サイトの通りにやったら一発でした。
https://www.tensorflow.org/hub/tutorials/image_retraining
#学習に必要なデータを集める
まずは、学習に必要なデータを集めるのですが、その前に一つ疑問になることがあります。
##著作権
著作権です。今回必要なデータとなるイラストや漫画の画像には、当然著作権が存在します。もし、法律で複製が認められていないならそれを守るべきです。好きな作品なら尚更です。
早速ボツネタだなこれ...と思いながら調べてみるとこんな記事が。

この条文を簡単に言うと、「情報解析目的なら、データなどの著作物を記録媒体にコピーできる」ということ。2009年に改正された条文で、当時はWeb解析や言語解析、自動翻訳の技術開発などを想定していたという。現在は(学習の手法にもよるが)、機械学習や深層学習も「情報解析」に含まれるとの考え方が主流だと、柿沼弁護士は話す。
「日本は機械学習パラダイス」 その理由は著作権法にあり - ITmedia NEWS

どうやら著作権法47条の7が肝らしい。

さらに調べて見ると、

情報解析のための複製(著作権法第47条の7)
コンピュータを使った情報解析のために、必要と認められる限度において、著作物を複製することができる。
著作物が自由に使える場合は? | 著作権って何? | 著作権Q&A | 公益社団法人著作権情報センター CRIC

本当だ。サンキュー著作権法。
ただし、複製はあくまでも機械学習などの情報解析の場合に認められており、複製した情報をそのまま公開するなどの行為は著作権法に違反します。
データを使わせていただく側なのでこれらのことには十分気をつけましょう。

##画像を集める
今度こそ本筋です。今回はキャラクターの分類・判定を行うので顔が写っている画像をかたっぱしから集めます。とにかく沢山です。多すぎということはありません。
僕は、単行本と所持している雑誌から顔が写っている部分を無音カメラで撮影し、PCに取り込みました。このとき写真はスクエアで撮影すると後で楽です。
また、作者の過去のツイートなどから画像を収集し、同様に保存しました。
##画像を加工する
顔でキャラクターを判定するので、画像に余計なものが写りこんでいると検知率が落ちてしまいます。なので、画像に顔だけが写るように画像を切り抜かなくてはいけません。
方法はいろいろありますが、後述する効率化のプログラムを使わないならアニメキャラの顔を検知するカスケード分類器があるのでそれを利用するのが簡単だと思います。
僕は、画像の少なさを正確さで補いたかったので手作業で加工しました。(後述)
##画像を分類する
いわゆるラベルづけの作業です。画像がどのキャラクターの画像かを教えていきます。
今回の場合は学習用のデータを格納するディレクトリを作成し、

# mkdir ~/doubiju_train/data/

その中に分類したい種類ごとにディレクトリを作成していきます。

# mkdir ~/doubiju_train/data/momone/
# mkdir ~/doubiju_train/data/kinako/
# mkdir ~/doubiju_train/data/aoi/
# mkdir ~/doubiju_train/data/shion/
# mkdir ~/doubiju_train/data/suigyoku/

これらのディレクトリの中に入ったファイルには自動的にそのディレクトリの名前がラベルとして与えられます。
なので、得られた画像のなかから桃音ちゃんだと思う画像は、data/momone/に格納すれば良いのです。
##効率化
ぶっちゃけ苦行です。
どうびじゅだから死ぬほど楽しいだけであって、これが無機質な文字だったり、だんごむしとわらじむしとかだったら発狂しかねません。しかも結構非効率です。
画像を開いて目視で確認して移動先を指定して移動を繰り替えしていたら苦行以外の何でもありません。さらに僕には画像の切り取りの作業もあります。
そこで、画像を開いて切り抜き・分類・保存をするツールを作成しました。
ディレクトリ単位で画像を読み込み、切り抜き・分類・保存をするツール - Qiita
リンク先にもプログラムはありますが一応。
動作環境はリンク先を参照して下さい。

import cv2
import sys
import os
import tkinter as tk
from tkinter import *
from PIL import Image,ImageTk

image_dir=os.path.expanduser('~/Pictures/data_src/') 
save_dir=os.path.expanduser('~/doubiju_train/data/') 

characters=["momone","kinako","aoi","shion","suigyoku"]

name_list=os.listdir(image_dir)
name_list.sort()

class Window(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master

        self.index=0
        self.local_num=0
        self.max_width=1400
        self.max_height=800
        self.count=1
        self.start_x=0
        self.start_y=0
        self.end_x=0
        self.end_y=0

        self.canvas = Canvas(master, width=1400, height=800)
        self.canvas.grid(row=0, column=0, columnspan=2, rowspan=4)
        self.load_img() 
        self.create_widgets()
        self.grid()

        self.canvas.bind("<Button-1>", self.clicked)
        self.canvas.bind("<ButtonRelease-1>", self.release)
        self.canvas.bind("<B1-Motion>", self.move)

    def scale_to_height(self,img):
        img_width,img_height=img.size
        if img_width>self.max_width and img_height>self.max_height:
            if img_width>img_height and img_height/img_width<self.max_height/self.max_width:
                rate=img_height/img_width
                img=img.resize((self.max_width,int(self.max_width*rate)))
            else:
                rate=img_width/img_height
                img=img.resize((int(self.max_height*rate),self.max_height))
        else:
            scale = self.max_height / img_height
            img=img.resize((int(img_width*scale),int(img_height*scale)))
        return img


    def load_img(self):
        path=image_dir+name_list[self.index]

        img=Image.open(path)

        self.resize_image = self.scale_to_height(img)

        self.img=ImageTk.PhotoImage(self.resize_image)
        self.image_on_canvas = self.canvas.create_image(0, 0, anchor=NW, image=self.img)

    def create_widgets(self):
        self.button_back_skip=Button(self)
        self.button_back_skip["text"]="<<"
        self.button_back_skip["command"]=self.back_skip
        self.button_back_skip.grid(row=1, column=0)

        self.button_back=Button(self)
        self.button_back["text"]="back"
        self.button_back["command"]=self.back_image
        self.button_back.grid(row=1, column=1)

        self.button_next=Button(self)
        self.button_next["text"]="next"
        self.button_next["command"]=self.next_image
        self.button_next.grid(row=1, column=2)

        self.button_next_skip=Button(self)
        self.button_next_skip["text"]=">>"
        self.button_next_skip["command"]=self.next_skip
        self.button_next_skip.grid(row=1, column=3)


        self.button_save=Button(self)
        self.button_save["text"]="rotate"
        self.button_save["command"]=self.rotate_image
        self.button_save.grid(row=2, column=0)

        for col,character in enumerate(characters):
            self.button_save=Button(self)
            self.button_save["text"]=character
            self.button_save["command"]= lambda: self.save_image(character)
            self.button_save.grid(row=2, column=col+1)

        self.button_save=Button(self)
        self.button_save["text"]="save"
        self.button_save["command"]=self.save_image_default
        self.button_save.grid(row=2, column=len(characters)+1)

        self.quit = tk.Button(self, text="QUIT", fg="red",
        command=self.master.destroy)
        self.quit.grid(row=2, column=len(characters)+2)


#------- button press --------------------------------------------------------------
    def next_image(self):
        self.canvas.delete("tmp")
        self.canvas.delete("rect")
        self.index+=1
        if self.index==len(name_list):
            self.index=0
        self.load_img()

    def back_image(self):
        self.canvas.delete("tmp")
        self.canvas.delete("rect")
        self.index-=1
        if self.index==-1:
            self.index=len(name_list)-1
        self.load_img()

    def next_skip(self):
        self.canvas.delete("tmp")
        self.canvas.delete("rect")
        self.index=(self.index+10)%len(name_list)
        self.load_img()

    def back_skip(self):
        self.canvas.delete("tmp")
        self.canvas.delete("rect")
        self.index=(len(name_list)+self.index-10)%len(name_list)
        self.load_img()

    def save_image(self,chara):
        cropped_img=self.resize_image.crop((self.start_x,self.start_y,self.end_x,self.end_y))
        cropped_img.save(save_dir+chara+"/"+name_list[self.index],quality=95)

    def save_image_default(self):
        cropped_img=self.resize_image.crop((self.start_x,self.start_y,self.end_x,self.end_y))
        cropped_img.save(save_dir+str(self.local_num)+name_list[self.index],quality=95)
        self.local_num+=1

    def rotate_image(self):
        rotated_image=self.resize_image.rotate(90*self.count,expand=True)
        self.img=ImageTk.PhotoImage(rotated_image)
        self.image_on_canvas = self.canvas.create_image(0, 0, anchor=NW, image=self.img)
        self.count+=1

#------- key press --------------------------------------------------------------
    def clicked(self,event):
        self.canvas.delete("rect")
        self.start_x=event.x
        self.start_y=event.y

    def move(self,event):
        self.canvas.delete("tmp")
        self.canvas.create_rectangle(self.start_x,self.start_y,event.x,event.y,outline="blue",width=2,tag="tmp")

    def release(self,event):
        self.canvas.create_rectangle(self.start_x,self.start_y,event.x,event.y,outline="blue",width=2,tag="rect")
        self.end_x=event.x
        self.end_y=event.y


root = tk.Tk()
root.title("cropper")
win=Window(master=root)
win.mainloop()

試しにここで配布されているアイコンを動かしてみるとこんな感じ。
Screenshot from 2019-04-07 15-37-13.png

これで楽にデータを用意することができました。
##データの水増し
おそらくすべてのイラストの画像を入手してもデータは足りません。
そこで、画像を反転したりノイズを追加したりしてデータを水増しします。
データ量が増えるので学習結果の向上が期待できますが、逆に訓練データになれすぎて未知のデータに対応できなくなる過学習を引き起こす場合もあります。
ここを参考にデータを水増ししていきたいと思います。

import cv2
import numpy as np
import sys
import os 
import re

def addGaussianNoise(src):
    row,col,ch= src.shape
    mean = 0
    var = 0.1
    sigma = 15
    gauss = np.random.normal(mean,sigma,(row,col,ch))
    gauss = gauss.reshape(row,col,ch)
    noisy = src + gauss
    
    return noisy

def addSaltPepperNoise(src):
    row,col,ch = src.shape
    s_vs_p = 0.5
    amount = 0.004
    out = src.copy()
    
    num_salt = np.ceil(amount * src.size * s_vs_p)
    coords = [np.random.randint(0, i-1 , int(num_salt))
                 for i in src.shape]
    out[coords[:-1]] = (255,255,255)

    num_pepper = np.ceil(amount* src.size * (1. - s_vs_p))
    coords = [np.random.randint(0, i-1 , int(num_pepper))
             for i in src.shape]
    out[coords[:-1]] = (0,0,0)
    return out

if __name__ == '__main__':
    min_table = 50
    max_table = 205
    diff_table = max_table - min_table
    gamma1 = 0.75
    gamma2 = 1.5
    
    average_square = (10,10)
    
    character=["momone/","kinako/","aoi/","shion/","suigyoku/"]

    for chara in character:
        path=os.path.expanduser('~/doubiju_data/data/'+chara)
        output=os.path.expanduser('~/doubiju_data/data/'+chara)

        name_list=os.listdir(path)

        for name in name_list:
            img_src = cv2.imread(path+name)
            trans_img = []
            
            trans_img.append(cv2.blur(img_src, average_square))      

            trans_img.append(addGaussianNoise(img_src))
            trans_img.append(addSaltPepperNoise(img_src))

            flip_img = []
            for img in trans_img:
                flip_img.append(cv2.flip(img, 1))
                flip_img.append(cv2.flip(img, 0))
                flip_img.append(cv2.flip(img, -1))
            trans_img.extend(flip_img)
            
            img_src.astype(np.float64)
            name=re.sub(r'\.jpg',r'',name)

            for i, img in enumerate(trans_img):
                cv2.imwrite(output + name + "_" + str(i+1) + ".jpg" ,img) 

これで1枚の画像から16枚の画像が生成され、データ数が16倍になります。
ここら辺の兼ね合いはいろいろ試してみるといいと思います。
僕はこの処理の後にグレースケール化してみたり、ガウシアンノイズやコントラスト処理を加えてみたりしました。
#実際に動かしてみる
さぁ、ようやっと実行です。
プログラムを準備します。

# cd ~/doubiju_train/
# curl -LO https://github.com/tensorflow/hub/raw/master/examples/image_retraining/retrain.py
# curl -LO https://github.com/tensorflow/tensorflow/raw/master/tensorflow/examples/label_image/label_image.py

--image_dirには訓練データの入ったディレクトリのpathを指定しましょう。

# python ~/doubiju_train/retrain.py --image_dir ~/doubiju_train/data

データ量にもよりますが結構時間がかかります。
学習が終わったら、出力されたデータを元に画像と各ラベルの類似度を出力させます。
--imageには、判別させたい画像のpathを指定します。

# python ~/doubiju_train/label_image.py \
--graph=/tmp/output_graph.pb --labels=/tmp/output_labels.txt \
--input_layer=Placeholder \
--output_layer=final_result \
--image=~/Pictures/data_src/momone.jpg

/tmp/output_graph.pbと/tmp/output_labels.txtは今後label_image.pyで判別するときに必要になるのでバックアップをとっておきましょう。

# cp /tmp/output_graph.pb ~/doubiju_train/output_graph.pb
# cp /tmp/output_labels.txt ~/doubiju_train/output_labels.txt

いちいちコマンドをうつのは面倒なので、シェルスクリプトにしちゃいましょう。

jadge.sh
#!/bin/sh

image="$HOME/Pictures/data_src/20190403_234740119.jpg"

python3 ~/doubiju_train/label_image.py\
			--graph=output_graph.pb\
			--labels=output_labels.txt\
			--input_layer=Placeholder\
			--output_layer=final_result\
			--image=$image
# ./jadge.sh
2019-04-08 22:53:17.506321: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2019-04-08 22:53:17.530495: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 2395585000 Hz
2019-04-08 22:53:17.530958: I tensorflow/compiler/xla/service/service.cc:150] XLA service 0x2147380 executing computations on platform Host. Devices:
2019-04-08 22:53:17.531005: I tensorflow/compiler/xla/service/service.cc:158]   StreamExecutor device (0): <undefined>, <undefined>
shion 0.95736223
momone 0.016319819
suigyoku 0.010770504
aoi 0.01067509
kinako 0.004872421

動きました。成功です。
与えた画像に対してどのラベルが近いかを数値で表しています。
##結果を見やすくする
一応成功しましたが、問題も残ります。
まず、与えた画像が何なのか確認するのが非常に面倒です。いちいちファイル名で検索して中身を確認するしかありません。
それから、値を文字で出されても一目でピンときません。グラフで表したら見やすそうです。
せっかくディレクトリ毎に画像を表示するプログラムができているので、表示するツールも作ってみました。
こんな感じです。

import os
import cv2 
import sys 
import tkinter as tk 
import subprocess as sp
import matplotlib.pyplot as plt
from tkinter import *
from PIL import Image,ImageTk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg

image_dir=os.path.expanduser('~/Pictures/data_src/') 
image_dir=os.path.expanduser('~/python/Machine_learning/crop_output/') 

name_list=[f for f in os.listdir(image_dir) if os.path.isfile(image_dir+f)]
name_list.sort()

chara_colors={"momone":"#f781bf","kinako":"#ffff33","aoi":"#377eb8","shion":"#984ea3","suigyoku":"#4daf4a"}

class Window(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
    
        self.index=0
        self.max_width=1400
        self.max_height=800
        self.count=1
        
        self.canvas = Canvas(master, width=1200, height=800)
        self.canvas.grid(row=0, column=0, columnspan=2, rowspan=4)
        
        self.load_image() 
        self.create_widgets()
        self.grid()
        
        self.canvas.bind("<Button-1>", self.clicked)
        self.canvas.bind("<ButtonRelease-1>", self.release)
        self.canvas.bind("<B1-Motion>", self.move)

        text_widget = Text(master)
        text_widget.place(x=1200,y=0,width=300,height=400)
        text_widget.place()
        
        self.text_widget=text_widget
        

        self.f = Figure(figsize=(5,4), dpi=100)
        
        self.plt = self.f.add_subplot(111)
        self.f.subplots_adjust(left=0.15)
        self.dataPlot = FigureCanvasTkAgg(self.f, master=master)
        self.dataPlot.draw()
        self.dataPlot.get_tk_widget().place(x=950,y=400,width=490,height=450)
    
    def resize_image(self,img):
        img_width,img_height=img.size
        if img_width>self.max_width and img_height>self.max_height:
            if img_width>img_height and img_height/img_width<self.max_height/self.max_width:
                rate=img_height/img_width
                img=img.resize((self.max_width,int(self.max_width*rate)))
            else:
                rate=img_width/img_height
                img=img.resize((int(self.max_height*rate),self.max_height))
        else:
            scale = self.max_height / img_height
            img=img.resize((int(img_width*scale),int(img_height*scale)))
        return img
    
    def load_image(self):
        path=image_dir+name_list[self.index]
        
        img=Image.open(path)
        
        self.resized_img = self.resize_image(img)
        
        self.img=ImageTk.PhotoImage(self.resized_img)
        self.image_on_canvas = self.canvas.create_image(0, 0, anchor=NW, image=self.img)

    def create_widgets(self):
        self.button_back_skip=Button(self)
        self.button_back_skip["text"]="<<"
        self.button_back_skip["command"]=self.back_skip
        self.button_back_skip.grid(row=1, column=0)
        
        self.button_back=Button(self)
        self.button_back["text"]="back"
        self.button_back["command"]=self.back_image
        self.button_back.grid(row=1, column=1)
        
        self.button_next=Button(self)
        self.button_next["text"]="next"
        self.button_next["command"]=self.next_image
        self.button_next.grid(row=1, column=2)
        
        self.button_next_skip=Button(self)
        self.button_next_skip["text"]=">>"
        self.button_next_skip["command"]=self.next_skip
        self.button_next_skip.grid(row=1, column=3)
        

        self.button_save=Button(self)
        self.button_save["text"]="rotate"
        self.button_save["command"]=self.rotate_image
        self.button_save.grid(row=2, column=0)

        self.button_save=Button(self)
        self.button_save["text"]="select"
        self.button_save["command"]=self.jadge_image
        self.button_save.grid(row=2, column=1)

        self.button_save=Button(self)
        self.button_save["text"]="jadge"
        self.button_save["command"]=self.jadge_image
        self.button_save.grid(row=2, column=2)

        self.quit = tk.Button(self, text="QUIT", fg="red",
        command=self.master.destroy)
        self.quit.grid(row=2, column=3)

#------- button press --------------------------------------------------------------
    def next_image(self):
        self.canvas.delete("tmp")
        self.canvas.delete("rect")
        self.index+=1
        if self.index==len(name_list):
            self.index=0
        self.load_image()

    def back_image(self):
        self.canvas.delete("tmp")
        self.canvas.delete("rect")
        self.index-=1
        if self.index==-1:
            self.index=len(name_list)-1
        self.load_image()

    def next_skip(self):
        self.canvas.delete("tmp")
        self.canvas.delete("rect")
        self.index=(self.index+10)%len(name_list)
        self.load_image()

    def back_skip(self):
        self.canvas.delete("tmp")
        self.canvas.delete("rect")
        self.index=(len(name_list)+self.index-10)%len(name_list)
        self.load_image()
    
    def save_image(self,chara):
        cropped_img=self.resized_img.crop((self.start_x,self.start_y,self.end_x,self.end_y))
        cropped_img.save(save_dir+chara+name_list[self.index],quality=95)

    def rotate_image(self):
        rotated_image=self.resized_img.rotate(90*self.count,expand=True)
        rotated_image=self.resize_image(rotated_image)
        self.img=ImageTk.PhotoImage(rotated_image)
        self.image_on_canvas = self.canvas.create_image(0, 0, anchor=NW, image=self.img)
        self.count+=1
    
    def jadge_image(self):
        cmd="python3 $HOME/doubiju_train/label_image.py "\
                                "--graph=$HOME/doubiju_train/output_graph.pb "\
                                "--labels=$HOME/doubiju_train/output_labels.txt "\
                                "--input_layer=Placeholder "\
                                "--output_layer=final_result "\
                                "--image=$HOME/Picture/data_src/"+name_list[self.index]
        
        chara_list=[]
        value_list=[]
        color_list=[]
        proc=sp.Popen(cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE)
        std_out, std_err = proc.communicate()

        result_list = std_out.decode('utf-8').rstrip().split('\n')
        
        self.text_widget.delete('1.0','end')
        self.text_widget.insert('end',"----"+name_list[self.index]+"----\n")
        for result in result_list:
            name,value=result.split(" ")
            chara_list.append(name)
            value_list.append(float(value))
            color_list.append(chara_colors[name])
            self.text_widget.insert('end',name+"\t\t"+value+"\n")
        
        value_list.reverse()
        chara_list.reverse()
        color_list.reverse()

        self.plt.clear()
        self.plt.set_xticks([i/10 for i in range(0,11)])
        self.plt.set_xticklabels([i/10 for i in range(0,11)])
        self.plt.barh(list(range(5)),value_list,tick_label=chara_list,color=color_list)
        
        self.dataPlot.draw()

#------- key press --------------------------------------------------------------
    def clicked(self,event):
        self.canvas.delete("rect")
        self.start_x=event.x
        self.start_y=event.y

    def move(self,event):
        self.canvas.delete("tmp")
        self.canvas.create_rectangle(self.start_x,self.start_y,event.x,event.y,outline="blue",width=2,tag="tmp")

    def release(self,event):
        self.canvas.create_rectangle(self.start_x,self.start_y,event.x,event.y,outline="blue",width=2,tag="rect")
        self.end_x=event.x
        self.end_y=event.y

root = tk.Tk()
root.title("cropper")
win=Window(master=root)
win.mainloop()

実行するとこんな感じです。
Screenshot from 2019-04-14 23-33-38.png
Screenshot from 2019-04-14 23-34-25.png

か゛わ゛い゛い゛な゛ぁ゛桃゛音゛ち゛ゃ゛ん゛
コマンドを実行し、返された値を取得して数値とグラフを表示しています。いずれも90%を越えていていい感じです。
それぞれのグラフの色は各キャラクターの名前に含まれている色をそのまま表示しています。

name color
#f781bf
奈子 #ffff33
#377eb8
#984ea3
#4daf4a
グラフをウィジェットとして埋め込むのが大変だった。(満身創痍)
#最後に
サンプルでもデータ側を工夫すれば、結構良い結果が得られるんだなと思いました。
機械学習を学ぶという建前でどうびじゅの画像をprprして心の隙間を埋めようとするだけの計画でしたが、なかなか有意義な学習だった気がします。また、困ったときに有効なツールを作って対処できたのは自分のなかで大きな成長だと思います。
これからは、機械学習の基本を学ぶとともに自分でコードを書いて更なる精度向上をしていきたいと思います。また、画像認識はなにも機械学習を使わなくても沢山方法はあるので、機械学習に縛られずに特徴量分析など別の方法でのアプローチもしていきたいと思います。
どうして私が美術科に!?3巻は4月25日発売なので興味のある方やきららMAX読者諸君は買われてみてはいかかがでしょうか。買え。
#参考 [TensorFlowで画像認識「〇〇判別機」を作る - Qiita](https://qiita.com/too-ai/items/4fad0239b8b3c465fe6d) [How to Retrain an Image Classifier for New Categories | TensorFlow Hub | TensorFlow](https://www.tensorflow.org/hub/tutorials/image_retraining) [Python + Tkinterで連番画像ファイルを素早く切り抜くGUI画像トリミングツール - Qiita](https://qiita.com/lp6m/items/e6be545eb05a50b574bd) [Python + Tkinter で作る、GUIな画像トリミングツール - Qiita](https://qiita.com/MasahikoYasui/items/4bfdfab0ba27c7ca0620) 相崎うたう(2017)『どうして私が美術科に!?』 芳文社
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?