パーソナルカラー診断に沿った好みの服装を診断する。

<title>[Python]パーソナルカラー診断に沿った好みの服装を診断する。</title>

<h1>はじめに</h1>
こんにちは。Qiita初投稿・エンジニア転職を目指し勉強中のtravenaです。
学習のアウトプット発信の一環として本記事を投稿します。短時間でできる限り頑張ります。

# 目的
### 自分に似合うかつ好きなコーディネートを提案します。　

季節ごとに着る服に悩みませんか。トレンドもころころ変わり自分の好みを見失うことも。。

こちらのアプリでは好きな画像を選ぶことであなたの好きな服装のジャンルを判定し、かつパーソナルカラー診断に沿ってあなたに似合うコーディネートを提案します。

<h1>パーソナルカラー診断とは</h1>
パーソナルカラー（英：Color analysis）とは、その人の生まれ持った身体の色（肌の色・髪の色・目の色など）と雰囲気が調和した色（＝似合う色）のことであり、人それぞれ個性が違うように似合う色もそれぞれ違うとする視覚心理学的理論に基づく審美感のことである。 似合う色を見つける診断のことを「パーソナルカラー診断」という。「似合う色」の化粧や服装を身につけると顔色が良く活き活きとした表情に見え、「似合わない色」を身につけると顔色が悪く元気がなく疲れた印象に見えてしまうことがあります。パーソナル診断はいくつかの特徴からあなたのパーソナルカラーを導き出すことです。

# 実際にお使いください。
https://travena3-pasokara-app-main-gqyrup.streamlitapp.com/


# 内容
1\. パーソナルカラー分析
2\. 季節ごとの好みの服装を選択する
3\.分析データとなる画像を収集する
4\.画像分析のモデルを作成する
5\.好みの服装の系統を分析する
6\.デプロイ
7\.まとめ

# 実行環境
・Visual Studio Code
・Google Colaboratory



# 1\. パーソナルカラー分析
  ※今回はstreamlitを使用したのでstreamlitをインポートしています。
```python:qiita.rb
#必要な機能をインストール
import streamlit as st
import numpy as np 
import collections
from PIL import Image
import glob
import os

st.title("パーソナルカラークローゼット")
```
１．各設問の回答をリストに格納する

```python:qiita.rb
ans_list = []#設問の答えを格納するリスト

#st.radioでラジオボタンを設置し各設問の答えを選択できるようにする。
#答えはgenreへ格納
genre = st.radio("Q1.あなたの目の色は？",['A','B','C','D',], horizontal=True,key="q1")
st.write('A.瞳は明るいブラウンかソフトなブラック。ガラス玉の様にキラキラしている。')
st.write('B.瞳はダークブラウンかブラック。落ち着いた印象がある')
st.write('C.瞳は赤みのブラウンかソフトなブラック。優しい印象がある')
st.write('D.瞳はブラックか赤みのダークブラウン。白目と黒目のコントラストがはっきりしている')
#genreをans_listへ追加します。
ans_list.append(genre)
```
2．回答を集計し最も当てはまるパーソナルカラーを割り出す

```python:qiita.rb
#collections.Counterでストの各要素の数え上げ
ans_list2=collections.Counter(ans_list)
#出現回数が多い要素順にCounterオブジェクトを並び替え
pc_ans = ans_list2.most_common()
#並び変えた結果先頭のものをパーソナルカラーとする
pc = pc_ans[0]
```
3\.割り出されたパーソナルカラーの画像を表示する
```python:qiita.rb
#パーソナルカラーごとのカラーリストの画像を定義する
image = Image.open("spring_color.jpeg")
image_2 = Image.open("summer_color.jpeg")
image_3 = Image.open("autumn_color.jpeg")
image_4= Image.open("winter_color.jpeg")

#pcの結果ごとのカラーリストを表示
st.write("あなたのパーソナルカラーは")
if "A" in pc:
    st.image(image, caption='spring',use_column_width=True)
if "B" in pc:
    st.image(image_2, caption='summer',use_column_width=True)  
if "C" in pc:
    st.image(image, caption='autumn',use_column_width=True)
if "D" in pc:
    st.image(image, caption='winter',use_column_width=True) 
```

# 2\.季節ごとの好みの服装を選択する
1\.季節ごとの画像を表示する

