カメラの画像から線検出
概要
カメラで撮った画像から直線状のものが正しい位置にあるかどうかを判定したく、 まずPythonで直線を検出するプログラムを作成しました。
これに加え、線が目的の位置にあるかを判定する機能を作れば、目的を果たせそうです。
使用する検出法により特色があります。以下4つを切り替えられるようにしました。
- ハフ変換
- 確率的ハフ変換
- LSD
- FLD
実行イメージ
ウェブカメラで撮影した画像に、検知した直線を赤線で描画します。
操作方法
- z: 次の検出方法に変更
- a: 検出する線の最小長を増やす(検出条件を厳しくする)
- s: 検出する線の最小長を減らす(検出条件をゆるくする)
- ESC: 終了する
作成方法
必要ライブラリ
pipを使ったインストールコマンドです。
pip install opencv-contrib-python pip install pylsd-nova pip install numpy
画面描画やハフ変換・確率的ハフ変換・FLDを使用するためにOpenCVを導入します。opencv-contrib-python
ではFLDでエラーが出たので、extraが含まれるopencv-contrib-python
とします。
LSD用にPyLSDを導入します。pylsd
だとうまく行かない?よくわかりませんがフォークの一つであるpylsd-nova
ならうまくいきました。
ソース
from cv2 import cv2 import math import numpy as np from pylsd.lsd import lsd cap = cv2.VideoCapture(0) fgbg = cv2.createBackgroundSubtractorMOG2() # 確率的ハフ変換 def detect_lines_hough_p(img): # グレイスケールに変換 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 線検出 edges = cv2.Canny(gray, 50, 150, apertureSize=3) lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi/360, threshold=50, minLineLength=50, maxLineGap=10) return [l[0] for l in lines] # ハフ変換 def detect_lines_hough(img): ret_lines = [] # グレイスケールに変換 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) canny = cv2.Canny(gray, 50, 150, apertureSize=3) # 線検出 lines = cv2.HoughLines(canny, 1, np.pi/180, 200) if lines is not None: for line in lines: for rho, theta in line: x1 = np.cos(theta)*rho - 1000*np.sin(theta) y1 = np.sin(theta)*rho + 1000*np.cos(theta) x2 = np.cos(theta)*rho + 1000*np.sin(theta) y2 = np.sin(theta)*rho - 1000*np.cos(theta) ret_lines.append((x1, y1, x2, y2)) return ret_lines else: return [] # LSD # use pylsd-nova def detect_lines_lsd(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) linesL = lsd(gray) lines = [(x1, y1, x2, y2) for (x1, y1, x2, y2, width) in linesL] return lines # FLD def detect_lines_fld(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # FLDインスタンス生成 length_threshold = 4 # 10 distance_threshold = 1.41421356 canny_th1 = 50.0 canny_th2 = 50.0 canny_aperture_size = 3 do_merge = False # 線検出 fld = cv2.ximgproc.createFastLineDetector(length_threshold,distance_threshold, canny_th1,canny_th2,canny_aperture_size,do_merge) # ライン取得 lines = fld.detect(gray) return [l[0] for l in lines] def main(): # 線検出タイプ detect_lines_types = [ ('FLD', detect_lines_fld ), ('LSD', detect_lines_lsd ), ('HoughLines', detect_lines_hough), ('HoughLinesP', detect_lines_hough_p), ] # 選択中線検出タイプ番号 selection = 0 width = 640 height = 480 # 線マスクの色 mask_color = (0, 0, 255) # 線の最小長さ min_len = 000 while True: ret, frame = cap.read() img = cv2.resize(frame, (width, height)) if detect_lines_types[selection][1] is not None: lines = detect_lines_types[selection][1](frame) # 255で埋まった3次元配列(縦x横xRGB3次元)を生成 mask = np.full((height, width, 3), 255, np.uint8) # 線の数 cnt = 0 for line in lines: x1, y1, x2, y2 = line # マスクに赤線を引く if (x1 - x2) ** 2 + (y1 - y2) ** 2 >= min_len ** 2: mask = cv2.line(mask, (int(x1), int(y1)), (int(x2), int(y2)), mask_color, 3) cnt += 1 # マスクかけ masked_img = cv2.bitwise_and(img, mask) # 文字描画 black = (0, 0, 0) white = (255, 255, 255) text1 = f'{selection+1}/{len(detect_lines_types)} {detect_lines_types[selection][0]}' img_with_text = cv2.putText(masked_img, text1, (1, 31), 0, 1, black) img_with_text = cv2.putText(masked_img, text1, (0, 30), 0, 1, white) img_with_text = cv2.putText(masked_img, f'line num: {cnt}, min length: {min_len}', (1, 61), 0, 1, black) img_with_text = cv2.putText(masked_img, f'line num: {cnt}, min length: {min_len}', (0, 60), 0, 1, white) img_with_text = cv2.putText(masked_img, f'z: next detection type, a: inc min line length, s:dec min line length, ESC: exit', (1, height - 20 + 1), 0, 0.5, black) img_with_text = cv2.putText(masked_img, f'z: next detection type, a: inc min line length, s:dec min line length, ESC: exit', (0, height - 20), 0, 0.5, white) cv2.imshow('masked', img_with_text) k = cv2.waitKey(1) if k == 27: break elif k == ord('z'): selection = (selection + 1) % len(detect_lines_types) elif k == ord('a'): min_len += 20 elif k == ord('s'): min_len = max(min_len - 20, 0) cap.release() cv2.destroyAllWindows() if __name__ == "__main__": main()
簡単な解説
関数detect_*
にカメラで撮影した画像を渡すと、検出した線の情報を(x1, y1, x2, y2)のタプルのリストとして返却します。
確率的ハフ変換、LSD、FLDは線分として検出し、ハフ変換は直線として検出するようです。
線分で検出するものは短い線も検出されてしまうので、ある程度の長さ以上でフィルタを掛けられるようmin_len
を用意しています。
終わりに
検出自体はほとんどライブラリ機能を呼び出しているだけなので、シンプルに実装できました。 ただしこのままだと過剰な検出がされているので、この情報から不要な情報の除外やパラメタ調整などをして使える情報に加工することが必要そうです。