もよいめも

不定期更新ものづくりブログ

【画像認識】OpenCV初心者がネジ識別プログラムを作ってみた

今回は部活でのうっとうしいネジ分けから解放されるべく第一歩として、OpenCVで「ネジ識別プログラム」を作ってみたので、それについて書いていこうと思います。

開発環境

今回はanaconda3を入れるといっしょについてくるSpyderという開発環境にOpenCVを入れてプログラムしました。

spyderの画面
spyder
自動整形がないのが残念ですが割と使いやすかったです。

プログラム

とりあえず今回作った(大半コピペ)プログラムを張っておきます。しかし、初めてのOpenCV という事もあり、しっかり覚えておきたいので今回は細かく説明していこうと思います。そのため、プログラム中のコメント等はすべて削除しています。
↓今回のプログラム

import cv2
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon

cam = cv2.VideoCapture(1)
_,img = cam.read()

lookUpTable = np.empty((1,256), np.uint8)
for i in range(256):
    lookUpTable[0,i] = np.clip(pow(i / 255.0, 0.5) * 255.0, 0, 255)
img_gamma = cv2.LUT(img, lookUpTable)

img_gray = cv2.cvtColor(img_gamma, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (5,5), 0)
_, img_binary= cv2.threshold(img_gray, 130, 255, cv2.THRESH_BINARY_INV)

contours = cv2.findContours(img_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]

def draw_contours(ax, img, contours):       
    ax.imshow(img)
    ax.axis('off')
    for i, cnt in enumerate(contours):
        cnt = np.squeeze(cnt, axis=1)
        ax.add_patch(Polygon(cnt, color='b', fill=None, lw=2))
        ax.plot(cnt[:, 0], cnt[:, 1], 'ro', mew=0, ms=2)
        ax.text(cnt[0][0], cnt[0][1], i, color='b', size='20')

fig, ax = plt.subplots(figsize=(6, 6))
draw_contours(ax, img_gamma, contours)

for i, cnt in enumerate(contours):
    rect = cv2.minAreaRect(cnt)
    (cx, cy), (width, height), angle = rect
    rect_points = cv2.boxPoints(rect)
    ax.add_patch(Polygon(rect_points, color='g', fill=None, lw=2))
    
    if width<height:
        kei=int(width)
        nag=int(height)
    else:
        kei=int(height)
        nag=int(width)
    print('番号:',i)
    print('縦:',kei,'横',nag)       
    L = 0
    if 44<=kei<=52:
        M = 3
    elif 55<=kei<=65:
        M = 4
    else:
        M = 0
    if M == 3 and 100>nag>80 or M == 4 and 110>nag>100:
        L = 8
    elif M == 3 and 78 >= nag >= 67 or M == 4 and 100 >= nag > 70:
        L = 5
    if M == 0:
        print('認識できませんでした')
    else:
        print('径:','M',M)
    if L == 0:
        nag = 11*nag/100-3
        L = int((nag + 4) / 5) * 5
    if M != 0:
        print('長さ:',L,'mm')

plt.show()

画像認識用のカメラとライトについて

プログラム解説の前に画像認識用のカメラとライトについて説明します。
今回は友人から借りたウェブカメラを使って画像認識していきます。
画像認識の方法より、白と黒のコントラストが重要になってくるため、一定の明るさで画像を撮る必要があります。そのため、簡単な撮影用の小道具を作りましたので紹介します。

撮影用の台
撮影用の何か
こんな感じでペットボトル飲料の上下を切りとってそこに蓋をしてハイパワーLEDと、穴をあけてウェブカメラをつけました。
撮影用の台の内部
撮影用の何かの内部
ちなみにLEDはUSBのバスパワーでつけています。
LED電源及びウェブカメラのUSB
LED電源及びウェブカメラのUSB

プログラム解説

では、1行目から解説していきます。

import cv2
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon

ここは、ただライブラリ等を入れているだけなので省略


#VideoCapture オブジェクトを取得
cam = cv2.VideoCapture(1)
#一コマ分の画像を取得してimgに代入
_,img = cam.read()

cv2.VideoCapture(1)の()の中の数字ですが、0だとパソコンの内臓カメラの画像になったので、おそらく指定するカメラの番号だと思います。


#ガンマ処理を行って画像を明るく
lookUpTable = np.empty((1,256), np.uint8)
for i in range(256):
    lookUpTable[0,i] = np.clip(pow(i / 255.0, 0.5) * 255.0, 0, 255)