```python:qiita.rb
#画像３０枚を見やすくするため画面を６分割する
ol = st.columns(6)

#季節の選択しと選択した季節によって画像を表示させる
if season_1 == "SS" :
    selected_list = file_list_SS
    for i in range(len(file_list_SS)):
        SS_image = Image.open(file_list_SS[i])
        ii =  i % 6
        col[ii].write(i)
        col[ii].image(SS_image,use_column_width=True)
if season_1 == "AW" :
    selected_list = file_list_AW
    AW_image = Image.open(file_list_AW)
        ii =  i % 6
        col[ii].write(i)
        col[ii].image(AW_image,use_column_width=True)
```
2\.好みの服装を選択する
```python:qiita.rb
#st.multiselectで複数の画像が選択できる
selected_numbers = st.multiselect('好みの服装の番号を選択', list(range(len(selected_list))))
selected_pictures = [selected_list[i] for i in selected_numbers]
```
# 3\. 分析データとなる画像を収集する
icrawlerを使用し画像を収集する
収集した画像を精査する
　※画像は検索ワードによって無作為に収集されているため精査をすることがモデルの精度に影響します。

### ★icrawlerを選んだ理由
服のジャンル分けをする上で写真データは必須でした。自身で集めると工数がかかってしまいます。
非常に楽に収集できるので画像分析の際は使用をお勧めします。
ただし、検索ワードの対象はすべてダウンロードされるため対象外のものも含まれてます。
より良いモデル作成のために集めた画像の精査を必ず実施してください。

```python:qiita.rb
!pip install icrawler
from google.colab import drive
#画像の格納先を記載
drive.mount('/content/drive')
for search_word,dir_name in zip(search_words,dir_names):

#検索リストの生成
search_words = ["レディース　コーディネート　古着MIX","レディース　コーデ　ガーリー","レディース　コーデ　ハンサム　海外","レディース　コーデ　カジュアル", ,"レディース　コーデきれいめ"]#検索ワードを記載する
    dir_names = ["hurugi","girly","handsome","casual","kirei"]#格納後のフォルダの命名

# Bing用クローラーの生成
   bing_crawler = BingImageCrawler(
       downloader_threads=5,           # ダウンローダーのスレッド数
       storage={'root_dir': "/content/drive/MyDrive/成果物/streamlit/SS_models/" + dir_name}) # ダウンロード先のディレクトリ名  

# クロール（キーワード検索による画像収集）の実行
   bing_crawler.crawl(
       keyword=search_word,   # 検索キーワード（日本語もOK）
       max_num= 200 )                    # ダウンロードする画像の最大枚数（max_num)は200枚
```

# 4\.画像分析のモデルを作成する
1\.モデルのインポート
```python:qiita.rb
#モジュールをインポート
import os#osモジュール（os機能がpythonで扱えるようにする）
import cv#画像や動画を処理するオープンライブラリ
import numpy as np#python拡張モジュール
import matplotlib.pyplot as plt#グラフ可視化
from tensorflow.keras.utils import to_categorical#正解ラベルをone-hotベクトルで求める
import glob#特定のパターンにマッチするファイルを取得する
from keras.applications.inception_v3 import InceptionV3#画像学習（48 層の畳み込みニューラル ネットワーク）
from keras.layers.pooling import GlobalAveragePooling2D#は 各チャンネル（面）の画素平均を求め、それをまとめる
from keras.callbacks import EarlyStopping#学習が収束した時に自動的に学習を止める
from keras.layers import Dense, Dropout, Flatten, Input#全結合層、過学習予防、平滑化、インプット
from tensorflow.keras import optimizers　#最適化
import tensorflow as tf #機械学習のライブラリ
```
2\.モデルを作成する

以下は通常のコードと変わらないため一旦折りたたみます
<details>
<summary>モデル構築途中</summary>

dir_names = ["hurugi","girly","handsome","casual","kirei"]
#分析データを格納したディレクトリに移動する
%cd '/content/drive/MyDrive/成果物/streamlit/SS_models/'

path_list = []
num_list=[]
for dir_name in dir_names: 
  path = (glob.glob('./'+dir_name+'/*'))
  num_list.append(len(path))
  path_list+=path

img_list= []
for i in range(len(path_list)):
    img = cv2.imread(path_list[i])
    img = cv2.resize(img, (300,300))
    img_list.append(img)

X = np.array(img_list)/255 
y =  np.array( [0]*num_list[0] + [1]*num_list[1] + [2]*num_list[2]+ [3]*num_list[3]+ [4]*num_list[4])

#データの分割
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_hot = to_categorical(y_train)
y_test_hot = to_categorical(y_test)
</details>

### ★es_cb = EarlyStopping(monitor='val_loss', patience=10, verbose=0, mode='auto')の使用について
ある程度学習に変化が見られなかったら学習を止めることができます。
画像のモデルを作成するにあたって画像データを使用することで学習が非常に遅かったです。
私用することで無駄なエポックを重ねることがなかったです。
学習を効率化するためにぜひ活用ください。

