1
2

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 3 years have passed since last update.

ImageDownloaderで画像を収集して、サンリオキャラの画像診断してみた

Last updated at Posted at 2021-08-14

サンリオキャラクターを画像認識するAIを作りました。

ImageDownloader

Chromeの拡張機能でWebサイトの画像をダウンロードできる
(公式)からchromeに追加をクリックして入手

分類するキャラクター

無料の画像サイトから拝借しました。

バツ丸

84741762_202x291.jpeg

シナモロール

4.jpg

ケロッピー

878856781.jpg

ハローキティ

84741725_220x146.jpeg

リトルツインスターズ

5467289.png

マイメロディ

68789739.jpg

ポムポムプリン

987895824.jpg

タキシードサム

95867738.jpg

画像水増し

ダウンロードした画像だけでは少ないので、水増しする
kerasを使っても、cv2を使ってもどちらでも可

kerasによる画像水増し
import keras
import numpy as np
import os
from keras.utils import np_utils
from keras.preprocessing import image
from keras.preprocessing.image import ImageDataGenerator


path = "フォルダパス"
files = os.listdir(path)
count = 0
file_count = len(files) + 1
print(len(files))
for file in files:
    img = image.load_img(path + "/" + file)
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    datagen = ImageDataGenerator(
        rotation_range = 180,
        width_shift_range= 0.2,
        height_shift_range = 0.3,
        zoom_range = [0.8, 1.2],
        channel_shift_range = 30
        )   
    
    for d in datagen.flow(x, batch_size=1):
        new_img = image.array_to_img(d[0], scale=True)
        image.save_img(path + "/" + str(file_count) + ".jpg", new_img)
        file_count += 1
        count += 1
        if count == 8:
            count = 0
            break

print("finish")
cv2による水増し
import os
import numpy as np
import cv2

file_path = "フォルダパス"

def save_img(img, n, path=file_path):
    path = os.getcwd()
    cv2.imwrite(path + "/" + str(n) + ".jpg", img)


