見出し画像

雲の画像判定(Aidemy成果物)

自己紹介ときっかけ

現在、システム会社に在職していますが、自分自身はプログラミングは行わずヘルプデスクとして長年勤務。
そんな中で自分でもプログラミングを行ってみたいと思い立ち、Aidemy Premium Planの「AIアプリ開発コース6か月」を受講。

受講内容だけなら6ヶ月であれば、十分に達成可能だが…
個人的な事情により途中2ヶ月半ほど何もできず。。
いろいろありましたが、WEBで動作するアプリを作成できたので紹介します。

概要

成果物のテーマについては動物なども考えたが、iPhoneで風景を撮影することも多く、雲の種類は10種類しかないということをネットで見かけたため題材とすることに決定。
今回は形が判別がしやすい4つの分類を選択しました。

 ・線状の雲<巻雲:すじ雲>
 ・粒々の雲<巻積雲:うろこ雲、高積雲:ひつじ雲>
 ・もくもくとした雲<積乱雲:入道雲>
 ・ふわっとした雲<積雲:わた雲>

実行環境

Python3.9.12
Google Colaboratory
Visual Studio Code

画像収集

収集方法

雲の種類毎に画像が必要になるため検索を駆使していたが、Kaggleなどでもデータセットを発見できず。。
見つかるものは衛星画像などばかりだったので、
icrawlerを使用してBing画像からスクレイピングで集めてみました。

# 必要なライブラリのインポート
from ast import keyword
from icrawler.builtin import BingImageCrawler

# 巻雲(けんうん):すじぐも                       → 0_Cirrus
# 巻積雲(けんせきうん):うろこぐも、いわしぐも    → 1_Cirrostratus
# 巻層雲(けんそううん):うすぐも                 → 2_Cirrocumulus
# 高積雲(こうせきうん):ひつじぐも               → 3_Altostratus
# 高層雲(こうそううん):おぼろぐも               → 4_Altocumulus
# 乱層雲(らんそううん:あまぐも                   → 5_Nimbostratus
# 層積雲(そうせきうん):うねぐも                 → 6_Stratocumulus
# 層雲(そううん):きりぐも                       → 7_Cumulus
# 積雲(せきうん):わたぐも                       → 8_Stratus
# 積乱雲(せきらんうん):にゅうどうぐも            → 9_Cumulonimbus

#検索文字の指定
keyword_name = "巻雲 すじ雲"
#フォルダ名の指定
strage_name = "0_Cirrus"

crawler = BingImageCrawler(storage={"root_dir" : strage_name})
crawler.crawl(keyword=keyword_name, max_num=1000)

集めた画像を確認すると、1000枚で指定にも関わらず実際にフォルダに保存されていたのは500枚程度。
ここから、雲の種類が判断できるのかを含めて各フォルダの画像精査が始まる。。
(※10分類×500枚=5000枚)

画像精査

各フォルダの画像を確認していくと、雲のみが撮影されている画像は少なく、基本的に建物や風景が含まれているのが大多数でした。
そもそも人の目で見たときに分類が分かるのかも含めて目視で1枚ずつ確認を進めていきました。

…うーん、、判別がつかない。。

元々、形が似ている巻積雲<うろこ雲>、高積雲<ひつじ雲>など判断が難しいと思っていましたが、その他の分類も同じような画像が多数、含まれていることが発覚。
このまま進めるとデータセットの精度自体が信用できなくなるため、最終的に形が判別できる4つの分類に変更しました。

形が分かりやすくなるようにトリミングを行いつつ精査を行うと最終的に各分類で200枚前後とかなり画像が少なくなりました。

モデルの作成と学習

今回はAidemyの講座で学んだVGG16を使用して転移学習する方法にしました。

# 雲の分類(4分類)

import os
import cv2
import matplotlib.pyplot as plt
import numpy as np
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.layers import Dense, Dropout, Flatten, Input
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras import optimizers
from google.colab import files

# Googleドライブから読み込み
path_line = os.listdir('/content/drive/MyDrive/Colab Notebooks/CloudShape/cloud_dataset/0_Line/')
path_circle = os.listdir('/content/drive/MyDrive/Colab Notebooks/CloudShape/cloud_dataset/1_Circle/')
path_thunder = os.listdir('/content/drive/MyDrive/Colab Notebooks/CloudShape/cloud_dataset/2_Thunder/')
path_cotton = os.listdir('/content/drive/MyDrive/Colab Notebooks/CloudShape/cloud_dataset/3_Cotton/')

