金属部品の品質不良を検出するアプリを作ってみた

### **1.はじめに** 
私は金属部品メーカーで勤務しています。部品の製造工程における品質検査項目の中には定量化が難しいものがあり、感応検査に頼らざるを得ない状況があります。こういった項目の検査を人の手に頼らず行えるようにする手段として機械学習による合否判定を試してみたいと考え、金属部品の画像をアップロードすると品質不良の有無を判定するアプリを作成してみました。

### **2.開発環境**
・VAIO TYPE Z (VJZ131)
・WINDOWS 10 HOME EDITION
・Google Colaborabory
・Python（Ver 3.8.5)

### **3.使用したライブラリ**

・TensorFlow
Googleが開発した機械学習のためのライブラリ。

・OpenCV
Open Source Computer Vision Libraryの略で画像処理・画像解析に使用するライブラリ。

・Numpy
Pythonで数値計算を行うためのライブラリ。

・Flask
PythonでWebアプリケーションを作成するためのライブラリ


### **4.学習データ**
Kaggleで公開されているポンプ用金属部品(インペラ)の画像データセットを使用。
*"casting product image data for quality inspection"*
https://www.kaggle.com/ravirajsinh45/real-life-industrial-dataset-of-casting-product


### **5.学習モデルの構築**
__①ライブラリのインポート～画像データの読み込み__

```python:defect_recognition.py

# ライブラリのインポート
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
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
import glob

# ディレクトリの変更
os.chdir(os.path.dirname(os.path.abspath(__file__)))


# ファイルパスの設定
path_OK = os.listdir('./OK')
path_DEFECT = os.listdir('./DEFECT')
path_JUDGE = os.listdir('./JUDGE')

# データを合格、不合格、検証用に分類
img_OK = []
img_DEFECT = []
img_JUDGE=[]

for i in range(len(path_OK)):
    img = cv2.imread('./OK/' + path_OK[i])
    img = cv2.resize(img, (200,200))
    img_OK.append(img)

for i in range(len(path_DEFECT)):
    img = cv2.imread('./DEFECT/' + path_DEFECT[i])
    img = cv2.resize(img, (200,200))
    img_DEFECT.append(img)

# 検証用データ
for i in range(len(path_JUDGE)):
    img = cv2.imread('./JUDGE/' + path_JUDGE[i])
    img = cv2.resize(img, (200,200))
    img_JUDGE.append(img)

X = np.array(img_OK + img_DEFECT)
y =  np.array([0]*len(img_OK) + [1]*len(img_DEFECT))
```

__②画像データのシャッフル__

```
rand_index = np.random.permutation(np.arange(len(X)))
X = X[rand_index]
y = y[rand_index]

# データの分割
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):]

# categorical_crossentropyとともに用いるためのバイナリのクラス行列に変換
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
```

__③モデルの構築__
今回はVGG16という学習済みのモデルを使って、品質不良の有無を判別するためのモデル構築（転移学習）を行っていきます。VGGモデルはオックスフォード大学のVGG(Visual Geometry Group)チームが開発を行い、「ImageNet」という120万枚の画像で構成される巨大なデータセットで学習を行った画像分類モデルで、2014年に行われた画像認識のコンペティションで2位と優秀な成績を収めています。VGGにはVGG16とVGG19の２種類のモデルがあり、今回使用するVGG16は畳み込み13層、全結合層3層の合計16層で構成されるニューラルネットワークです。

```
# モデルにvggを使用
input_tensor = Input(shape=(200, 200, 3))
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

# vggのoutputを受け取り、2クラス分類する層を定義します
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(2, activation='softmax'))

# vggと、top_modelを連結
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.fit(X_train, y_train, batch_size=16, epochs=15, validation_data=(X_test, y_test))

# 重みを保存
result_dir = 'results'
if not os.path.exists(result_dir):
    os.mkdir(result_dir)
model.save(os.path.join(result_dir, 'model.h5'))

```
以下は学習の過程です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/da2b8a18-0c26-03d0-2c24-b91410cea8f7.png)

__⑤合否判定の関数とモデルの評価__

```

# 画像を一枚受け取り、合格か不合格かを判定する関数

def pred_quality(img):
    img = cv2.resize(img, (200, 200))
    pred = np.argmax(model.predict(np.array([img])))
    if pred == 0:
        return 'OK'
    else:
        return 'DEFECT'
    

# 精度の評価
scores = model.evaluate(X_test, y_test, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])

```

__⑥検証__

```
# 検証用データ10枚でテストを行う
import os
import glob
files_list=[]
path = "/content/drive/My Drive/Defect_recognition/JUDGE"
files = glob.glob(path + "/*")
for f in files:
    files_list.append(os.path.basename(f))

count=0
for i in img_JUDGE:
  cv2_imshow(i)
  if count==0:
    print("ファイル名:" + files_list[0])
  else:
    print("ファイル名:" + files_list[count])
  count+=1
  print("判定結果:" + pred_quality(i))

```

ファイル名:検証データ_不良品_0_9980.jpeg
判定結果:DEFECT
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/7cefcf0d-feb9-5a9f-738c-a5a22a40fe96.png)



ファイル名:検証データ_不良品_0_9963.jpeg
判定結果:DEFECT
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/21fee628-0a26-e173-9fc8-35c87e381c74.png)



