[Python] WEBカメラ物体認識とRGB値取得プログラムを作成した[OpenCV]

PythonでWEBカメラを操作して物体の認識とその物体のRGB値を取得するプログラムを開発しました。
何を考えてどう作ったのか、というところを備忘録として記事を書きました。

Pythonを勉強し始めて約半年の人間の作成物ですので、まだ改良の余地はたくさんです。
これからどんどん進化させて行きたいと思っています。

<h2>背景</h2>
現在、私はメーカーの工場で技術開発職として勤務をしています。
工場現場における製品の工程設計・不良率削減・作業効率化を主な業務としています。

工場現場では、工程設計時に「流石に発生しないだろう」というようなトラブルが発生することがあります。
不良製品を出荷することがないよう内部で食い止めるために最新の注意を払っていますが、それでも流出してクレームになるということがあります。

その度に恒久対策としてシステムの導入を試みます。
しかし、「設計から着手までの期間が長い」「ケースがレアすぎるために導入しても価格と効果の採算が取れない」といったような理由からすぐに導入ができないという問題があります。

そのような場合に、自分たちで少しプログラミングして簡単なシステムを作ることができれば、すぐに導入できるし導入コストもほぼ０で達成できるのではないかと考え、導入できるかはさておき自分で作ってみようとなりました。

<h2>今回作成したもの</h2>
今回作成したものは、印刷物のプリント確認です。
詳細は避けますが、簡単に説明します。

プリンターで紙に絵や柄を印刷します。連続で印刷するので、排紙部には印刷された紙が溜まります。
作業者はその紙を目視で確認し、印刷されていることを確認しますが見落としにより印刷されていない白紙が混ざってしまう場合があります。

その見落としを防ぐために、WEBカメラを使用して確認を行いたいと考えました。

排紙部に紙が出てきたことをWEBカメラが検知してその紙を撮影、紙の4つの頂点を検知してその中点の周りのエリアの平均のRGB値を取得します。

そのRGB値とプリント前の白紙のRGB値の値を比較して、差がある(プリントされている)かどうかを確認します。

差がない(プリントされていない)場合に、ログに「○枚目の紙がプリントされていない可能性があります。」と表示させることで、出荷前に確認することができるようになるために、流出を避けることができる。

という簡易的な自動目視確認プログラムを作成してみました。

<h2>コード</h2>
コードは<a href="https://github.com/wagochan005/web_camera">こちら</a>にアップしています。
適宜リファクタリングする予定です。

<h2>主な関数の内容</h2>
環境は以下のとおりです。
・Python 3.9.6
・Opencv 4.0.1
※anaconda使用

####ライブラリ
OpenCVを利用する時に利用したライブラリは以下です。(他はGitHubを参照ください)
```
import cv2
```


#### WEBカメラの動作
```
# カメラキャプチャの開始
    cap = cv2.VideoCapture(0)

# イメージを定義
    img = cap.read() # カメラキャプチャのウィンドウ表示
    img = cv2.resize(img, (600, 400)) # ウィンドウフレームのサイズを扱いやすいサイズに変更

#ここはカメラのフレームサイズ、FTP値によって変更する
    cap.set(cv2.CAP_PROP_FPS, 30)           # カメラFPSを設定
    cap.set(cv2.CAP_PROP_FRAME_WIDTH,3840) # カメラ画像の横幅を設定3840
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 2160) # カメラ画像の縦幅を設定2160

# カメラで移されている映像を表示する
    cv2.imshow("test", img) # 第１引数はウィンドウ名、第２引数は画像の名前

# カメラの解放
    cv2.waitKey(0) # 何かしらのキーを押すと次へ進む
    cv2.destroyAllWindows() # ウィンドウ表示を終了
    cap.release() # カメラの解放
```

