!! 여기서 histogram은 opencv 함수 histogram을 사용하지 않았습니다.
이진화한 이미지는 하나의 채널과 0~255로 이루어진 이미지입니다. 그리고 차선이 있는 부분은 대부분 255에 근접한 값을 가질 것이고 차선이 없는 부분은 0에 근접한 값을 가질 것입니다. 그래서 생각한 방법이 바로 하나의 열에 대해 모든 행의 값을 더하면 차선이 있는 부분의 열에는 매우 큰 값이, 차선이 없는 부분에는 매우 작은 값이 나타날 것이라고 생각했습니다.
그렇게 실행한 결과, 한 프레임에 대한 histogram의 결과 그래프를 보면 380정도에 왼쪽 차선이, 1050정도에 오른쪽 차선이 있다는 것을 알 수 있게 되었습니다.
결과 이미지 :
7.Window ROI
def slide_window_search(binary_warped, left_current, right_current):
out_img = np.dstack((binary_warped, binary_warped, binary_warped))
nwindows = 4
window_height = np.int(binary_warped.shape[0] / nwindows)
nonzero = binary_warped.nonzero() # 선이 있는 부분의 인덱스만 저장
nonzero_y = np.array(nonzero[0]) # 선이 있는 부분 y의 인덱스 값
nonzero_x = np.array(nonzero[1]) # 선이 있는 부분 x의 인덱스 값
margin = 100
minpix = 50
left_lane = []
right_lane = []
color = [0, 255, 0]
thickness = 2
for w in range(nwindows):
win_y_low = binary_warped.shape[0] - (w + 1) * window_height # window 윗부분
win_y_high = binary_warped.shape[0] - w * window_height # window 아랫 부분
win_xleft_low = left_current - margin # 왼쪽 window 왼쪽 위
win_xleft_high = left_current + margin # 왼쪽 window 오른쪽 아래
win_xright_low = right_current - margin # 오른쪽 window 왼쪽 위
win_xright_high = right_current + margin # 오른쪽 window 오른쪽 아래
cv2.rectangle(out_img, (win_xleft_low, win_y_low), (win_xleft_high, win_y_high), color, thickness)
cv2.rectangle(out_img, (win_xright_low, win_y_low), (win_xright_high, win_y_high), color, thickness)
good_left = ((nonzero_y >= win_y_low) & (nonzero_y < win_y_high) & (nonzero_x >= win_xleft_low) & (nonzero_x < win_xleft_high)).nonzero()[0]
good_right = ((nonzero_y >= win_y_low) & (nonzero_y < win_y_high) & (nonzero_x >= win_xright_low) & (nonzero_x < win_xright_high)).nonzero()[0]
left_lane.append(good_left)
right_lane.append(good_right)
# cv2.imshow("oo", out_img)
if len(good_left) > minpix:
left_current = np.int(np.mean(nonzero_x[good_left]))
if len(good_right) > minpix:
right_current = np.int(np.mean(nonzero_x[good_right]))
left_lane = np.concatenate(left_lane) # np.concatenate() -> array를 1차원으로 합침
right_lane = np.concatenate(right_lane)
leftx = nonzero_x[left_lane]
lefty = nonzero_y[left_lane]
rightx = nonzero_x[right_lane]
righty = nonzero_y[right_lane]
left_fit = np.polyfit(lefty, leftx, 2)
right_fit = np.polyfit(righty, rightx, 2)
ploty = np.linspace(0, binary_warped.shape[0] - 1, binary_warped.shape[0])
left_fitx = left_fit[0] * ploty ** 2 + left_fit[1] * ploty + left_fit[2]
right_fitx = right_fit[0] * ploty ** 2 + right_fit[1] * ploty + right_fit[2]
ltx = np.trunc(left_fitx) # np.trunc() -> 소수점 부분을 버림
rtx = np.trunc(right_fitx)
out_img[nonzero_y[left_lane], nonzero_x[left_lane]] = [255, 0, 0]
out_img[nonzero_y[right_lane], nonzero_x[right_lane]] = [0, 0, 255]
plt.imshow(out_img)
plt.plot(left_fitx, ploty, color = 'yellow')
plt.plot(right_fitx, ploty, color = 'yellow')
plt.xlim(0, 1280)
plt.ylim(720, 0)
plt.show()
ret = {'left_fitx' : ltx, 'right_fitx': rtx, 'ploty': ploty}
return ret
!! cv2.HoughLines()나 cv2.HoughLinesP()를 사용하지 않은 이유 : HoughLine 함수들은 무겁기도 하고 곡선에 대한 차선 인식이 정확하지 않기 때문에 사용하지 않았습니다.
left_current = 이미지의 왼쪽에 있는 값 중 가장 큰 값을 가진 인덱스 good_left = window 안에 있는 부분만을 저장 다음 window의 left_current는 good_left의 길이가 50보다 작으면 nonzero_x의 인덱스 good_left의 값을 가지는 인자들의 mean값.
※ np.concatenate : Array를 1차원 배열으로 만들어 줌. ※ np.trunc : 소수점 부분을 버림.
[OpenCV]차선 인식
OpenCV Version = 4.2.0.34 // 3.x 버전을 사용해도 O
유투브 영상:
https://www.youtube.com/watch?v=ipyzW38sHg0
결과 영상:
1. Youtube 영상 불러오기
※ youtube_dl과 pafy 설치
2. Warpping (Bird Eye View)
※ getPerspectiveTransform(원근법) :
Perspective(원근법) 변환은 직선의 성질만 유지가 되고, 선의 평행성은 유지가 되지 않는 변환입니다. 기차길은 서로 평행하지만 원근변환을 거치면 평행성은 유지 되지 못하고 하나의 점에서 만나는 것 처럼 보입니다.(반대의 변환도 가능)
4개의 Point의 Input값과 이동할 output Point 가 필요합니다.
변환 행렬을 구하기 위해서는 cv2.getPerspectiveTransform() 함수가 필요하며, cv2.warpPerspective() 함수에 변환행렬값을 적용하여 최종 결과 이미지를 얻을 수 있습니다.
참고 블로그 : opencv-python.readthedocs.io/en/latest/doc/10.imageTransformation/imageTransformation.html
minv값은 마지막에 warpping된 이미지를 다시 원근감을 주기 위해 반대의 matrix값을 저장하는 변수입니다.
결과 이미지 :
아래 사진에서 빨간 원은 source 점을, 파란 원은 destination 점을 나타냅니다.
3. Color Filter (HLS 사용)
※ HLS (Hue, Luminanse, Saturation) :
lower = ([minimum_blue, minimum_green, minimum_red])
upper = ([maximum_blue, maximum_green, maximum_red])
더 자세한 내용은 opencv 홈페이지를 참조하시기 바랍니다.
docs.opencv.org/3.4/de/d25/imgproc_color_conversions.html
노란색 차선과 흰색 차선을 각각 필터링하여 bitwise_or로 합해준 mask를 원본 이미지와 bitwise_and로 합해준다면 mask부분만 남게 됩니다.
결과 이미지 :
4. ROI
검은색 이미지(mask)를 생성한 후, 다각형을 _shape의 형태로 생성하면 bitwise_and로 원본 이미지에서 _shape의 형태만큼의 픽셀이 복사됩니다. 만약 채널이 2채널 이상일 때는 mask의 채널도 맞춰줍니다.
아래 그림은 _shape에 대한 점을 순서대로 나타낸 것입니다.
결과 이미지 :
5. Threshold
이진화를 하기 위해서는 하나의 채널을 가진 gray scale 이미지로 바꿔주어야 합니다.
※ Threshold(이진화) :
아래 그림은 임계값이 127일 때의 결과 이미지입니다.
이 외에도 cv.ADAPTIVE_THRESH_MEAN_C, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_OTSU 등이 있고 적절한 함수를 쓰시면 됩니다.
docs.opencv.org/master/d7/d1b/group__imgproc__misc.html#gaa9e58d2860d4afa658ef70a9b1115576
이처럼 이진화 옵션의 종류는 다양하지만 저는 THRESH_BINARY를 사용하여 임계값(160) 이하의 값은 검은색으로, 이상의 값은 255(하얀색)으로 나타나게 했습니다.
결과 이미지 :
6. Histogram
이진화한 이미지는 하나의 채널과 0~255로 이루어진 이미지입니다. 그리고 차선이 있는 부분은 대부분 255에 근접한 값을 가질 것이고 차선이 없는 부분은 0에 근접한 값을 가질 것입니다. 그래서 생각한 방법이 바로 하나의 열에 대해 모든 행의 값을 더하면 차선이 있는 부분의 열에는 매우 큰 값이, 차선이 없는 부분에는 매우 작은 값이 나타날 것이라고 생각했습니다.
그렇게 실행한 결과, 한 프레임에 대한 histogram의 결과 그래프를 보면 380정도에 왼쪽 차선이, 1050정도에 오른쪽 차선이 있다는 것을 알 수 있게 되었습니다.
결과 이미지 :
7.Window ROI
left_current = 이미지의 왼쪽에 있는 값 중 가장 큰 값을 가진 인덱스
good_left = window 안에 있는 부분만을 저장
다음 window의 left_current는 good_left의 길이가 50보다 작으면 nonzero_x의 인덱스 good_left의 값을 가지는 인자들의 mean값.
※ np.concatenate : Array를 1차원 배열으로 만들어 줌.
※ np.trunc : 소수점 부분을 버림.
polyfit에 대한 참고 블로그 : pinkwink.kr/1127
결과 이미지 :
8. Draw Line
fillPoly함수로 왼쪽 선과 오른쪽 선을 포함하는 다각형을 그리고, pts_mean은 선과 선 사이의 center값으로 휘어진 곡선의 정도 등을 알 수 있습니다.
마지막으로 함수 warpping에서 가져온 값 minv를 이용해 원근감을 다시 준 이미지에 addWeighted함수로 다각형 색을 연하게 합성하는 작업으로 최종 결과물이 나타나게 됩니다.
결과 이미지 :
9. 코드
- 바닥에 있는 글씨나 앞 차가 차선을 밟을 때 생기는 불인식에 대한 수정 필요.
감사합니다.
'OpenCV > OpenCV' 카테고리의 다른 글