自動車の画像からメーカーを分類をしてみる


# はじめに
こんにちは。
私はシステム開発会社でSEやプロマネを担当しています。担当するシステムは普通の業務システムが多くAI機械学習などのエマージングテックにはあまり縁がありません。しかし今後そういった案件のプロマネをしたりデータサイエンティストと一緒に作業することも想定されるため、技術のキャッチアップを目的としてAidemy　データ分析コースを受講することにしました。

この記事は学習した内容を総括したものです。自動車の画像分類を通して学習の振り返りをしていきたいと思います。

# 実行環境
Windows11
Google Colab　
Python 3.10.11


# 画像を収集する

自動車の画像データとしてKaggleサイトから以下をダウンロードして使用することにしました。

[Stanford Car Dataset by classes folder](https://www.kaggle.com/datasets/jutrera/stanford-car-dataset-by-classes-folder )

画像はこんな感じです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3121398/440d851a-11de-9168-c6fb-6af2a62a9a53.png)

Kaggleサイトからローカルにダウンロードして解凍します。


```python:homework2.ipynb
from google.colab import drive
drive.mount('/content/drive')
# kaggle ライブラリのインストール
!pip install kaggle

# 一時フォルダに .kaggleフォルダを作成
!mkdir ~/.kaggle

# MyDrive の kaggle.json を一時フォルダ内の .kaggleフォルダにコピー
!cp /content/drive/MyDrive/kaggle/kaggle.json ~/.kaggle/

# アクセス権限の設定
!chmod 600 ~/.kaggle/kaggle.json
!mkdir ~/.kaggleN

# zipファイルのダウンロード
!kaggle datasets download -d jutrera/stanford-car-dataset-by-classes-folder -p /content/drive/MyDrive/kaggle

# 解凍
!unzip /content/drive/MyDrive/kaggle/stanford-car-dataset-by-classes-folder -d /content/drive/MyDrive/kaggle
!rm /content/drive/MyDrive/kaggle/stanford-car-dataset-by-classes-folder.zip


```


ダウンロードデータには「"メーカー名" + " " + "車名"」ごとに画像ファイルが格納されています。
また訓練・テスト用で分けて格納されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3121398/b80a437d-5de3-7464-a39b-f808ef2144ed.png)


ただし車名の分類をするには画像データ量が少ないと感じたので今回はメーカー名の判別にとどめることにしました。
とりわけ車の形が特徴的な「Jeep」、「Acura」を対象としています。


それを実施するためにファイルの準備を続けます。

元のフォルダは車名ごとでしたが、処理しやすいように画像をメーカーごとに振り分けます。
つまりJeepとAcuraの画像を対象に、以下のフォルダに振り分けます。（それ以外のメーカーの画像は削除しました。）

訓練データ用
/content/maker/train/Jeep
/content/maker/train/Acura

テストデータ用
/content/maker/test/Jeep
/content/maker/test/Acura

```
import pandas as pd
import os
import shutil

root_directory = "/content/drive/MyDrive/kaggle/"
local_root_directory = "/content/"


name_df = pd.read_csv(root_directory + "names.csv", names=['class_name'])
name_df.index = name_df.index + 1
name_df = name_df.rename_axis("classes")
name_df["maker"] = name_df["class_name"].str.split().apply(lambda x: x[0])

train_df = pd.read_csv(root_directory + "anno_train.csv", names=["file_name", "p1", "p2", "p3", "p4", "classes"])
train_df = train_df.drop(columns=["p1", "p2", "p3", "p4"])

test_df = pd.read_csv(root_directory + "anno_test.csv" , names=["file_name", "p1", "p2", "p3", "p4", "classes"])
test_df = test_df.drop(columns=["p1", "p2", "p3", "p4"])

train_df = pd.merge(train_df, name_df, on="classes").sort_values(by="file_name", ascending=True)
test_df = pd.merge(test_df, name_df, on="classes").sort_values(by="file_name", ascending=True)

maker_directory = root_directory + "maker/"
if not os.path.exists(maker_directory):
  #print(maker_directory)
  os.mkdir(maker_directory)
  for pattern in ["train/", "test/"]:
    pattern_directory = maker_directory + pattern
    if not os.path.exists(pattern_directory):
      os.mkdir(pattern_directory)
      for maker_name in name_df["maker"].drop_duplicates():
        maker_name_directory = pattern_directory + maker_name
        if not os.path.exists(maker_name_directory):
          os.mkdir(maker_name_directory)


data = {"train/": train_df, "test/": test_df}

for pattern, dataset in data.items():
  image_root = root_directory + "car_data/car_data/"
  for row in dataset.itertuples(index=False):
      image_file_path_from = image_root + pattern + row.class_name + "/" +  row.file_name
      image_file_path_to = maker_directory + pattern + row.maker + "/" 
      #print(image_file_path_from)
      #print(image_file_path_to)
      if os.path.exists(image_file_path_from):
        shutil.copy(image_file_path_from, image_file_path_to)
```

