IT

Tensorflow2.x+Kerasで環境構築しつつ画像収集から画像二値分類まで【Windows】

こんにちは,しまさん(@shimasan0x00)です.

以前,WindowsにGPUで機械学習できる環境を用意していたのですが,2020年6月のWindows Updateで全部破壊され,データが飛んでからというものちょっとした学習であればGoogleColabでごまかしごまかしで生きていました.

ですが,せっかくPCにRTX積んでるんだからもう一度!と思って色々と挫折しつつ試行錯誤しながら環境構築し,実際に画像収集から画像二値分類までをお話できればなと思います.

[1][2]がまた仕様で王冠になってます.申し訳無いです.

環境

  • OS
    • Windows10
  • Anaconda(Python 3.7.6) : 2020/02月版
    • Tensorflow 2.3.x
    • Keras 2.x
  • GPU
    • RTX2060
  • Nvidia
    • CUDA 10.1
    • cuDNN SDK 7.6

スポンサーリンク

環境構築

まずはGPU対応のTensorflowをインストールしていきます.

もしTensorflowやKerasを既にインストールしている人はアンイストールしておきましょう.

※ 特に!Geforce Experienceを既にインストールしている人,他のnvidiaドライバ周りをインストールしている人はアンイストールしてから始めると安心できます.

以下の「公式」ページを参考にインストールします.

※ 公式以外の記事を参考にすると思わぬところでミスがおきる(おきた)ので極力控えましょう

私の場合はGeforce周りのみ予めインストールしていた際に色々nvidia周りのパッケージをインストールしていたみたいでここでアウト判定がでてたみたいです.

他のものだと注意点は以下の通りです.

  • NVIDIA® GPU ドライバ : 自分の挿してるGPUで異なる
  • CUDA® ツールキット : 10.1(v1,v2などではなく,10.1をインストール)
  • cuDNN SDK 7.6 : 7.6の一番古いものを選んでインストール

あとは対応しているPythonが3.5-3.8なのも地味に注意です.

nvidia周りのインストールが終わったらpipでインストールします.

昔はtensorflow-gpuでGPU対応版をインストールしていた気がするのですが2.xになって便利になっていますね.

pip install tensorflow
pip install keras


インストールが無事できたらMNISTデータセットでうまくいっているか確かめましょう.

また,以下のコードでGPUが認識されているかを確かめることができるのでオススメです.

from tensorflow.python.client import device_lib
device_lib.list_local_devices()


GPUを使用しているかを確かめる記事をいくつか拝見すると,WindowsだとタスクマネージャーでGPU使用率の変化を見るとか,MNISTデータセットで学習する際の時間を見て判断するというものもありました.

また,もう一つ大事なことがあってTensorflowくんはデフォルトだとGPUをフルに使おうとしてくるので制限をかけるコードを使用することを強くオススメします.

import tensorflow as tf
physical_devices = tf.config.experimental.list_physical_devices('GPU')
if len(physical_devices) > 0:
    for k in range(len(physical_devices)):
        tf.config.experimental.set_memory_growth(physical_devices[k], True)
        print('memory growth:', tf.config.experimental.get_memory_growth(physical_devices[k]))
else:
    print("Not enough GPU hardware devices available")

スポンサーリンク

データ収集

今回はある目的を果たすための前段階としてCNNを使って顔画像の二値分類をしたい!というところから始まっています.

ですので収集対象とする画像データは特定の人物2名を対象としています.

画像を収集し,顔部分の検出と切り取り,単純に画像収集するだけでは学習には枚数が足りないので画像拡張をすることでデータの水増ししていきます.

画像収集

メインは以下のサイトを参考に実装してみました.

クエリ部分に「人の名前」で検索をかけ,それぞれ300枚収集しました.

※クロームのブラウザのバージョンを必ず確認しましょう.

chrome://version/

自分のOSに合わせたクロームをダウンロードし,解凍してから作業ディレクトリに配置しておきましょう.

それぞれ二人のユーザについて./imgs/XXX(User名)以下に保存します.

顔検出と切り出し

顔検出はface_recognitionライブラリを使用して検出していきます.

pipでインストールできるので楽です.

エラーが出る場合,dlib周りで引っかかっている可能性があるので検索して対処するといいかもしれません.

