【OpenCV】画像のクラス分類にDLを使いたくない - uniformLBPによるクラス分類

OpenCV Advent Calendar 2023 22日目
全国高専_非公式 Advent Calendar 2023 22日目

画像のクラス分類をテクスチャ解析でやってみようという試みです。
記事タイトルにもあるように、uniformLBPを用いてやってみます。

# 自己紹介
どこかしら高専の学生です。
サーバーとかフロントとかバックとかデザインとかプレゼンとか、色々やる人です。
普段は物体検知とかをディープでポンしてます。

# この記事の目的
画像のクラス分類を、機械学習を使ってはいけない場合、どうやって実装するのかの例を提示してみます

# 実験諸条件
### 実験内容
uniformLBP(テクスチャ解析)を用いて3クラス分類

### 実験に用いる画像
人間、猫、犬の画像(kaggleから集めてきた)
画像サイズはそれぞれ違うし、背景も色々、色も色々です。
総データ数は各4000枚(基準3200, テスト800)です。
<p>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128128/be71046c-375a-f34c-41eb-8421c47a776a.jpeg" width="100px" />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128128/4984ba97-dcce-f0a9-b012-ac3ece7533b9.jpeg" width="100px" />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128128/074b46d8-8722-960a-c75d-270a87573aee.png" width="100px" />
</p>

<p>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128128/6b9703ec-0457-0b1a-7497-f4d05ed0f17c.jpeg" width="100px" />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128128/772eb1fa-c285-d328-e3a7-ef5a364d3365.jpeg" width="100px" />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128128/76b9f388-6313-b5a3-f16b-a192ec856326.jpeg" width="100px" />
</p>

<p>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128128/4d4a5240-55fb-e929-8824-168a69a12672.jpeg" width="100px" />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128128/d874df87-a186-ce2e-aee1-19ab401f9f98.jpeg" width="100px" />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128128/7ae50599-eb8a-6cca-2585-e2873aa42479.jpeg" width="100px" />
</p>


# uniformLBPとは
この記事に詳しい説明が載っています。わかりやすい。
今回行う実験は、この記事の延長にあたります。

https://qiita.com/tancoro/items/959ae9c14048c06bea8e

簡単に特徴を言うと、色の明暗に影響せず、形状情報だけを参照して色々できるやつ。相対値を使ってるので、色はほぼ関係ないです。

# 実装
適当な図ですが、だいたいこんな感じ。
![dddd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128128/7f27dbf8-6189-cb8f-a8b6-e9a69b4cbb89.png)


### コード
動作環境はgoogle colab

フォルダから画像を一括で読み込み、ベクトル化する。
```Python
import os
import cv2
import cv2
from skimage import feature
from matplotlib import pyplot as plt
from glob import glob
import os
import numpy as np
import pandas as pd

def lbp(folder, label, save_folder:str|bool = None):
    df = pd.DataFrame()
    filepath_list = glob(f"{folder}/*")
    for index, filepath in enumerate(filepath_list):
        print(index)
        filename = os.path.basename(filepath)
        bgr_img = cv2.imread(filepath)
        gray_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2GRAY)
        gray_img = cv2.resize(gray_img, (width, height))
        # gray_img = cut(gray_img, width, height)
        if (gray_img.shape != (width, height)):
            continue
        # compute the Local Binary Pattern representation of the image
        lbp = feature.local_binary_pattern(gray_img, POINTS, RADIUS, method='uniform')
        lbp_ravel = lbp.ravel()[lbp.ravel() != POINTS+1] #特定の数値削除
        u, counts = np.unique(lbp_ravel, return_counts=True)
        vector = counts / (width*height)
        v = str(vector.tolist())
        df2 = pd.DataFrame({'filename': filename, 'vector': [v], "label": label, "score": 0})
        df = pd.concat([df, df2])

    return df

# フォルダから画像を読み込みながら、256x256にし、ベクトル化する
df_person = lbp(folder="/content/3class/person", label="person")
df_cat = lbp(folder="/content/3class/cat", label="cat")
df_dog = lbp(folder="/content/3class/dog", label="dog")
```

