🌿

紫陽花の分類(Selenium+BeautifulSoup4+Keras)

2021/06/29に公開

アジサイを見に公園に行ってきました。
いろんなアジサイがあって綺麗だなと思ったので、分類してみます。

紫陽花(アジサイ)とは

広義のアジサイは、アジサイ科アジサイ属の総称です。
花のように見えるのはがくで、真ん中の点々は雌蕊雄蕊です。
https://ja.wikipedia.org/wiki/アジサイ

もっこり咲いているのがホンアジサイ

線香花火みたいに咲いているのがガクアジサイ

ガクアジサイに似ていて葉に光沢がないのがヤマアジサイ(違ったらごめんなさい)

白くてがくが小さいのはアナベル(最近はピンクもある)

紫陽花(アジサイ)を2種類に分類

ヤマアジサイはガクアジサイに似ているので精度は期待できません。葉にフォーカスして写真を撮っている人が少ないですし、これは諦めます。
アナベルもホンアジサイ系の品種改良なので諦めます。

  • ホンアジサイ
  • ガクアジサイ

これだけに絞ります。

学習データを用意

たくさん写真を撮ってきたのですが、分類するつもりで撮ってないので全然足りませんでした。なので学習データを作るために画像をスクレイピングします。

Selenium+BeautifulSoup4でGoogleの画像検索

Chromeのバージョンに合わせてChromeDriverをインストールします。

! pip install chromedriver-binary==91.0.4472.101
dl_hydrangea.ipynb
import shutil
import bs4
import chromedriver_binary
from selenium import webdriver
import re

# 保存するURLの取得
def image(data,maximum = 30):
    # Google画像検索のURL取得
    url = "https://www.google.com/search?hl=jp&q=" + data + "&sclient=img&btnG=Google+Search&tbs=0&safe=off&tbm=isch"

    driver = webdriver.Chrome()
    driver.get(url)
    
    total = 0
    result = []
    while True:
        html = driver.page_source.encode('utf-8')
        soup = bs4.BeautifulSoup(html,'html.parser')   # 整形
        links = soup.find_all("img",attrs={"src":re.compile("https://*")})   # img elementの取得
        print(len(links))
        result +=links
        total += len(links)
        if total > maximum:
            break
        else:
            True

    return result
            
# 該当するURLからdownload
def download(url,file_name):
    req = requests.get(url, stream=True)
    if req.status_code == 200:
        with open(file_name + ".png", 'wb') as f:   # pngをbinでfileに書き出し
            req.raw.decode_content = True
            shutil.copyfileobj(req.raw, f)   # fileにpng画像データをコピー

# 検索するキーワード
name = 'ホンアジサイ'
# 保存先
outputname='img/hydrangea_hon'
links = image(name,500)
print(len(links))
for i in range(1,500):
    link = links[i].get("src")
    download(link,outputname + str(i))

スクロールする部分をうまく自動化できなかったのですが、起動したChromeDriverをスクロールしていくとそれを指定した最大値まで取得します。(実際にはちょっと多めに取得し、download時に絞られています)

BeautifulSoupを使うにあたって、どの要素を抜き出したいかは自分で考える必要があります。前に書いた記事が役に立ちました。
https://zenn.dev/midori/articles/c4dcdf556e04c62adc63

ゴミデータを捨てる

ガクアジサイの中に紛れ込んでいるホンアジサイ、ホンアジサイの中に紛れ込んでいるガクアジサイを捨てます。目視で!

いざ学習

データセットを作る

取得したデータでデータセットを作ります。
訓練データとテストデータに分けるところまでやります。

import keras
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.layers.core import Dense, Dropout, Activation, Flatten
import numpy as np
from sklearn.model_selection import train_test_split
from PIL import Image
import glob
import matplotlib.pyplot as plt
import os
import cv2
import random
import numpy as np
# pickleで保存するのでインポートする
import pickle

DATADIR = "C:\\Users\\XXX\img"
CATEGORIES = ["gaku", "hon"]

IMG_SIZE = 50
training_data = []

def create_training_data():
    for class_num, category in enumerate(CATEGORIES):
        path = os.path.join(DATADIR, category)
        for image_name in os.listdir(path):
            try:
                img_array = cv2.imread(os.path.join(path, image_name),)  # 画像読み込み
                img_resize_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE))  # 画像のリサイズ
                training_data.append([img_resize_array, class_num])  # 画像データ、ラベル情報を追加
            except Exception as e:
                pass

create_training_data()

random.shuffle(training_data)  # データをシャッフル

X = []  # 画像データ
y = []  # ラベル情報

# データセット作成
for feature, label in training_data:
    X.append(cv2.cvtColor(feature, cv2.COLOR_BGR2RGB))
    y.append(label)

# numpy配列に変換
X = np.array(X)
y = np.array(y)

X = X.astype('float32')
X = X / 255.0

# 正解ラベルの形式を変換
y = np_utils.to_categorical(y, 2)

# 学習用データとテストデータ
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20)

学習モデルを作る

Kerasで作ります。
Poolingが良く分かっていないので、CNNにはなっていないです。
input_shapeが作ったデータセットのShapeと一致している必要があります。
最後はSoftmaxにします。

model = Sequential()

model.add(Conv2D(32, (1, 1), padding='same',input_shape=(50,50,3)))
model.add(Activation('relu'))
model.add(Conv2D(32, (1, 1)))
model.add(Activation('relu'))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
#model.add(Dense(4))
#model.add(Activation('relu'))
model.add(Dense(2))
model.add(Activation('softmax'))

# コンパイル
model.compile(loss='categorical_crossentropy',optimizer='SGD',metrics=['accuracy'])

訓練

#訓練
history = model.fit(X_train, y_train, batch_size=100,epochs=200)

評価

#評価 & 評価結果出力
print(model.evaluate(X_test, y_test))

モデルの保存

# モデルを保存する
model.save("my_model_hydrangea")

使ってみる!!!

画像を読み込んで判定させてみます。
ガクアジサイが0、ホンアジサイが1です。

imgpath ='C:\\Users\\XXX\\Pictures\\test.jpg'

img_array = cv2.imread(imgpath, cv2.IMREAD_GRAYSCALE)  # 画像読み込み
img_resize_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE))  # 画像のリサイズ

img_pred_array = np.array(cv2.cvtColor(img_resize_array,cv2.COLOR_BGR2RGB))

img_pred_array = img_pred_array.astype('float32')
img_pred_array = img_pred_array/ 255.0
img_pred_array = img_pred_array.reshape([-1,50,50,3])

pred = model.predict(img_pred_array)

print('pred:',np.argmax(pred))

ホンアジサイ




1だ!ホンアジサイと予測できている!

ガクアジサイ




0だ!ガクアジサイと予測できている!

感想

本当のことを言うと、上記の例はほぼたまたま上手く行ったようなもので、ほとんどはホンアジサイと予測されてしまいます。
アジサイの中でどっちなのかって結構難しい分類なのかもしれないですね。目で見ても判別に迷うものもありますし。
改善点はたくさんあるのですが、Kerasの勉強になりました。

※掲載している写真は自分で撮ったものなので無断転載しないでください。

Discussion