img_gamma = cv2.LUT(img, lookUpTable)

撮影した画像は私の使っているウェブカメラだと少し画像が暗いのでガンマ処理を行って明るくします。
どの程度明るくするかは、lookUpTable[0,i] = np.clip(pow(i / 255.0, 0.5) * 255.0, 0, 255)の「0.5」の部分を変更することで行えます。


#画像を白黒画像にする
img_gray = cv2.cvtColor(img_gamma, cv2.COLOR_BGR2GRAY)
#画像を少しぼかす
img_blur = cv2.GaussianBlur(img_gray, (5,5), 0)
#画像を完全な白と黒の画像(二値画像)に
_, img= cv2.threshold(img_blur, 130, 255, cv2.THRESH_BINARY_INV)

画像認識の下処理として画像を完全な白と黒の画像にしなければなりません。さらにその下処理としてまずはカラー画像を白黒(グレーも含む)にします。
その後、細かいノイズを処理するために画像をぼかします。ちなみにぼかし具合はimg_blur = cv2.GaussianBlur(img_gray, (5,5), 0)の「(5,5)」の値を変えることで変更できます。
そして、最後に画像を完全な白と黒の画像(二値画像)にして下処理は終了です。


#二値画像から輪郭を検出
contours = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]

画像imgの黒い背景から白い物の輪郭を検出します。
検出した輪郭はcontoursにリストとして代入されます。


fig, ax = plt.subplots(figsize=(6, 6))    
ax.imshow(img_gamma)
ax.axis('off')
for i, cnt in enumerate(contours):
    cnt = np.squeeze(cnt, axis=1)
    # 輪郭の点同士を結ぶ線を描画する。
    ax.add_patch(Polygon(cnt, color='b', fill=None, lw=2))
    # 輪郭の点を描画する。
    ax.plot(cnt[:, 0], cnt[:, 1], 'ro', mew=0, ms=2)
    # 輪郭の番号を描画する。
    ax.text(cnt[0][0], cnt[0][1], i, color='b', size='20')

img_gammaの上に輪郭の線を書きます。
これと、この下のプログラムは参考サイトのほとんどそのままなので、詳しい説明はそちらのほうを見てください。


for i, cnt in enumerate(contours):
    # 輪郭に外接する回転した長方形を取得する。
    rect = cv2.minAreaRect(cnt)
    (cx, cy), (width, height), angle = rect
    # 回転した長方形の4点の座標を取得する。
    rect_points = cv2.boxPoints(rect)
    # 回転した長方形を描画する。
    ax.add_patch(Polygon(rect_points, color='g', fill=None, lw=2))

ネジを囲む長方形も描画します。


#取得した長方形の縦横の長さを比べて、長いほうをnag,短いほうをkeiに代入
 if width<height:
        kei=int(width)
        nag=int(height)
    else:
        kei=int(height)
        nag=int(width)
 #取得した長方形の番号をコンソール画面に表示します。
    print('番号:',i)
 #取得した長方形の縦横の長さをコンソール画面に表示します。
    print('縦:',kei,'横',nag)       
 #取得した縦横の値をからネジの径や長さを割り出します。
    L = 0
    if 44<=kei<=52:
        M = 3
    elif 55<=kei<=65:
        M = 4
    else:
        M = 0
    if M == 3 and 100>nag>80 or M == 4 and 110>nag>100:
        L = 8
    elif M == 3 and 78 >= nag >= 67 or M == 4 and 100 >= nag > 70:
        L = 5
    if M == 0:
        print('認識できませんでした')
    else:
        print('径:','M',M)
    if L == 0:
        nag = 11*nag/100-3
        L = int((nag + 4) / 5) * 5
    if M != 0:
        print('長さ:',L,'mm')

ここは完全オリジナルのプログラムで、
ひたすら画像認識させて縦横の長さの傾向を調べ
そこからぶりぶり計算してネジの径や長さを割り出せるようにしました。


#画像を表示します。
plt.show()

ここで画像を表示します。

感想とか

今回、初めて画像認識をしてみましたが、OpenCV が便利すぎて正直めちゃくちゃ楽だったです。
私の中で画像認識ブームが到来してきました。

今現在、画像認識した結果をArduinoを間に挟んでLCDに表示しようとしているのですが、こっちのほうが難しいですね。
LCD表示できるようになったらまた記事を書くので、どうぞお待ちください。