# ピクセル数の指定(X,Y共通)
pic = 100

# 画像の配列を作成
img_line = []
img_circle = []
img_thunder = []
img_cotton = []

# 種類ごとにリサイズして配列へ追加
for i in range(len(path_line)):
    img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/CloudShape/cloud_dataset/0_Line/' + path_line[i])
    img = cv2.resize(img, (pic, pic))
    img_line.append(img)

for i in range(len(path_circle)):
    img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/CloudShape/cloud_dataset/1_Circle/' + path_circle[i])
    img = cv2.resize(img, (pic, pic))
    img_circle.append(img)

for i in range(len(path_thunder)):
    img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/CloudShape/cloud_dataset/2_Thunder/' + path_thunder[i])
    img = cv2.resize(img, (pic, pic))
    img_thunder.append(img)

for i in range(len(path_cotton)):
    img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/CloudShape/cloud_dataset/3_Cotton/' + path_cotton[i])
    img = cv2.resize(img, (pic, pic))
    img_cotton.append(img)

# 配列の画像データをNumpy配列へ変換
X = np.array(img_line + img_circle + img_thunder + img_cotton)

# 正解ラベルの紐づけ( 0~3 )
y = np.array([0]*len(img_line) + [1]*len(img_circle) + [2]*len(img_thunder) + [3]*len(img_cotton))

# 画像データの順番をランダムにする
rand_index = np.random.permutation(np.arange(len(X)))
X = X[rand_index]
y = y[rand_index]

# データの分割( 学習8:テスト2 )
X_train = X[:int(len(X)*0.8)]
y_train = y[:int(len(y)*0.8)]
X_test = X[int(len(X)*0.8):]
y_test = y[int(len(y)*0.8):]

# 正解ラベルをone-hot表現へ変換
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

# ↓↓ここから転移学習↓↓
# 入力値の設定
input_tensor = Input(shape=(pic, pic, 3))

# vgg16のインスタンスの作成(include_top=False →VGGの特徴抽出部分のみ使用)
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(256, activation='relu'))
top_model.add(Dropout(rate=0.5))
top_model.add(Dense(4, activation='softmax'))

# モデルの結合
model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))

# VGGモデルの重みを固定
for layer in model.layers[:19]:
  layer.trainable = False

# コンパイルの条件設定
model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
              metrics=['accuracy'])

# 学習状況の表示
model.summary()

# 学習の実行
history = model.fit(X_train, y_train, batch_size=100, epochs=10, validation_data=(X_test, y_test))
# ↑↑ここまで転移学習↑↑

# 精度の評価(適切なモデル名に変えて、コメントアウトを外してください)
scores = model.evaluate(X_test, y_test, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])

# 正解率の可視化
plt.plot(history.history['accuracy'], label='acc', ls='-')
plt.plot(history.history['val_accuracy'], label='val_acc', ls='-')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(loc='best')
plt.show()


# データの可視化(検証データの先頭の10枚)
for i in range(10):
    x = X_test[i]
    plt.subplot(2, 5, i+1)
    b,g,r = cv2.split(x) 
    x = cv2.merge([r,g,b])
    plt.imshow(x)
plt.suptitle("10 images of test data",fontsize=20)
plt.show()

# 予測(検証データの先頭の10枚)
pred = np.argmax(model.predict(X_test[0:10]), axis=1)
print(pred)


#resultsディレクトリを作成
result_dir = 'results'
if not os.path.exists(result_dir):
    os.mkdir(result_dir)
    
# 重みを保存
model.save(os.path.join(result_dir, 'model.h5'))

files.download( '/content/results/model.h5' ) 

画像は100×100にリサイズ、学習回数は10回にしています。

モデルの作成では画像サイズを50、100、150、200に変更しながら確認していましたが、実行時間と正解率のバランスが良かった「100」を選択しました。

[1 1 2 1 1 0 0 1 3 0]
0:線状 1:粒々 2:もくもく 3:ふわっと

WEBサイド

HTMLとCSSでレイアウトを作成しています。

HTML

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>CloudShape</title>
    <link rel="stylesheet" href="./static/stylesheet.css">