#### 画像の処理
```
# 撮影画像の保存
    cv2.imwrite("ファイル名", img)

# 画像の読み込み
    image = cv2.imread()

# 画像のグレースケール化
    img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 画像をCanny法により、エッジを検出できる形にする
    threshold1 = 100
    threshold2 = 200
    edge = cv2.Canny(img_gray, threshold1, threshold2)

# 膨張処理　境界線を太くする
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
    dilates = cv2.dilate(edge, kernel)

# 輪郭の検出
    contours, hierarchy = cv2.findContours(dilates, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 輪郭の見つける
    
# 小さい輪郭は削除(検出するものは比較的大きいため、小さい物体を検出しないようにする)
    contours = list(filter(lambda x: cv2.contourArea(x) > 10000, contours)) #cv2.contourAreaで輪郭の大きさを算出している
    
# 座標を抽出
    for contour in contours:
        x, y, w, h = cv2.boundingRect(contour)
        print(x, y, w, h)

# 輪郭の描画
    cv2.drawContours(image, contours, -1, color=(255, 255, 255), thickness=2)

# 4つの頂点をマーキング
    cv2.circle(img=image, center=(x, y), radius=10, color=(255, 255, 255), thickness=2)
    cv2.circle(img=image, center=(x + w, y), radius=10, color=(255, 255, 255), thickness=2)
    cv2.circle(img=image, center=(x, y + h), radius=10, color=(255, 255, 255), thickness=2)
    cv2.circle(img=image, center=(x + w, y + h), radius=10, color=(255, 255, 255), thickness=2)

# 4つの頂点から中点を算出する
    center_x = int(x + (w / 2))
    center_y = int(y + (h / 2))

# 中点回りの範囲の色情報を取得するためのBOXを作る(描画)
    boxFromX = center_x - 50
    boxFromY = center_y - 50
    boxToX = center_x + 50
    boxToY = center_y + 50
    imgBox = image[boxFromY : boxToY, boxFromX : boxToX]

    cv2.rectangle(image, (boxFromX, boxFromY), (boxToX, boxToY), (255, 255, 255), thickness=1, lineType=cv2.LINE_8, shift=0)

# Box内のRGB値を出力する
# flattenで一元化してmeanで平均を取得
    b = imgBox.T[0].flatten().mean()
    g = imgBox.T[1].flatten().mean()
    r = imgBox.T[2].flatten().mean()

# RGB値を表示する
    print("R : " + str(int(r)))
    print("G : " + str(int(g)))
    print("B : " + str(int(b)))
    RGB = [str(int(r)), str(int(g)), str(int(b))]
    
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# 画像上に検出した頂点と輪郭と四角を
    plt.imshow(image, cmap = "gray")  

```

## 一部の機能を解説

### Canny処理
Canny処理とは、グレースケール化した画像のエッジを検出する方法
threshold1,threshold2の値を大きくすると、初期検出に有効である。


### 膨張処理
dilateを使用して、Canny処理で出したエッジを太くすることができる。

# 処理の動作を紹介

まずは、写真を撮影
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1857676/a3eedd05-706b-0791-abae-5e417ac494ce.png)

Canny処理にて、物体のエッジを抽出
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1857676/f7ca22c4-66e6-ba5f-81d4-2d1a50adf35e.png)

Canny処理にて検出した物体のエッジをdilkate太くする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1857676/8b22995c-1316-702f-ca09-94a60a3191fb.png)

元の画像に輪郭、頂点、BoXを表示し、BoX内のRGB値を出力する(中の◯はテストで入れただけなので気にしないでください。)
左上の頂点の座標、幅、高さも算出することにより、中点等を算出している。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1857676/4dc55019-e3ba-c526-459c-89ea4566b7c8.png)

### まとめ
このような処理を行うことで、どのような物体でもある程度検出して、色の取得が可能となりました。

連続で印刷される紙の場合は、カメラの視界で動きを検出し、視界の映像が大きく動いたときに画像を保存するような運用にしています。
これにより、排紙を検出することが可能になりました。

また、１番最初に基準の色を格納し、その基準色と比べて排紙された紙の色と変化があるかどうかをif文で確認するようにしています。

１枚毎にRGB値をLogに記録しており、色の変化がないときにはそのLogに「色の変化がない」という文言を追記することで、最後にログを確認して印刷されていない紙がないかどうかを確認することができます。

以上、初めての開発でした(^^)