カメラ画像全体から判別対象を認識する

# やりたいこと
以下のテキストでは、AIを用いた画像認識により対象物を分類し、ロボットアームにより仕分けを行っています。
- [DOBOT Magician AIｘ画像認識ｘロボットアーム制御](https://afrel.co.jp/dobot/education-product/)

対象物を判別する手順として、OpenCVを用いてカメラ画像中から対象物を切り出し、それを学習データとしたり、学習したモデルに入れて判別を行っています。
そのため、カメラ画像中から対象物を上手く認識し、切り取ることができるように下敷きを用意したり、細かいカメラの調整等が必要になります。

今回は、カメラ画像から対象物を切り出さずに、カメラ画像1枚を学習データとし、対象物の分類ができるようにプログラムを修正してみたいと思います。

::: note
前提条件
- 修正するプログラムは、テキスト記載のサンプルプログラム
- 対象物を置く位置（＝DOBOTが対象物を拾いに行く座標）は固定
    ※ 切り出しをしない（領域を取得しない）ので対処物のカメラ座標が取得できないため
:::


# できたもの
## 判別対象
今回、判別対象は、2x6のレゴブロックを4つ重ねたものとしました。
重ね方によって正常か否かを判別するように以下のような学習データを集めました。

**正常な形**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145092/cf780162-0a49-c0d9-6e55-b3fe7a1f9d96.png" width=30%>

**正常ではない形**
<p>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145092/63da8c2a-f9d0-48aa-3017-9ec85e912db4.png" width=30%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145092/7675eb19-f157-b7af-2ac3-1528ea932a9a.png" width=30%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145092/6da1fc02-8181-92de-4333-6e112d6508e1.png" width=30%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145092/5f942145-f78b-78eb-888b-2f5ea4f14db6.png" width=30%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145092/00badc33-6fea-6466-fca0-354b9f5e242f.png" width=30%>
</p>


## カメラの調整
カメラ画像全体を学習データとするため、判別対象が画像の中に占める割合が大きくなるように（判別対象が大きく映るように）カメラの位置は低めに設置しました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145092/a84edf37-5828-6484-9e45-7ab6e55ac380.jpeg" width=70%>

## 動作
<p>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145092/76056cf7-2214-971f-f5f3-a0e6ff0f514b.png" width=40%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145092/4f4add94-697d-d215-dacc-caab6f2a05d7.png" width=40%>
</p>
問題なく判別することができました。

<p>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145092/e257b5e3-66ff-9429-b4b1-83869992a780.png" width=40%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145092/c2c31e45-41b4-22d2-d41f-853ba286126e.png" width=40%>
</p>
学習データに含まれていない形も「正常ではない」として判別しました。

<iframe width="560" height="315" src="https://www.youtube.com/embed/efC6FJcLpJE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# プログラム
## 修正後
```python:shoot_trainingData.py
# 教師データの作成（4）
if __name__ == '__main__':

    ～省略～
    
    while True:
        # VideoCaptureから1フレーム読み込む（4-2）
        ret, frame = cap.read()
        # 加工なし画像を表示する
        cv2.imshow('Raw Frame', frame)

        # キー入力を1ms待つ
        k = cv2.waitKey(1)

        # 「ESC（27）」キーを押す
        # プログラムを終了する
        if k == 27:
            break

        # 「C」キーを押す
        # WEBカメラのゲイン値、露出の値を調整する
        elif k == ord('c'):
            g = input("gain     : ")
            e = input("exposure : ")
            print("\n - - - - - - - - - - ")
            camset.camera_set(cv2, cap, gain = float(g), exposure = float(e))
            camset.camera_get(cv2, cap)
            print(" - - - - - - - - - - \n")

       # 画像の保存（4-9）
       # 「S」キーを押す
       # そのまま切り取って画像を保存する
        elif k == ord('s'):
            w_result = True
            nowtime = datetime.now().strftime("_%y%m%d_%H%M%S_")
            # リストに格納された矩形を長辺に合わせてサイズ調整する
            img_src = rect_preprocess(frame)
            # サイズ調整した正方形を画像(png)データで保存する
            capstr = DATA_DIR + CAPTURE_NAME + nowtime + ".png"
            cv2.imwrite(capstr, img_src)
            print(capstr)
            print(" - - - - - - - - - - " + str(1) + " images saved\n")


        # 「A」キーを押す
        # 補正を加えた画像を保存する
        elif k == ord('a'):
            w_result = True
            save_cnt = 0
            nowtime = datetime.now().strftime("_%y%m%d_%H%M%S_")
            # 取得した矩形を長辺に合わせてサイズ調整する
            img_src = rect_preprocess(frame)
            capstr = DATA_DIR + CAPTURE_NAME + nowtime
            # サイズ調整した正方形に補正を加えて保存する
            save_cnt = save_image(img_src, capstr, save_cnt)

            print(" - - - - - - - - - - " + str(save_cnt) + " images saved\n")


        # 「R」キーを押す
        # 画像を回転させた上に補正を加えた画像を保存する
        elif k == ord('r'):
            w_result = True
            save_cnt = 0
            nowtime = datetime.now().strftime("_%y%m%d_%H%M%S_")
            # 取得した矩形を長辺に合わせてサイズ調整する
            img_src = rect_preprocess(frame)
            # 画像の中心位置
            center = tuple(np.array([img_src.shape[1] * 0.5, img_src.shape[0] * 0.5]))
            # 画像サイズの取得(横, 縦)
            size = tuple(np.array([img_src.shape[1], img_src.shape[0]]))

            # リストに格納された長方形を画像(png)データで保存
            # 回転（0°, 90°, 180°, 270°）して、変換処理した画像を保存
            for j in range(0, 4):
                rot = 90 * j
                # 回転変換行列の算出
                rotation_matrix = cv2.getRotationMatrix2D(center, angle=rot, scale=1.0)
                # アフィン変換
                rot_img = cv2.warpAffine(img_src, rotation_matrix, size, flags=cv2.INTER_CUBIC)

                capstr = DATA_DIR + CAPTURE_NAME + nowtime + "_rot" + str(rot)
                save_cnt = save_image(rot_img, capstr, save_cnt)

            print(" - - - - - - - - - - " + str(save_cnt) + " images saved\n")


    # キャプチャをリリースして、ウィンドウをすべて閉じる
    cap.release()
    cv2.destroyAllWindows()
```

```python:classifier.py
import os
import sys, time
from datetime import datetime
from argparse import ArgumentParser

from tensorflow.keras.models import model_from_json
from tensorflow.keras.preprocessing.image import img_to_array, load_img
import cv2
import numpy as np

import cameraSetting as camset
import dobotClassifier as dc

from common import *
# from TransformationMatrix import MATRIX
# MATRIX = np.array(MATRIX)

～省略～

# 学習済みモデルを使って仕分ける（3）
if __name__ == '__main__':

    ～省略～

    while True:
        # VideoCaptureから1フレーム読み込む（3-3）
        ret, frame = cap.read()
        ret, edframe = cap.read()
        # 加工なし画像を表示する
        cv2.imshow('Raw Frame', frame)

        img_src = rect_preprocess(frame)

        # リサイズする
        image = cv2.resize(img_src, dsize=(PIC_SIZE, PIC_SIZE))
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        image = np.asarray(image)
        # image_list.append(image)

        # 画像の前処理（3-9）
        image = img_to_array(image)
        image = image.astype("float32") / 255.
        image = image[None, ...]

        # 分類する（3-10）
        result = model.predict_classes(image)
        proba  = model.predict_proba(image)
        result_num = int(result[0])

        # ラベルを表示する（3-13）
        label = str(result_num) + " " + LabelName[result_num]
        # cv2.rectangle(edframe, (x, y-15), (x+len(label)*10, y), draw_white, -1, cv2.LINE_AA)
        x = 20
        y = 20
        cv2.putText(edframe, label, (x, y), font, FONT_SIZE, draw_black, FONT_WIDTH, cv2.LINE_AA)

        # 「P」キーが押されたときの処理（3-14）
        if proba_flag == True:
            # # 回転を考慮した外接矩形を表示
            # cv2.drawContours(edframe, [box], 0, draw_red, 1)

            # 確率を表示
            for n in LabelName:
                cnt = getattr(LabelNumber, n)
                proba_str = "[{:<6}] {:>5.2f}%".format(n, proba[0, cnt] * 100.)
                if cnt == np.argmax(proba):
                    cv2.putText(edframe, proba_str, (x+5, y+30+(20*cnt)), font, FONT_SIZE, draw_red, FONT_WIDTH, cv2.LINE_AA)
                else:
                    cv2.putText(edframe, proba_str, (x+5, y+30+(20*cnt)), font, FONT_SIZE, draw_black, FONT_WIDTH, cv2.LINE_AA)

        # 描画した画像を表示
        cv2.imshow('Edited Frame', edframe)


        # キー入力を1ms待つ
        k = cv2.waitKey(1)

        # 「ESC（27）」キーを押す
        # プログラムを終了する
        if k == 27:
            break

        # 「C」キーを押す
        # WEBカメラのゲイン値、露出の値を調整する
        elif k == ord('c'):
            g = input("gain     : ")
            e = input("exposure : ")
            print("\n - - - - - - - - - - ")
            camset.camera_set(cv2, cap, gain = float(g), exposure = float(e))
            camset.camera_get(cv2, cap)
            print(" - - - - - - - - - - \n")

        # 「P」キーを押す
        # 各ラベルの確率を画面上に表示する／再度押すと消える
        elif k == ord('p'):
            proba_flag = not(proba_flag)

        # 「H」キーを押す
        # DOBOTをホームポジションに移動させる（位置リセット）
        elif k == ord('h'):
            dc.move_home()

        # DOBOTで仕分け（3-17）
        # 「S」キーを押す
        # 最後に取得した矩形とその結果を元にDOBOTでピックアップする
        elif k == ord('s'):
            transform_pos = [200, 0]
            print(str(result_num) + " " + LabelName[result_num] + "  -  " + str(transform_pos))
            while dc.dobot_classifier(result_num, 200, 0) != True:
                pass

    # 終了処理（3-18）
    # DOBOTの終了処理
    dc.finalize()
    # キャプチャをリリースして、ウィンドウをすべて閉じる
    cap.release()
    cv2.destroyAllWindows()
```