SVMを使ってりんごとばななの画像を分類してみたかった話

## はじめに
はじめまして！新人未経験エンジニアのやっさんです:girl_tone1:

タイトル紛らわしいですが、**今回はサポートベクターマシン(SVM)の分類はしません**！りんごとばななの画像を収集して、画像のRGB値を特徴量としてSVMで分類してみたかったのですが、スクレイピングした画像に対象物以外が多く混入していたので断念しました💦
今回はSVMでの分類をするための前段階までやりました！本記事では、データの収集と前処理、データの可視化までを行います👀

1.画像収集
Google Colaboratoryでりんご:apple:とばなな:banana:をキーワードに画像をそれぞれ50-60枚スクレイピング
2.RGB値測定
スクレイピングした画像をImageJでRGB測定を行う
3. データ整形
エクセルのマクロでRGB測定の結果のデータを整形
4.データの可視化
RでRGB値をもとに3Dマッピングをして可視化

## 画像収集
Google Colaboratoryでりんご:apple:とばなな:banana:をキーワードに画像をそれぞれ50-60枚スクレイピングします！
まずは必要なライブラリをインストールします。
```Colaboratory:
!pip install icrawler
```
次に以下のコードを追加し、画像を収集します。下記コードはばななをキーワードに収集しています。キーワードをりんごに変更してりんごも同様に収集します。
```Colaboratory:
from icrawler.builtin import BingImageCrawler
crawler = BingImageCrawler(storage = {'root_dir' : './bnimage'})
crawler.crawl(keyword = 'ばなな', max_num = 60)
```
ローカルに、収集した画像を保存していきます。
まずは、Google ColaboでGoogle Driveをマウントをして
```Colaboratory:
!ls '/content/drive/My Drive'
```
Google Colab上の収集画像ファイルをGoogle Driveの指定した場所にコピーします。
```Colaboratory:
import os
# google colaboratory上の画像収集directory名
images_dir = 'bnimage'
# google drive上のdirectory名。colab_work_tmp（名前は任意）の下にdirectory を作成して作業したい。
gdir = '/content/drive/My Drive/apimages/' + images_dir
# ディレクトリ作成
if not os.path.exists(gdir):
    os.makedirs(gdir)
# images_dir  --> gdir　にコピー
from distutils.dir_util import copy_tree
copy_tree(images_dir, gdir)
```
上で指定した場所からダウンロードすることで、エクスプローラーに保存できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3808091/e71a1055-1163-2608-68a2-2e58f2e353eb.png)

## RGB値測定
スクレイピングした画像をImageJでRGB測定を行います。
ImageJのマクロを使って一気にRGB測定を行います。
私が用いたImageJのバージョンは1.54dです。
ImageJのメニューバー＞Plugins＞New＞MacroでMacro.ijmを開き、下記コードを追加します。
forループの中でSaveするやり方ではじめコードを作成していたのですが、前の画像の測定結果はリセットされずに結果が追加されていく形だったので、一番最後の画像の測定が終わった後に結果を保存すればすべての画像の測定結果が保存されると考えてfor分の外に出しています。
```ImageJ:Macro.ijm
//フォルダパスの指定
store_path = "C:/Users/..../Downloads/result-appleimages/";
inputdir = "C:/Users/..../Downloads/aimage-20240625T065514Z-001/appleimages";
// フォルダ内のファイルリストを取得
filelist = getFileList(inputdir);
// 処理対象の各画像についてループ
for (x = 0; x < filelist.length; x++) {
    // ファイル名を取得
    title = filelist[x];
    // 画像を開く
    open(inputdir + "/" + title);
    // RGB測定を実行
    run("RGB Measure");
    // 画像ウィンドウを閉じる
    close();
}
    // 結果をテキストファイルとして保存 
    saveAs("Measurements", store_path + "RGB_Measure_" + title + ".txt");
```

次のようにRGB値の測定結果がテキストファイルで得られます。(画像はExcelで開いたとき)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3808091/e1810bb9-63aa-2c2b-387c-ab92a881efae.png" width=50%>


## データの整形
ExcelのVBAでRGB測定の結果のデータを下の画像のように整形します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3808091/c3f38e47-2bf3-2bb0-5d86-97701f6f001b.png" width=50%>