```
import shutil

#maker_root = '/content/maker/'
maker_root = maker_directory

target_maker_list = ["Jeep", "Acura"]

for pattern, dataset in data.items():
  folder_path = maker_root + pattern
  folder_list = os.listdir(folder_path)
  for directory in folder_list:
    if directory not in target_maker_list:
      shutil.rmtree(folder_path + "/" + directory)

```


次にtensorflowをつかってモデルの学習を行います。

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

import matplotlib.pyplot as plt

import numpy as np
from keras.preprocessing import image

# 画像のディレクトリを指定
train_dir = maker_directory + "train/"
test_dir = maker_directory + "test/"



# データ拡張を適用する
train_datagen = ImageDataGenerator(
    rescale=1./255, # 画像ピクセル値を0〜1の範囲に変換
    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' # 新しいピクセルを埋める方法
)

test_datagen = ImageDataGenerator(rescale=1./255)

# バッチサイズと画像サイズを指定
batch_size = 32
img_height = 150
img_width = 200

# トレーニング用データジェネレータを作成
train_generator = train_datagen.flow_from_directory(
    train_dir,
    target_size=(img_height, img_width),
    batch_size=batch_size,
    class_mode='categorical'
)

# テスト用データジェネレータを作成
test_generator = test_datagen.flow_from_directory(
    test_dir,
    target_size=(img_height, img_width),
    batch_size=batch_size,
    class_mode='categorical'
)

# モデルを定義
num_classes = 2

model = Sequential([
    Conv2D(32, (3, 3), activation='relu', input_shape=(img_height, img_width, 3)),
    MaxPooling2D((2, 2)),
    Conv2D(64, (3, 3), activation='relu'),
    MaxPooling2D((2, 2)),
    Conv2D(128, (3, 3), activation='relu'),
    MaxPooling2D((2, 2)),
    Conv2D(128, (3, 3), activation='relu'),
    MaxPooling2D((2, 2)),
    Flatten(),
    Dense(512, activation='relu'),
    Dense(num_classes, activation='softmax')
])

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


# モデルをトレーニング
epochs = 100
history = model.fit(
    train_generator,
    epochs=epochs,
    validation_data=test_generator
)



accuracy=history.history['accuracy']
val_accuracy=history.history['val_accuracy']
epochs=len(accuracy)
plt.plot(range(epochs), accuracy, marker = '.', label = 'accuracy')
plt.plot(range(epochs), val_accuracy, marker = '.', label = 'val_accuracy')
plt.legend(loc = 'best')
plt.grid()
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()
```

学習の経過をグラフで表示したものです。
なんどか試しましたが、今回のケースでは100回くらい試行すれば正答率９割超えで結果が収束したように思えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3121398/dd116a53-003b-3969-2584-320aac478061.png)


学習したモデルを使いネットで拾ったJeepやAcuraの画像をつかって判別してみます。
※コードはJeepを使った判別結果です。
※使用したJeep、Acura画像は権利関連が不安だったので掲載していません

```
import numpy as np
import keras.utils as image

labels = (train_generator.class_indices)
print(labels)
 
# 新しい画像ファイルを読み込む
# validate_file_path = maker_root + "train/" + target_maker_list[0] + "/"
# image_list = os.listdir(validate_file_path)
# print(validate_file_path)
# for image_file_path in image_list:
#   img_path = validate_file_path + image_file_path
#   img = image.load_img(img_path, target_size=(img_height, img_width))

#   # 画像をnumpy配列に変換
#   x = image.img_to_array(img)
#   x = np.expand_dims(x, axis=0)

#   # モデルを使用してクラスを予測
#   preds = model.predict(x)

#   # 結果を表示
#   print(preds)

img = image.load_img("/content/drive/MyDrive/kaggle/download/Jeep/16.jpg", target_size=(img_height, img_width))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)

# モデルを使用してクラスを予測
preds = model.predict(x)

# 結果を表示
print(preds)


実行結果
{'Acura': 0, 'Jeep': 1}
1/1 [==============================] - 0s 36ms/step
[[1. 0.]]

```

結果が正しく判別できました。


# まとめ
今回は特徴量の差が大きい２種類の分類について、期待したとおりの分類をすることができました。
一方で、当初学習に使用したJeep画像枚数がAcuraの倍くらいあったせいで結果が正しく出なかったことなど、試行錯誤が続きすんなりいかないところもありました。
簡単な分類ケースではありますが、実体験として実施することができてよかったと思います。

また、当初目的としていた機械学習の流れや実施方法もつかむことができました。
自力で複雑な分析はできませんが、専門家であるデータサイエンティストがどのようなステップで仕事をしていくのかを理解できたことで、今後の仕事にも生かしていけるのではないかと思います。