ファイル名:検証データ_不良品_0_9877.jpeg
判定結果:DEFECT
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/590d1eb0-0f7d-1b4c-b37f-7440b00cb457.png)



ファイル名:検証データ_不良品_0_9879.jpeg
判定結果:DEFECT
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/6d8a70c1-84fb-ba26-cf71-5f1ff76e1d16.png)


 
ファイル名:検証データ_不良品_0_9995.jpeg
判定結果:DEFECT
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/c4f711a6-562b-6b0e-57b1-75fe1ecded2f.png)
 


ファイル名:検証データ_良品_0_9990.jpeg
判定結果:OK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/e6847d78-5c3a-b6c7-11b2-ecf736d0e859.png)
 
ファイル名:検証データ_良品0_9843.jpeg
判定結果:OK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/a51bbee3-7769-5afb-e7e2-71f0e2de6025.png)
 
ファイル名:検証データ_良品0_9959.jpeg
判定結果:OK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/44d61d6f-4895-b3ed-c739-b95d7ddb8de3.png)
 


ファイル名:検証データ_良品_0_9966.jpeg
判定結果:OK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/514ae2fa-9398-dc9a-404e-10d0e98eb1a1.png)

 

ファイル名:検証データ_良品_0_9850.jpeg
判定結果:OK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/f1f5d2ad-bb12-3ec3-4447-3002d2a43042.png)


### **6.Herokuへのデプロイ**
5で作成したモデルを使ってWEBアプリを作成していきます。

PYTHONコード

```python:main.py
import os
from flask import Flask, request, redirect, render_template, flash
from werkzeug.utils import secure_filename
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.preprocessing import image

import numpy as np

os.chdir(os.path.dirname(os.path.abspath(__file__)))


classes = ["OK","DEFECT"]
image_size = 200

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:
            flash('ファイルがありません')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            flash('ファイルがありません')
            return redirect(request.url)
        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,target_size=(image_size,image_size))
            # img = image.load_img(filepath, grayscale=True, target_size=(image_size,image_size))
            img = image.img_to_array(img)
            data = np.array([img])
            #変換したデータをモデルに渡して予測する
            result = model.predict(data)[0]
            predicted = result.argmax()
            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)
```

HTMLコード

```index.html

<!DOCTYPE html>
<html lang='ja'>
<head>
    <meta charset='UTF-8'>
    <meta name='viewport' content="device-width, initial-scale=1.0">
    <meta http-equiv='X-UA-Compatible' content="ie=edge">
    <title>Quality Defect Checker</title>
    <link rel='stylesheet' href="./static/stylesheet.css">
</head>
<body>
    <header>   
        <img class='header_img' src="https://aidemyexstorage.blob.core.windows.net/aidemycontents/1621500180546399.png" alt="Aidemy">
        <a class='header-logo' href="#">Quality Defect Checker</a>
    </header>

    <div class='main'>    
        <h2> AIが送信された画像の合否を判定します</h2>
        <p>画像を送信してください</p>
        <form method='POST' enctype="multipart/form-data">
            <input class='file_choose' type="file" name="file">
            <input class='btn' value="submit!" type="submit">
        </form>
        <div class='answer'>{{answer}}</div>
    </div>

    <footer>
        <img class='footer_img' src="https://aidemyexstorage.blob.core.windows.net/aidemycontents/1621500180546399.png" alt="Aidemy">
        <small>&copy; 2019 Aidemy, inc.</small>   
    </footer>
</body>
</html>
```

ターミナルへ以下のコマンドを入力してHerokuへデプロイします。

```
#アプリのあるディレクトリへ移動
cd #ファイルパス
#Herokuへログイン
heroku login
#Herokuでアプリを作成し、buildpackをpythonとする
heroku create #アプリ名 --buildpack heroku/python
$ git init
$ heroku git:remote -a (アプリ名)
$ git add .
$ git commit -m '(更新内容を書く)'
$ git push heroku master
```
Herokuへのデプロイ完了が完了しました。モデル構築後に行った検証と同じ画像10点を使ってテストを行ったところ、すべて正しく判定されることが確認できました。
https://aidemy1023-3.herokuapp.com/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2151903/7c567202-0d57-b237-26b0-ca93eebf344d.png)


### **7.まとめ**
今回モデルを最適化する過程で、厄介だったのは光の加減やピント等のバラツキでした。今回は画像サイズを大きくすることでこの問題は解消できましたが、ワークの映り方のばらつきは今回の取り組みを実際の品質検査に応用する際にも課題になり得るのではと考えています。実際の品質検査へ応用する際には今回実践した内容に加えて以下のようなアプローチも検討し、精度を向上させたいと考えています。

・学習モデルをVGG16から変更する（Inception V3等）
・誤判定される画像に類似した特徴（光の当たり、ピントの具合等）を持つデータを水増して学習を行う

### **8.参考文献**
今回使用したデータセット
https://www.kaggle.com/ravirajsinh45/real-life-industrial-dataset-of-casting-product

VGG16の概要
https://jp.mathworks.com/help/deeplearning/ref/vgg16.html
https://towardsdatascience.com/step-by-step-vgg16-implementation-in-keras-for-beginners-a833c686ae6c