### ★InceptionV3の使用
画像認識の精度が高いInceptionV3を使用しました。
少し重くはなりますが、InceptionV3を使った際と比べると精度が上がりました。


```python:qiita.rb
# モデルに InceptionV3を使います
input_tensor = Input(shape=(300, 300, 3))
inceV3 = InceptionV3(include_top=False, weights="imagenet", input_tensor=input_tensor)
x = inceV3.output
x = GlobalAveragePooling2D()(x)
x = Dense(512, activation='relu')(x)
predictions = Dense(len(dir_names), activation='softmax')(x)

# vggと、top_modelを連結します
model = Model(inputs=inceV3.input, outputs=predictions)

for layer in model.layers[:249]:
    layer.trainable = False

    # Batch Normalization の freeze解除
    if layer.name.startswith('batch_normalization'):
        layer.trainable = True

for layer in model.layers[249:]:
    layer.trainable = True
# EarlyStoppingでval_lossを監視し10回数値の変動が見られなかった場合は学習を停止します。
es_cb = EarlyStopping(monitor='val_loss', patience=10, verbose=0, mode='auto')
# コンパイルします
#loss　どれくらい正解と離れているか
model.compile(loss='categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

# 学習を行います 
history=model.fit(X_train, y_train_hot, batch_size=32, epochs=50, validation_data=(X_test, y_test_hot), callbacks=[es_cb])

# 作成したモデルの保存
model.save('./cnn.h5')

```
### ・実装結果
![実装結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2737468/f06702aa-df7e-43e1-b1a3-5b33eff5ed31.png)

## より良いモデルにするために
画像認識の精度を上げるために画像のサイズを上げた結果、学習に時間がかかりました。
加えて精度を上げるためにInceptionV3を使用したことで十分な学習をすることができなかったです。
今後はハイパーパラメータの自動化を実行するなどをしてより良い学習を実施したいです。
また前処理として服のジャンルとしてより明確なものを採用する、画像に対して判断軸をより狭めるなど画像の精査をより慎重に行うべきだったと感じております。



# 5\.好みの服装分析を分析する
1\.モデルによってどのジャンルが最も選ばれたかを集計する

```python:qiita.rb
model = load_model('cnn.h5')#学習済みモデルをロード

classes = ["hurugi","girly","handsome","casual","kireime"]
image_size = 300
#受け取った画像を読み込み、np形式に変換
img_data = []
for path in selected_pictures :    
    img = image.load_img(path, target_size=(image_size,image_size))#filepathを選択した写真に
    img = image.img_to_array(img)
    img_data.append(img)
data = np.array(img_data)
predict_botan = False
if len(selected_pictures) >= 6:
    predict_botan = st.button('選択完了')
#変換したデータをモデルに渡して予測する
if predict_botan :
    result = model.predict(data)
    predicted = result.argmax(axis=1)
    u,counts = np.unique(predicted, return_counts=True)
#もっとも多かったジャンルを割り出す
    result_class= (u[counts.argmax()])
```
2\.自身のパーソナルカラーに沿ったジャンルのコーデを表示する

```python:qiita.rb
#パーソナルカラーと選ばれたジャンルから画像を１枚表示する。
    season_dict = {"A":"spring","B":"summer","C":"autumn","D":"winter"}
    concut_path = [season_1, season_dict[pc[0]],classes[result_class]]
    result_filename = "_".join(concut_path)
    #ファイルのパスを構成する
    result_filepath = season_1 + "_" +  season_dict[pc[0]] + "/" + result_filename + ".jpg"
    #パスを使用して画像を表示する
    result_picture = Image.open(result_filepath)
    st.write("今季のあなたにピッタリのコーディネートはこちらです。")
    st.image(result_picture)

```

# 6\.デプロイ
・Gitアカウントの作成

https://git-scm.com/book/ja/v2/GitHub-%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E6%BA%96%E5%82%99%E3%81%A8%E8%A8%AD%E5%AE%9A

・Streamlit Sharingによるデプロイ

https://tech.ledge.co.jp/entry/2020/10/20/120000


# 7\.まとめ
### 改善点
・好み分析を選択するタブが画像とかぶってみにくい
➡移動させることでより見やすくする

・モデルの精度がおもったより上がらなかった
➡モデル作成の分析対象を見直す

・分析後提示される画像が少ない
➡たくさんの情報が得られるように画像の表示を増やす。


### 最後に
わからないことが多く、中だるみもあり想定より長く時間をかけてしまいました。
全くの初心者ですがここまで完成させることができて驚いております。
後学のためと始めましたが、仕事で活用できるようになりたいです。