測定結果の内容をコピーしてExcelに貼り付け、Altキー＋F11でMicrosoft Visual Basic Editorを起動させ、下記コードを追加します。
```VBA:
Sub FormatData()
    Dim wsSource As Worksheet
    Dim wsOutput As Worksheet
    Dim lastRow As Long, i As Long
    Dim labelValue As String
    Dim redValue As Double, greenValue As Double, blueValue As Double, meanValue As Double
    Dim outputRow As Long
    ' ソースデータを含むシートを参照
    Set wsSource = ThisWorkbook.Sheets("Sheet1")  ' データがあるシート名に変更してください
    ' 出力用の新しいシートを作成
    Set wsOutput = ThisWorkbook.Sheets.Add
    wsOutput.Name = "FormattedData"  ' 出力用シートの名前を設定
    ' ヘッダーを設定
    wsOutput.Cells(1, 1).Value = "Red"
    wsOutput.Cells(1, 2).Value = "Green"
    wsOutput.Cells(1, 3).Value = "Blue"
    wsOutput.Cells(1, 4).Value = "(R+G+B)/3"
    ' 出力用シートの行数カウンター初期化
    outputRow = 2
    ' データをコピーして処理
    lastRow = wsSource.Cells(wsSource.Rows.Count, "A").End(xlUp).Row
    ' データを行ごとに処理
    For i = 2 To lastRow Step 5
        ' Label の値を取得（1つ目の行のLabelを基準にする）
        labelValue = wsSource.Cells(i, 1).Value
        ' Red, Green, Blue, Mean の値を取得
        redValue = wsSource.Cells(i, 3).Value
       greenValue = wsSource.Cells(i + 1, 3).Value
        blueValue = wsSource.Cells(i + 2, 3).Value
        meanValue = wsSource.Cells(i + 3, 3).Value
        ' 出力用シートにデータを書き込む
        wsOutput.Cells(outputRow, 1).Value = redValue
        wsOutput.Cells(outputRow, 2).Value = greenValue
        wsOutput.Cells(outputRow, 3).Value = blueValue
        wsOutput.Cells(outputRow, 4).Value = meanValue
        ' 次の行に移動
        outputRow = outputRow + 1
    Next i
    ' 余分な列を削除（必要に応じて）
    wsOutput.Columns("E:IV").Delete
    ' 処理完了メッセージ
    MsgBox "データの整形が完了しました。"
End Sub
```
データ整形後のりんごのRGB測定結果とばななのRGB測定結果を一つにまとめ、ラベル(apple,banana)を追加します(csvファイルで保存)。


## データの可視化
Rを使って、RGB値のデータで3Dプロットして、ラベル(apple,banana)ごとにプロットに色を付け、それをGIFアニメーションとして保存します。
私が用いたRのバージョンは4.3.0です。

```R:R Script
# rglパッケージをロード
library(rgl)
# りんごとばななのRGB測定結果を一つにまとめてラベルを追加したファイル名を指定
file_name <- "all-image.csv"
# データを読み込む
Data <- read.csv(file_name)
# グループ(りんごとばなな)でデータを分割する
groups <- split(Data, Data$label)
# グループごとにRGB値をプロットする
for (group in names(groups)) {
  group_data <- groups[[group]]
  # R, G, B値を取得
  R <- group_data$R
  G <- group_data$G
  B <- group_data$B
  # 色を設定
  if (group == "apple") {
    color <- "red"
  } else if (group == "banana") {
    color <- "blue"
  }
  # 3Dプロットを作成
  plot3d(R,G,B, xlab = "R intensity", ylab = "G intensity", zlab = "B intensity",
         xlim = c(0, 255), ylim = c(0, 255), zlim = c(0, 255), size = 5, col = color)
}
# グラフのウィンドウサイズを調整
par3d(windowRect = c(20, 40, 600, 620))
# 処理完了のメッセージを表示
message("3D map was created.")
#ここまででWorking Directoryを設定した状態で実行
---------------------------------
#3D mapができたことを確認してから以下を実行
# `animation()`関数の定義
animation <- function(name = "3D-animation"){
  dir <- getwd()  # 現在の作業ディレクトリを取得
  path <- paste(dir, name, sep = "/")  # 保存するGIFアニメーションのファイルパスを指定
  movie3d(spin3d(), fps = 10, duration = 10, movie = path)  # 3Dグラフィックを回転させながらアニメーションを作成し、指定したファイルパスに保存
  message("GIF animation was created.")  # 完了メッセージを出力
  message("Stored to...")
  message(dir)
}
# `animation()`関数の呼び出し
animation()

```
今回はりんごを赤で、ばななを青でプロットしました。
[3Dプロットアニメーション](https://media.discordapp.net/attachments/1199627489702596659/1257488759574892616/3D-animation.gif?ex=6684972b&is=668345ab&hm=d8a2b69602b4d6d13578d94776f5576e068457cfe21a78f957622cd4f891273f&)

**画像**

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3808091/d0606f5d-967a-908b-ead3-bc6c74c54186.jpeg" width=40%>  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3808091/cf62040c-28b8-f7a7-d2ba-666822dd3a62.jpeg" width=40%>


## コメント
思っていたよりも分布に差がなくて驚きました。これだとうまくSVMで分類できそうにないですね👀:sob:
りんごはR値にもっと寄ってほしいですし、ばななはB値から完全に離れてほしかったです。
スクレイピングした画像をみてみると、りんごやばなな以外が移っている写真が多く入っていました。りんごとばななはキーワード検索だと厳しいですね。りんごは、カットされたものや、アップルパイ、青りんごなどが混入していました。ばななだと、東京ばななとか、チョコバナナ、熟れてないばなななどが混入していました。対象物ではなく、画像全体でRGB値取得したことも、原因の一つにありそうです。

スクレイピングする際のキーワードを工夫することで、このやり方でもSVMで分類できるのではないかと考えているのですが、何かいい案がありましたら、コメントいただけると幸いです💦

 ひまわりと桜とか、炎色反応とか、BTB溶液の酸性、中性、アルカリ性とか...👀