基準(いわゆる学習)データと、テストデータを分ける
```Python
# 基準データとテストデータを分ける

BASIS_RATIO = 0.8
RANDOM_STATE=5

def cos_sim(v1, v2):
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

# print(df_person)

df_shuffled = df_person.sample(frac=1, random_state=RANDOM_STATE)
basis_person = df_shuffled.iloc[:int(BASIS_RATIO * len(df_shuffled))].reset_index(drop=True)
test_person = df_shuffled.iloc[int(BASIS_RATIO * len(df_shuffled)):].reset_index(drop=True)

df_shuffled = df_cat.sample(frac=1, random_state=RANDOM_STATE)
basis_cat = df_shuffled.iloc[:int(BASIS_RATIO * len(df_shuffled))].reset_index(drop=True)
test_cat = df_shuffled.iloc[int(BASIS_RATIO * len(df_shuffled)):].reset_index(drop=True)

df_shuffled = df_dog.sample(frac=1, random_state=RANDOM_STATE)
basis_dog = df_shuffled.iloc[:int(BASIS_RATIO * len(df_shuffled))].reset_index(drop=True)
test_dog = df_shuffled.iloc[int(BASIS_RATIO * len(df_shuffled)):].reset_index(drop=True)

basis = pd.concat([basis_person, basis_cat, basis_dog]).reset_index(drop=True)
test = pd.concat([test_person, test_cat, test_dog]).reset_index(drop=True)

print(basis, test)
```

分類
基準データとcos類似度で近い順20件を考慮
```Python
from sklearn.metrics import confusion_matrix, accuracy_score
import seaborn as sns
import matplotlib.pyplot as plt

# 上位20件でアンサンブル
N=20
y_pred_multi = []

for i, t in test.iterrows():
    print(i)
    for index, row in basis.iterrows():
        basis["score"][index] = cos_sim(eval(row["vector"]), eval(t["vector"]))
    df = basis.sort_values("score", ascending=False).reset_index()
    result = df.head(N)["label"].mode()[0]
    if (t["label"] != result):
        print(t["label"], t["filename"], ">", result)
        # print(df.head(10))

    if(result=="person"):
        y_pred_multi.append(0)
    elif(result=="cat"):
        y_pred_multi.append(1)
    else:
        y_pred_multi.append(2)

test.replace(['person', 'cat', 'dog'], [0, 1, 2], inplace=True)
y_true_multi = test["label"].tolist()

cm = confusion_matrix(y_true_multi, y_pred_multi)
cm = pd.DataFrame(data=cm, index=["person", "cat", "dog"],
                           columns=["person", "cat", "dog"])

print(cm)

sns.heatmap(cm, cmap='Reds', annot=True, annot_kws={'fontsize': 16})
plt.xlabel("予測", fontsize=18)
plt.ylabel("正解", fontsize=18)
plt.savefig('sklearn_confusion_matrix.png')

print(accuracy_score(y_true_multi, y_pred_multi))
```

# 結果と考察
### 結果
正解率: 67.6%！！！微妙！！！！
猫を犬、犬を猫と間違える割合が圧倒的に多い
人間はそれなりにあたってますね
![スクリーンショット 2023-12-21 17.36.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128128/307c0ab7-5551-24a3-40fb-1ec52c62576a.png)


### 考察
犬と猫はいずれも毛皮を持ってます。テクスチャ的にほぼ同じものなので、分けられないのは当然ですね。
被写物以外が多すぎるのも精度が低下した原因。
クラス分類をするのに適しているかはわからないですが、私が学生としてやっている研究においては正解率92%という中々な結果を残してたりします。今回は機械学習を使わずにということでcos類似度でやってますが、ベクトルデータの一つとして、機械学習にぶちこむのはありかもしれない。


# 終わりに
前処理をうまくすれば何かには実用できそう。
テクスチャ解析はもう一個有名なものでGLCMというのがありまして、こっちはセグメンテーションとかできます。本当はこっちを書きたかったけど、結構擦られてるのでいいかなぁって。
研究や仕事に使えそうならぜひ使ってみてください。
ここまでkosakae256が提供しました。明日もお楽しみに。

# 参考文献
https://qiita.com/tancoro/items/959ae9c14048c06bea8e

https://qiita.com/yellow_detteiu/items/58d4b046071ada77594b

# あどかれ

https://adventar.org/calendars/8777