pip install face_recognition

顔検出した画像をそれぞれ./cutImg/XXX(ユーザ名)に保存していきます.

まずはライブラリのインポートをします.

import tensorflow as tf
from tensorflow import keras
from keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Conv2D, Flatten, Dropout, MaxPooling2D
from keras.callbacks import ModelCheckpoint

import itertools
from datetime import datetime
import json
import cv2
import glob
import re
import os
import sys
import matplotlib.pyplot as plt
import numpy as np
import time
import random
import IPython.display as display
import pathlib

img_file = glob.glob('./imgs/A/*.jpg')

tc = 0
fc = 0

for i in range(len(img_file)):
    try:
        img = face_recognition.load_image_file(img_file[i])
        imgs = cv2.imread(img_file[i], cv2.IMREAD_COLOR)
    except:
        print('image' + str(i) + ':NoImg')
        continue
    if imgs is None:
         print('image' + str(i) + ':NoImg')
    else:
        face_locations = face_recognition.face_locations(img)
        if len(face_locations) > 0:
            for rect in face_locations:
                t, r, b, l = rect
                cv2.imwrite('./cutImg/A/' + 'cutted' + str(tc) + '.jpg',imgs[t:b,l:r])
                tc +=1
        else:
            print(img_file[i],' :NoFace') 
            fc += 1
print('True Count :',str(tc))
print('False Count :',str(fc))

print('done')

顔を切り出した画像を保存したら次は学習するために画像サイズを合わせていきます.

今回は特に決め打ちで「100×100」にします.

その結果を./resizeImg/XXX(ユーザ名)に保存していきます.


rimg_file = glob.glob('./cutImg/A/*.jpg')

criret = 0.999
IMG_SIZE = (100, 100)
passhist_list = []
face_detect_count = 0

for file_name in rimg_file:
    try:
        img = cv2.imread(file_name)
        img = cv2.resize(img, dsize=IMG_SIZE)
    except:
        continue
    if img is None:
        print("img is none.")
        continue
    img_hist = []
    img_hist.append(cv2.calcHist([img], [0], None, [256], [0, 256]))
    img_hist.append(cv2.calcHist([img], [1], None, [256], [0, 256]))
    img_hist.append(cv2.calcHist([img], [2], None, [256], [0, 256]))
    for pass_hist in passhist_list:
        if cv2.compareHist(img_hist[0],pass_hist[0],cv2.HISTCMP_CORREL) > criret:
            break
        if cv2.compareHist(img_hist[1],pass_hist[1],cv2.HISTCMP_CORREL) > criret:
            break
        if cv2.compareHist(img_hist[2],pass_hist[2],cv2.HISTCMP_CORREL) > criret:
            break
    else:
        passhist_list.append(img_hist)
        cv2.imwrite('./resizeImg/A/' +  str(face_detect_count) + '.jpg',img)
        face_detect_count += 1


リサイズしたらあとはそれぞれの切り出し画像を根性判定します.

対象人物でない画像,顔が横を向いている画像などを除去し,残った画像が少ない方に合わせるように調整して今回はそれぞれ160枚の顔画像が得られました.

ただ,160枚というのは学習するとなったら心もとないですし,未知データに頑強であってほしいので以降で画像拡張で水増ししていきます.

データ水増し

kerasに存在するラクラク画像拡張ライブラリでよしなにデータを増やします.

以降で使用する学習データを保存することになります.

今回は./data/XXX(ユーザ名)で水増しデータを保存します.

画像1枚に対して8枚水増ししていきます.

dirs = glob.glob('./resizeImg/*')

dir_names = [re.sub('./resizeImg\\\\','',x) for x in dirs]

datagen = ImageDataGenerator(
        rotation_range=40,
        width_shift_range=0.2,
        height_shift_range=0.2,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True,
        fill_mode='nearest')


for i,name in enumerate(dir_names):
    files = glob.glob('./resizeImg/'+name+'/*.jpg')
    
    if not os.path.exists('./data/'+name):
        os.makedirs('./data/'+name, exist_ok=True)
    
    for file in files:
        img_array = cv2.imread(file,)
        img_array = cv2.cvtColor(img_array, cv2.COLOR_BGR2RGB)
        img_array = img_array.reshape((1,) + img_array.shape)
        j = 0
        for batch in datagen.flow(img_array, batch_size=1,
                                  save_to_dir='./data/'+name, save_prefix=name, save_format='jpg'):
            j += 1
            if j == 8:
                break