def scratch_image(img, warp=True, flip_y=True, flip_x=True, gray=True, thr=True, filt=True, resize=True, erode=True):
    methods = [warp, flip_y, flip_x, gray, thr, filt, resize, erode]
    img_size = img.shape
    filter1 = np.ones(3,3)
    mat = cv2.getRotationMatrix2D(tuple(np.array([img.shape[1], img.shape[0]] / 2), 90, 1.0))
    scratch = np.array([
        lambda x: cv2.warpAffine(x, mat, img.shpae[::-1][1:3]),
        lambda x: cv2.flip(x, 1),
        lambda x: cv2.flip(x, 0),
        lambda x: cv2.cvtColor(x, cv2.COLOR_BGR2GRAY),
        lambda x: cv2.threshold(x, 120, 255, cv2.THRESH_BINARY)[1],
        lambda x: cv2.GaussianBlur(x, (75, 75), 0),
        lambda x: cv2.resize(cv2.resize(x, (img_size[1]//5, img_size[0]//5))),
        lambda x: cv2.erode(x, filter1)
    ])
    doubling_images = lambda f, imag:(imag + [f(i) for i in imag])
    for func in scratch[methods]:
        images = doubling_images(func, images)
    return images


def scratch_image2(img):   # OpenCVによるデータの水増し(lambda)
    filter1 = np.array([[0, 1, 0], 
                        [1, 0, 1],
                        [0, 1, 0]], np.uint8)
    filter2 = np.ones((3, 3))
    list_resize = [2, 3, 5, 7]
    list_mosaic = [3, 5, 7, 10]
    list_rotation = [45, 135, 225, 315]
    list_flip = [0, 1, -1]
    list_cvt1 = [0]
    list_cvt2 = [0]
    list_THRESH_BINARY = [50, 100, 150, 200]
    list_THRESH_BINARY_INV = [50, 100, 150, 200]
    list_THRESH_TRUNC = [50, 100, 150, 200]
    list_THRESH_TOZERO = [50, 100, 150, 200]
    list_THRESH_TOZERO_INV = [50, 100, 150, 200]
    list_gauss = [11, 31, 51, 71]
    list_gray = [0]
    list_nois_gray = [0]
    list_nois_color = [0]
    list_dilate = [filter1, filter2]
    list_erode = [filter1, filter2]

    parameters = [list_resize, list_mosaic, list_rotation, list_flip, list_cvt1, list_cvt2, list_THRESH_BINARY, \
              list_THRESH_BINARY_INV, list_THRESH_TRUNC, list_THRESH_TOZERO, list_THRESH_TOZERO_INV, list_gauss, \
              list_gray, list_nois_gray, list_nois_color, list_dilate, list_erode]

    methods = np.array([
        lambda i: cv2.resize(img, (img.shape[1] // i, img.shape[0] // i)),
        lambda i: cv2.resize(cv2.resize(img, (img.shape[1] // i, img.shape[0] // i)), (img.shape[1],img.shape[0])),
        lambda i: cv2.warpAffine(img, cv2.getRotationMatrix2D(tuple(np.array([img.shape[1] / 2, img.shape[0] /2])), i, 1), (img.shape[1], img.shape[0])),
        lambda i: cv2.flip(img, i),
        lambda i: cv2.cvtColor(img, cv2.COLOR_BGR2LAB),
        lambda i: cv2.bitwise_not(img),
        lambda i: cv2.threshold(img, i, 255, cv2.THRESH_BINARY)[1],
        lambda i: cv2.threshold(img, i, 255, cv2.THRESH_BINARY_INV)[1],
        lambda i: cv2.threshold(img, i, 255, cv2.THRESH_TRUNC)[1],
        lambda i: cv2.threshold(img, i, 255, cv2.THRESH_TOZERO)[1],
        lambda i: cv2.threshold(img, i, 255, cv2.THRESH_TOZERO_INV)[1],  
        lambda i: cv2.GaussianBlur(img, (i, i), 0),
        lambda i: cv2.imread(img, i),
        lambda i: cv2.fastNlMeansDenoising(cv2.imread(img, i)),
        lambda i: cv2.fastNlMeansDenoisingColored(img),
        lambda i: cv2.dilate(img, i),
        lambda i: cv2.erode(img, i)
        ])
    num = 0
    for ind, method in enumerate(methods):
        for parameter in parameters[ind]:
            num += 1
            cnv_img = method(parameter)
            save_img(cnv_img, num)


def resize_image(image, n, image_size=(256,256)):
    path = os.getcwd()
    img = cv2.imread(image)
    img = cv2.resize(img, image_size)
    cv2.imwrite(path + "/" + str(n) + ".jpg" )


files = os.listdir(file_path)
count = len(files) + 1
for file in files:
    image = cv2.imread(file_path +"/" + file)
    scratch_images = scratch_image(img)
    for im in scratch_images:
        cv2.imwrite(file_path + "/" + str(count) + "/" + ",jpg", im)

画像サイズ調整

画像を読み込むときにもサイズ調整するので、ここではしてもしなくてもよい。

画像サイズ調整
from PIL import Image
import sys
import os
import re
import cv2

path = "サイズ変更前フォルダパス"
resize_path = "サイズ変更後フォルダパス"
files = os.listdir(path)
for file in files:
    img = cv2.imread(path + "/" + file)
    img = cv2.resize(img, (150, 150))
    cv2.imwrite(resize_path + "/" + file, img)

print("finish")

kerasで学習させる

# 必要なライブラリのインポート
import os
import numpy as np
import matplotlib.pyplot as plt
import cv2
from keras.utils.np_utils import to_categorical
from keras.layers import Dense, Dropout, Flatten, Input
from keras.applications.vgg16 import VGG16
from keras.models import Model, Sequential
from keras import optimizers

画像ファイルの読み込み

# google colabを利用
# ディレクトリパスを変数に入れる
badtzmaru = "/content/drive/MyDrive/Colab Notebooks/バツ丸画像の保存ディレクトリ/"
# os.listdirでディレクトリ内のファイルを一覧で取得(ループ処理するため)
path_badtzmaru = os.listdir(badtzmaru)
# 以下同
cinnamon = "/content/drive/MyDrive/Colab Notebooks/シナモロール画像ディレクトリ/"
path_cinnamon =  os.listdir(cinnamon)
keroppi = "/content/drive/MyDrive/Colab Notebooks/けろっぴ画像ディレクトリ/"
path_keroppi = os.listdir(keroppi)
kitty = "/content/drive/MyDrive/Colab Notebooks/キティ画像ディレクトリ/"
path_kitty = os.listdir(kitty)
LTS = "/content/drive/MyDrive/Colab Notebooks/キキララ画像ディレクトリ/"
path_LTS = os.listdir(LTS)
mymelody = "/content/drive/MyDrive/Colab Notebooks/マイメロ画像ディレクトリ/"
path_mymelody = os.listdir(mymelody)
pompom = "/content/drive/MyDrive/Colab Notebooks/ポムポムプリン画像ディレクトリ/"
path_pompom = os.listdir(pompom)
sam = "/content/drive/MyDrive/Colab Notebooks/タキシードサム画像ディレクトリ/"
path_sam = os.listdir(sam)

# 各画像データを格納するリスト
img_badtzmaru = []
img_cinnamon = []
img_keroppi = []
img_kitty = []
img_LTS = []
img_mymelody = []
img_pompom = []
img_sam = []

# ループ処理で画像データをリストに格納していく
for i in range(len(path_badtzmaru)):
  # 画像をパスで読み込み
  img = cv2.imread(badtzmaru + path_badtzmaru[i])
  # 画像サイズを変更
  img = cv2.resize(img, (150,150))
  # リストに追加
  img_badtzmaru.append(img)

for i in range(len(path_cinnamon)):
  img = cv2.imread(cinnamon + path_cinnamon[i])
  img = cv2.resize(img, (150,150))
  img_cinnamon.append(img)

for i in range(len(path_keroppi)):
  img = cv2.imread(keroppi + path_keroppi[i])
  img = cv2.resize(img, (150,150))
  img_keroppi.append(img)

for i in range(len(path_kitty)):
  img = cv2.imread(kitty + path_kitty[i])
  img = cv2.resize(img, (150,150))
  img_kitty.append(img)

for i in range(len(path_LTS)):
  img = cv2.imread(LTS + path_LTS[i])
  img = cv2.resize(img, (150,150))
  img_LTS.append(img)

for i in range(len(path_mymelody)):
  img = cv2.imread(mymelody + path_mymelody[i])
  img = cv2.resize(img, (150,150))
  img_mymelody.append(img)

for i in range(len(path_pompom)):
  img = cv2.imread(pompom + path_pompom[i])
  img = cv2.resize(img, (150,150))
  img_pompom.append(img)

for i in range(len(path_sam)):
  img = cv2.imread(sam + path_sam[i])
  img = cv2.resize(img, (150,150))
  img_sam.append(img)

訓練データとテストデータを作成

# 各画像をまとめて配列にする
X = np.array(img_badtzmaru + img_cinnamon + img_keroppi + img_kitty + img_LTS + img_mymelody + img_pompom + img_sam)
# 各画像にラベルを付けて配列にする
# ラベル:0 => バツ丸、 ラベル:1 => シナモロール・・・
y = np.array([0]*len(img_badtzmaru) + [1]*len(img_cinnamon) + [2]*len(img_keroppi) + [3]*len(img_kitty) + [4]*len(img_LTS) + [5]*len(img_mymelody) + [6]*len(img_pompom) + [7]*len(img_sam))

# np.arangeでXと同じ大きさの連番を生成し、
# np.random.permutationでランダム配列にする
rand_index = np.random.permutation(np.arange(len(X)))
# ランダム配列をインデックスとして渡し、Xとyをランダムに並べ替える
X = X[rand_index]
y = y[rand_index]

# 80%を訓練データに、残りをテストデータにする
X_train = X[:int(len(X)*0.8)]
y_train = to_categorical(y[:int(len(y)*0.8)])
X_test = X[int(len(X)*0.8):]
y_test = to_categorical(y[int(len(y)*0.8):])

転移学習

# 画像サイズをインプットデータにする
input_tensor = Input(shape = (150, 150, 3))
vgg16 = VGG16(include_top=False, weights="imagenet", input_tensor=input_tensor)

# モデルを作成
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(512, activation="relu"))
top_model.add(Dropout(rate=0.3))
top_model.add(Dense(256, activation="relu"))
top_model.add(Dropout(rate=0.3))
top_model.add(Dense(128, activation="relu"))
top_model.add(Dropout(rate=0.2))
top_model.add(Dense(64, activation="relu"))
# softmax関数で8個の出力を渡す
top_model.add(Dense(8, activation="softmax"))

# 入力はvgg.input, 出力はtop_modelにvgg16の出力を入れたもの
# VGGの特徴抽出部分のみを使い、以降は自分で作成したモデルと結合
model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))

# modelの16層目までのvgg16の特徴抽出部分は学習させないため、固定する
for layer in model.layers[:15]:
  layer.trainable = False

# コンパイルする
model.compile(loss="categorical_crossentropy",
              optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
              metrics=["accuracy"])

# 訓練する
model.fit(X_train, y_train, batch_size=8, epochs=45)

テストデータの確認

# テストデータで評価する
# verbose:進行状況の表示モード.0=>表示なし,1=>プログレスバー,2=>各試行毎に一行出力
scores = model.evaluate(X_test, y_test, verbose=1)
print("Test loss:", scores[0])
print("Test accuracy:", scores[1])

>> 10/10 [==============================] - 8s 384ms/step - loss: 0.1534 - accuracy: 0.9619
>> Test loss: 0.15337002277374268
>> Test accuracy: 0.961904764175415
# 96.2%の成功率

モデルを使った予測を表示する関数

def pred_sanrio(img):
  img = cv2.resize(img, (150, 150))
  # model.predictでモデルの予測を取得する
  # np.argmaxで予測の最大値のインデックスを取得する
  pred = np.argmax(model.predict(np.array([img])))
  if pred == 0:
    return "Badtz-Maru"
  elif pred == 1:
    return "Cinnamoroll"
  elif pred == 2:
    return "Kero Kero Keroppi"
  elif pred == 3:
    return "Hello Kitty"
  elif pred == 4:
    return "Little Twin Stars"
  elif pred == 5:
    return "My Melody"
  elif pred == 6:
    return "Pompom Purin"
  elif pred == 7:
    return "Tuxedo Sam"

画像が適切に判断されているか確認

同じ処理は関数にまとめてもよかった。

for i in range(3):
  img = cv2.imread(badtzmaru + path_badtzmaru[i])
  # cv2.imreadはBGR、pltはRGBなので変換する
  img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  plt.imshow(img)
  plt.show()
  print(pred_sanrio(img))

for i in range(3):
  img = cv2.imread(cinnamon + path_cinnamon[i])
  img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  plt.imshow(img)
  plt.show()
  print(pred_sanrio(img))

for i in range(3):
  img = cv2.imread(keroppi + path_keroppi[i])
  img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  plt.imshow(img)
  plt.show()
  print(pred_sanrio(img))

for i in range(3):
  img = cv2.imread(kitty + path_kitty[i])
  img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  plt.imshow(img)
  plt.show()
  print(pred_sanrio(img))

for i in range(3):
  img = cv2.imread(LTS + path_LTS[i])
  img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  plt.imshow(img)
  plt.show()
  print(pred_sanrio(img))

for i in range(3):
  img = cv2.imread(mymelody + path_mymelody[i])
  img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  plt.imshow(img)
  plt.show()
  print(pred_sanrio(img))

for i in range(3):
  img = cv2.imread(pompom + path_pompom[i])
  img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  plt.imshow(img)
  plt.show()
  print(pred_sanrio(img))

for i in range(3):
  img = cv2.imread(sam + path_sam[i])
  img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  plt.imshow(img)
  plt.show()
  print(pred_sanrio(img))

結果

image.png
Badtz-Maru
image.png
Cinnamoroll
image.png
Kero Kero Keroppi
image.png
Hello Kitty
image.png
Little Twin Stars
image.png
My Melody
image.png
Pompom Purin
image.png
Tuxedo Sam

正解 誤答
バツ丸 3 0
シナモロール 2 1
ケロッピー 3 0
キティ 3 0
リトルツインスターズ 2 1
マイメロ 3 0
ポムポムプリン 3 0
タキシードサム 3 0
合計 22 2
確率: 83%

考察

テストデータで96%の正解率は、目標の95%を超えた。
課題として、使用した画像枚数に差がある(タキシードサムとキティの差は3倍)、ハイパーパラメータの設定が難しいなど、改善点がみつかりました。
初めての画像診断としてはいい結果かなと思います。
最後の検証は各キャラクター3枚ずつしか使っていないので、枚数を増やせば正解率は上がると思います。

参考資料

画像水増し

OpenCVによるデータの水増し(lambda)
ImageDataGenerator
Kerasによる画像の水増し操作
KerasのImageDataGeneratorで学習用画像を水増しする方法
Kerasによるデータ拡張
NumPyでの画像のData Augmentationまとめ

kerasで学習させる

はじめてのニューラルネットワーク:分類問題の初歩
ModelクラスAPI
【python入門】os.listdirでファイル・ディレクトリの一覧を取得
NumPyのarange, linspaceの使い方(連番や等差数列を生成)
numpy.random.permutation – 配列の要素をランダムに並べ替えた新しい配列を生成
過学習と学習不足について知る
【Deep Learning】 Batch sizeをどうやって決めるかについてまとめる

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?