PythonとOpenCVで車線検出
PythonとOpenCVで、車線を検出するプログラムを作成しました。チョー単純なロジックなので、条件が想定外になると検出できないんですが、ある程度検出できています。
映像から1フレームの画像を取り出し、グレースケールに変換してからヒストグラムを平坦化します。空の部分は不要なので消去します。それとは別に、フレームの画像をHSVに変換してから一定の範囲の黄色だけを抽出します。これは、白線の検出用にグレースケールに変換してしまうと、黄色がわからなくなるため、別途黄色だけ抽出する、ということです。
抽出した黄色部分もグレースケールに変換してから、Cannyを使用して輪郭を抽出します。その輪郭画像に対してHoughLinesを使用して直線を検出しています。
使用している道路の映像は、PS4のゲーム、The Crew2のものです。画像処理用としては、実写の映像よりもだいぶ条件がいい状態です。実写の映像を自分で撮影してくるのはメンドイので横着しました。
# -*- coding: utf-8 -*- """ 車線検出 Cannyによるエッジ検出とHoughLinesによる直線検出 python 3.6 opencv 4.1 """ import cv2 import numpy as np import sys # コマンドライン引数 1番目=入力映像ファイル名 2番目=出力映像ファイル名 3番目=出力輪郭線ファイル名 args = sys.argv if len(args) == 1: sys.exit(1) input_file = args[1] output_file = None contour_file = None if len(args) >= 3: output_file = args[2] if len(args) == 4: contour_file = args[3] def equalize(img_org): """ 平坦化 """ img_tmp = cv2.cvtColor(img_org, cv2.COLOR_BGR2GRAY) # ヒストグラム平坦化 img_tmp = cv2.equalizeHist(img_tmp) # 画面の上半分消去 count = np.array([[0, 0], [1279, 0], [1279, 340], [0, 340]]) img_tmp = cv2.fillPoly(img_tmp, pts=[count], color=(0)) return img_tmp def yellow(img_org): """ 黄色線抽出 HSVに変換してから黄色だけを抽出する """ img_hsv = cv2.cvtColor(img_org, cv2.COLOR_BGR2HSV) # 日本の道路の黄色線 # lower_color = np.array([0, 40, 50], np.uint8) # upper_color = np.array([15, 200, 200], np.uint8) # The Crew2の黄色線 lower_color = np.array([15, 100, 50], np.uint8) upper_color = np.array([40, 200, 200], np.uint8) # マスク画像の生成 img_mask = cv2.inRange(img_hsv, lower_color, upper_color) # フレーム画像とマスク画像の共通の領域を抽出 img_tmp = cv2.bitwise_and(img_hsv, img_hsv, mask=img_mask) img_tmp = cv2.cvtColor(img_tmp, cv2.COLOR_BGR2GRAY) ret, img_tmp = cv2.threshold(img_tmp, 10, # 閾値 256, # 画素値の最大値 cv2.THRESH_BINARY) # 2値化type # 画面の上半分消去 count = np.array([[0, 0], [1279, 0], [1279, 340], [0, 340]]) img_tmp = cv2.fillPoly(img_tmp, pts=[count], color=(0)) return img_tmp def road(img): """ 車線検出・描画 HoughLinesを使用して一定の長さの直線を検出・描画 """ road = np.zeros((720, 1280, 3), np.uint8) lines = cv2.HoughLines(img, 1, # lines np.pi/180, # rho 150) # theta if lines is not None: for line in lines: for rho, theta in line: a = np.cos(theta) b = np.sin(theta) x0 = a*rho y0 = b*rho x1 = int(x0 + 2000*(-b)) y1 = int(y0 + 2000*(a)) x2 = int(x0 - 2000*(-b)) y2 = int(y0 - 2000*(a)) # 水平に近い線は描画しない if abs(a) < 0.15: continue # print("rho,theta,a,b,x0,y0", rho, theta, a, b, x0, y0) cv2.line(road, (x1, y1), (x2, y2), (0 ,0, 255), 2) # 画面の上半分消去 count = np.array([[0, 0], [1279, 0], [1279, 340], [0, 340]]) road = cv2.fillPoly(road, pts=[count], color=(0, 0, 0)) return road def main(): cap = cv2.VideoCapture(input_file) # 描画した映像の録画用 fmt = cv2.VideoWriter_fourcc("m", "p", "4", "v") size = (1280, 720) if output_file is not None: output_writer = cv2.VideoWriter(output_file, fmt, 30, size) if contour_file is not None: contour_writer = cv2.VideoWriter(contour_file, fmt, 30, size) while(cap.isOpened()): # 1フレームの画像読み込み img_org = cap.read()[1] if img_org is None: break # グレースケールに変換してから平坦化 img_tmp = equalize(img_org) # 黄色線は別に抽出してからグレースケールに変換 img_yellow = yellow(img_org) # 2つの画像をorする img_tmp = cv2.bitwise_or(img_tmp, img_yellow) # blurをかけて細かな部分をつぶす img_tmp = cv2.blur(img_tmp, ksize=(5, 5)) # 輪郭抽出 img_for_draw = cv2.Canny(img_tmp, 200, # threshold1 255, # threshold2 apertureSize = 3) # 車線検出・描画 road_lines = road(img_for_draw) # グレースケールからRGBに変換 img_for_draw = cv2.cvtColor(img_for_draw, cv2.COLOR_GRAY2BGR) # 元の画像と描画した車線を合成 img = cv2.bitwise_or(img_org, road_lines) cv2.imshow("img", img) # 映像ファイルに保存 if output_file is not None: output_writer.write(img) if contour_file is not None: contour_writer.write(img_for_draw) # qキーが押されたら終了 if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() if __name__ == '__main__': main()