データセット作成

Tensorflowにはデータセットを楽に作成,管理できるtf.data.Datasetがあるのであやかって使うことにします.

先程までの画像では正規化されてないので正規化する関数を用意して前処理しておきます.

def preprocess_image(image):
    image = tf.image.decode_jpeg(image, channels=3)
    image = tf.image.resize(image, [100, 100])
    image /= 255.0  # normalize to [0,1] range

    return image

def load_and_preprocess_image(path):
    image = tf.io.read_file(path)
    return preprocess_image(image)

dsとしてdatasetを構築します.バッチサイズは適当です.

AUTOTUNE = tf.data.experimental.AUTOTUNE

data_root = pathlib.Path('./data/')
all_image_paths = list(data_root.glob('*/*'))
all_image_paths = [str(path) for path in all_image_paths]
random.shuffle(all_image_paths)

image_count = len(all_image_paths)

label_names_all = sorted(item.name for item in data_root.glob('*/') if item.is_dir())
label_names = [x for x in label_names_all if '.' not in x]

label_to_index = dict((name, index) for index,name in enumerate(label_names))

all_image_labels = [label_to_index[pathlib.Path(path).parent.name]
                    for path in all_image_paths]

path_ds = tf.data.Dataset.from_tensor_slices(all_image_paths)
image_ds = path_ds.map(load_and_preprocess_image, num_parallel_calls=AUTOTUNE)

label_ds = tf.data.Dataset.from_tensor_slices(tf.cast(all_image_labels, tf.int64))

image_label_ds = tf.data.Dataset.zip((image_ds, label_ds))

BATCH_SIZE = 32

ds = image_label_ds.apply(
  tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))
ds = ds.batch(BATCH_SIZE)
ds = ds.prefetch(buffer_size=AUTOTUNE)

さいごに,学習用とテスト用のデータに分割します.テスト用のデータは全体の20%にしています.

TAKE_SIZE = int(image_count * 0.2)

train_data = ds.skip(TAKE_SIZE)
test_data = ds.take(TAKE_SIZE)

スポンサーリンク

学習

とりあえずでそれっぽいCNNを作成して学習させます.

また,最良のモデルデータを自動で保存するようにしています.

batch_size = 32
epochs = 30
IMG_HEIGHT = 100
IMG_WIDTH = 100

model = Sequential([
    Conv2D(16, 3, padding='same', activation='relu', input_shape=(IMG_HEIGHT, IMG_WIDTH ,3)),
    MaxPooling2D(),
    Dropout(0.2),
    Conv2D(32, 3, padding='same', activation='relu'),
    MaxPooling2D(),
    Conv2D(64, 3, padding='same', activation='relu'),
    MaxPooling2D(),
    Dropout(0.2),
    Flatten(),
    Dense(512, activation='relu'),
    Dense(1, activation='sigmoid')
])

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

total_train = image_count - TAKE_SIZE
total_val = TAKE_SIZE

modelCheckpoint = ModelCheckpoint(filepath = 'cnn.h5',
                                  monitor='val_loss',
                                  verbose=1,
                                  save_best_only=True,
                                  save_weights_only=False,
                                  mode='min',
                                  period=1)

history = model.fit(
    train_data,
    steps_per_epoch=total_train // batch_size,
    epochs=epochs,
    validation_data=test_data,
    validation_steps=total_val // batch_size,
    callbacks=[modelCheckpoint]
)

結果を描画してみます.

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

さいごに

今回はとりあえずデータ収集からモデルの構築,学習までをほんとうに大雑把ではありますがやりました.

次はこの学習結果を使用した応用と目的達成のための準備をしていきたいと思います.

参考

ABOUT ME
しまさん
高専→大学編入→大学院→? / 計算社会科学,ウェブマイニングなど / グレープフルーツと本が好き / SNS(Twitter,YouTube,Twitch)やVTuberのデータ分析屋 詳しいプロフィールはこちら≫ 投げ銭はコチラ