</head>
<body>
    <header>   
        <a class="header-logo">雲判別(4分類)</a>
    </header>

    <div class="main">    
        <h2> 選択された画像の雲の形をざっくりと識別します</h2>
        <p>画像を選択して送信してください</p>
        <br>
        <p>【線状の雲<巻雲:すじ雲>】</p>
        <p>【粒々の雲<巻積雲:うろこ雲、高積雲:ひつじ雲>】</p>
        <p>【もくもくとした雲<積乱雲:入道雲>】</p>
        <p>【ふわっとした雲<積雲:わた雲>】</p>
        <br>
        <form method="POST" enctype="multipart/form-data">
            <input class="file_choose" type="file" name="file">
            <input class="btn" value="! 判定 !" type="submit">
        </form>
        <div class="answer">{{answer}}</div>
    </div>

    <footer>
        <small>2022 h.s</small>   
    </footer>
</body>
</html>

CSS

header {
    background-color: #87CEFA;
    height: 60px;
    margin: -8px;
    display: flex;
    justify-content: space-between;
}

.header-logo {
    color: #fff;
    font-size: 25px;
    margin: 15px 25px;
}

.header_img {
    height: 25px;
    margin: 15px 25px;
}

.main {
    height: 370px;
}

h2 {
    color: #444444;
    margin: 60px 0px;
    text-align: center;
}

p {
    color: #444444;
    margin: 10px 0px 10px 0px;
    text-align: center;
}

.answer {
    color: #444444;
    margin: 0px 0px 30px 0px;
    text-align: center;
}

form {
    text-align: center;
}

footer {
    background-color: #87CEFA;
    height: 50px;
    margin: 80px 0px 30px 0px;
    position: relative;
}


small {
    margin: 15px 25px;
    position: absolute;
    left: 0;
    bottom: 0;
}

Flask

WEBでアップロードした画像を扱い、学習済みモデルで識別するためにFlaskサイドを製作します。

import os
from flask import Flask, request, redirect, render_template, flash, session
from werkzeug.utils import secure_filename
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.preprocessing import image

import numpy as np


classes = ["線状の","粒々の","もくもくとした","ふわっとした"]
image_size = 100

UPLOAD_FOLDER = "uploads"
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])

app = Flask(__name__)

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

model = load_model('./model.h5')#学習済みモデルをロード


@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        if 'file' not in request.files:
            return render_template("index.html",answer='ファイルがありません')
        file = request.files['file']
        if file.filename == '':
            return render_template("index.html",answer='ファイルがありません')
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(UPLOAD_FOLDER, filename))
            filepath = os.path.join(UPLOAD_FOLDER, filename)

            #受け取った画像を読み込み、np形式に変換
            img = image.load_img(filepath, grayscale=False, target_size=(image_size,image_size,3))
            img = image.img_to_array(img)
            data = np.array([img])
            
            #変換したデータをモデルに渡して予測する
            result = model.predict(data)[0]
            predicted = result.argmax()
            ans_per = float(result[predicted]*100)

            pred_answer = "これは 【 " + classes[predicted] + "雲 】 です"
  
          return render_template("index.html",answer=pred_answer)

    return render_template("index.html",answer="")


if __name__ == "__main__":
    port = int(os.environ.get('PORT', 8080))
    app.run(host ='0.0.0.0',port = port)

画面確認

下の画像を選択して確認します。

ちゃんと【もくもくとした雲】って判定されました。

考察

上記の写真であればきちんと判定されましたが、
他の画像でも確認していると【線状の雲】が【粒々の雲】となる間違いが多くなっていました。
また、同様に【ふわっとした雲】が【もくもくとした雲】として判定されることも多かったです。

モデル作成では正解率がかなり高かったので意外に判定できると思っていたのですが、収集した画像がかなり少なかったので、きちんと判定するには足りなかったのかと思われます。

今回はモデルの水増しは行っていませんが、学習する枚数を増やすことで精度が上がると思われます。
また、カラー画像で学習させたため、グレースケールや色反転を行うことでの精度向上も確認したいと思います。

まとめ

AIアプリ開発の6ヶ月で最初の頃は順調に進んでいましたが、途中の中断期間が非常に悔やまれます。。

Pythonというプログラムを使用したいと思い始めた受講でしたが、機械学習を含めてWEBサイド制作もあり、一通り触れられたことは非常に大きな経験になったと感じています。
受講前に独学を少し行っていましたが、今回の内容までは辿りつかなかった気しかしないです。。

今後は受講内容の復習も進めていき、学んだ内容を生かして他の言語も含めて「自分でモノを作れる」ように頑張っていきたいと思います。

いいなと思ったら応援しよう!