티스토리 뷰

OpenCV

[ OpenCV ] 도로 차선 인식 프로그램

통통푸딩 2021. 6. 17. 01:15
반응형

도로 차선을 인식하는 프로그램을 만들어보았다.

OpenCV를 사용하고 언어는 C++을 사용했다.

 

1. 구현 순서 및 설명

구현한 순서는 다음과 같다. 

(1) 원본 영상을 읽어오기

(2) 흰색, 노란색 범위에 있는 것만 필터링하여 후보로 저장

(3) 영상을 GrayScale로 변환

(4) Canny Edge Detection으로 에지를 추출 (잡음 제거를 위한 Gaussian 필터링도 포함)

(5) 자동차의 진행 방향 바닥에 존재하는 차선만을 검출하기 위한 관심 영역을 지정

(6) Hough 변환으로 에지에서의 직선 성분을 추출

(7) 추출한 직선 성분으로 좌우 차선에 있을 가능성이 있는 직선들만 따로 뽑아서 좌우 각각 직선을 계산하고 선형 회귀로 가장 적합한 선 찾기

(8) 자동차의 진행 방향을 예측

(9) 영상에 최종 차선을 선으로 그리고 내부 다각형을 색으로 채운다. 예측 진행 방향 텍스트를 영상에 출력

(10) 결과 영상을 동영상 파일로 저장하고 중간에 캡쳐하여 사진으로 저장

(11) 결과 영상을 출력

 

 

(1) 원본 영상을 읽어오기

저장되어 있는 도로를 달리는 자동차의 동영상 파일을 읽어온다. 읽어온 동영상 파일에서 프레임을 추출해 영상 처리를 한다. VideoCapture를 이용한다.

 

 

 

(2) 흰색, 노란색 범위에 있는 것만 필터링하여 후보로 저장

도로의 차선은 흰색과 노란색이므로 범위를 지정하여 필터링하도록 한다. BGR 영상에서 흰색 차선 후보를 검출하고, HSV 영상에서 노란색 차선 후보를 검출한다. 그 후에 두 영상을 합친다. Scalar로 색의 범위를 지정해놓고 inRange함수와 bitwise_and로 필터링한다.

inRange함수 는 특정 색상 영역을 추출한다. 지정 범위내에 있으면 그 값을 그대로, 그렇지 않은 부분은 0(검은색)으로 채워서 결과값을 반환한다.

그 후에 bitwise_and 함수로 비트의 AND연산을 통해 mask로 씌워진 부분만 출력되도록 한다. 필터링된 흰색과 노란색 두 영상을 addweighted 함수로 합쳐서 차선 후보 영상을 만든다.

 

 

 

(3) 영상을 GrayScale로 변환

차선 후보 영상으로부터 에지(Edge)를 추출해야 한다. 에지를 효과적으로 추출하기 위해 다음과 같은 과정을 거친다.

먼저 cvtColor 함수의 인자에 COLOR_BGR2GRAY를 사용하여 GrayScale로 변환한다.

 

 

 

(4) Canny Edge Detection으로 에지를 추출 (잡음 제거를 위한 Gaussian 필터링도 포함)

이제 영상에서 에지를 추출하기 위해 Canny Edge Detection 알고리즘을 사용한다. 에지 검출 방식에서 가장 많이 사용하는 알고리즘이다. 일반적으로 에지를 검출할 때는 노이즈(잡음)에 민감하다. 그래서 Canny 함수의 알고리즘은 다음의 5단계를 거친다.

 

1. Gaussian Filter로 이미지의 잡음을 제거한다.

2. Sobel Filter를 사용하여 Gradient의 크기(intensity)를 구한다.

3. Non-maximum suppression을 적용하여 경계선 검출기에서 거짓 반응을 제거한다.

4. 경계선으로써 가능성 있는 픽셀을 골라내기 위해 double threshold 방식을 적용한다.

5. 앞서 double threshold 방식에서 maxVal을 넘은 부분을 strong edge, minVal과 maxVal 사이의 부분을 weak edge로 설정하여 strong edge와 연결되어 있는 weak edge를 edge로 판단하고 그렇지 않는 부분은 제거한다. (Hysteresis thresholding)

 

Canny함수를 사용하면 가우시안 필터링을 거치기 때문에 노이즈를 제거한 후에 에지를 추출할 수 있다.

 

 

 

(5) 자동차의 진행 방향 바닥에 존재하는 차선만을 검출하기 위한 관심 영역을 지정

에지가 검출된 영상에서 차선만을 검출하기 위해서 자동차의 진행 방향의 일정 영역 바닥으로 제한한다. 우선 관심 영역의 정점 네 개를 계산한다. 모두 0으로 되어있는 비어있는 영상을 만들고 그 영상에 네 개의 정점으로 정의된 다각형 내부를 fillConvexPoly 함수로 색상을 채운다. 그러면 다각형 내부만 0이 아닌 수가 되고, 이영상을 원래의 에지검출 영상과 bitwise_and 함수를 실행한 결과 영상을 반환한다. 결과 영상은 관심 영역의 에지만 남게 된다.

 

 

(6) Hough 변환으로 에지에서의 직선 성분을 추출

가장 핵심적인 단계로, 허프 변환을 사용하여 검출된 에지 영상에서의 직선 성분을 추출한다.

 

허프 변환이란

영상에서 (x,y) 좌표공간의 픽셀들은 (r,θ) 매개변수 공간에서 곡선의 형태로 나타난다. 또한 (x,y) 좌표공간에서 같은 직선상에 존재하는 픽셀들의 경우 (r,θ) 매개변수 공간에서 교점을 가지게 된다. 허프 변환 기법은 이러한 특징을 이용하여 영상의 특징픽셀들을 (x,y) 좌표공간에서 (r,θ) 매개변수 공간으로 사상(mapping)시킨 후, voting 과정을 통

해 교점을 찾아 직선 성분을 추출하는 방법이다.

 

OpneCV에서는 HoughLinesP 함수를 사용하여 허프 변환을 한다. HoughLinesP 함수는 HoughLines 함수와 다르게 허프 변환에 확률을 적용한 직선 검출을 한다.

 

void cv::HoughLinesP(cv::InputArray image, cv::OutputArray lines, double rho, double theta, int threshold, double minLineLength = (0.0), double maxLineGap = (0.0))

- image : 입력할 이미지 변수, Edge detect 된 이미지를 입력해야 함

- lines : 허프 변환 직선 검출 정보를 저장할 Array

- rho : 계산할 픽셀(매개 변수)의 해상도. 그냥 1을 사용하면 됨. 변환된 그래프에서, 선에서 원점까지의 수직 거리

- theta : 계산할 각도(라디안, 매개변수)의 해상도, 선 회전 각도. 모든 방향에서 직선을 검출하려면 PI/180 을 사용하면 된다.

- threshold : 변환된 그래프에서 라인을 검출하기 위한 최소 교차 수

- minLineLength : 검출할 직선의 최소 길이, 단위는 픽셀

- maxLineGap : 검출할 선위의 점들 사이의 최대 거리, 점 사이의 거리가 이 값보다 크면 다른 선으로 간주함

 

minLineLength 와 maxLineGap 값을 통해서 찾고자 하는 선분을 두 번 걸러주게 된다. 모든 점이 아닌 직선일 수 있는 픽셀들의 좌표만 연산하기 때문에 연산량이 줄어들어 연산속도가 더 빠르다.

 

 

 

 

(7) 추출한 직선 성분으로 좌우 차선에 있을 가능성이 있는 직선들만 따로 뽑아서 좌우 각각 직선을 계산하고 선형 회귀로 가장 적합한 선 찾기

추출한 선분으로부터 좌우 차선에 있을 가능성이 있는 직선들만 따로 뽑아서 좌우 각각 하나의 직선으로 변환한다. 검출된 모든 허프 변환 직선들의 기울기를 계산한다. 직선을 기울기와 대략적인 위치에 따라 좌우로 분류한다.

선형 회귀를 통해 좌우 차선 각각 가장 적합한 선을 찾는다. 이 때, fitLine 함수로 주어진 contour에 최적화된 직선을 추출한다.

 

void cv::fitLine (InputArray points, OutputArray line, int distType, double param, double reps, double aeps)

- points : 2D나 3D 점들의 input 벡터.

- line : output line 파라미터. 2D fitting일 경우에 4개 요소의 벡터를 반환( Vec4f나 Vec4d같은). 3D fitting일 경우엔 6개 요소의 벡터를 반환.

- distType: 거리 계산 방식 (DIST_L2, DIST_L1, DIST_L12, DIST_FAIR, DIST_WELSCH, DIST_HUBER)

- param: distType에 전달할 인자, 0 = 최적값 선택

- reps: 반지름 정확도, 선과 원본 좌표의 거리, 0.01 권장

- aeps: 각도 정확도, 0.01 권장

 

fitLine 함수는 ∑iρ(ri) 를 최소화하여 설정된 2D 또는 3D 점에 선을 맞춘다. 여기서  ri 는 i 번째 점 사이의 거리이고, 선과 ρ(r)은 거리 함수이다. distType에 따라  ρ(r)가 다르다. 나는 DIST_L2를 사용했다. ρ(r)=r^2/2 , 가장 간단하고 빠른 최소제곱법이다.

 

위와 같은 방법으로 좌우 각각 가장 적합한 선을 찾고, 선 끝의 두 점을 각각 계산한다. y = m*x + b를 통해 x = (y-b) / m 이라는 것을 이용한다.

 

 

 

(8) 자동차의 진행 방향을 예측

이 단계는 계획서에 작성했던 원래 계획에서 추가한 단계이다. 좀 더 흥미로운 것을 추가하고 싶어서 진행 방향을 계산하는 단계를 추가해보았다. 자율 주행 자동차처럼 진행 방향을 알아서 파악하도록 하는 것이 목적이다. 자동차의 진행 방향은 Straight(직진), Left Turn(좌회전), Right Turn(우회전) 중 하나다. 두 차선이 교차하는 지점(사라지는 점)이 중심점으로부터 왼쪽에 있는지 오른쪽에 있는지를 통해 진행 방향을 예측한다. 먼저, 두 차선이 교차하는 지점의 x좌표를 계산하고 중심점의 x좌표보다 어느 쪽에 있는지를 통해 예측한다.

 

 

 

(9) 영상에 최종 차선을 선으로 그리고 내부 다각형을 색으로 채운다. 예측 진행 방향 텍스트를 영상에 출력

좌우 차선을 경계로 하는 내부 다각형을 투명하게 색을 채운다. 다각형의 네 꼭짓점을 이용하여 위에서도 사용했던 fillConvexPoly 함수를 사용한다. 색을 채운 영상과 기존의 영상을 합친 후에 최종 좌우 차선을 line함수로 선을 그린다. putText 함수로 예측 진행 방향을 텍스트로 영상에 입힌다.

 

 

 

(10) 결과 영상을 동영상 파일로 저장하고 중간에 캡쳐하여 사진으로 저장

VideoWriter로 결과 영상을 동영상 파일로 저장한다.

결과 실행 도중에 영상을 캡쳐하여 imwrite함수로 이미지도 저장한다.

 

 

 

(11) 결과 영상을 출력

결과 영상은 imshow 함수로 출력하고 esc 키로 종료할수 있도록 한다.

 

 

 

 

2. 소스 코드

< RoadLaneDetector.h>

#pragma once
#include <opencv2/highgui/highgui.hpp>
#include<iostream>
#include <string>
#include <vector>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

class RoadLaneDetector
{
private:
	double img_size, img_center;
	double left_m, right_m;
	Point left_b, right_b;
	bool left_detect = false, right_detect = false;

	//관심 영역 범위 계산시 사용 
	double trap_bottom_width = 0.85;  //사다리꼴 아래쪽 가장자리 너비 계산을 위한 백분율
	double trap_top_width = 0.07;     //사다리꼴 위쪽 가장자리 너비 계산을 위한 백분율
	double trap_height = 0.4;         //사다리꼴 높이 계산을 위한 백분율


public:
	Mat filter_colors(Mat img_frame);
	Mat limit_region(Mat img_edges); 
	vector<Vec4i> houghLines(Mat img_mask);
	vector<vector<Vec4i> > separateLine(Mat img_edges, vector<Vec4i> lines); 
	vector<Point> regression(vector<vector<Vec4i> > separated_lines, Mat img_input);
	string predictDir();
	Mat drawLine(Mat img_input, vector<Point> lane, string dir);
};

 

 

 

 

< RoadLaneDetection.cpp >

#include "opencv2/opencv.hpp"
#include <opencv2/highgui/highgui.hpp>
#include "RoadLaneDetector.h"
#include <iostream>
#include <string>
#include <vector>


Mat RoadLaneDetector::filter_colors(Mat img_frame) {
	/*
		흰색/노란색 색상의 범위를 정해 해당되는 차선을 필터링한다.
	*/
	Mat output;
	UMat img_hsv, img_combine;
	UMat white_mask, white_image;
	UMat yellow_mask, yellow_image;
	img_frame.copyTo(output);

	//차선 색깔 범위 
	Scalar lower_white = Scalar(200, 200, 200); //흰색 차선 (RGB)
	Scalar upper_white = Scalar(255, 255, 255);
	Scalar lower_yellow = Scalar(10, 100, 100); //노란색 차선 (HSV)
	Scalar upper_yellow = Scalar(40, 255, 255);

	//흰색 필터링
	inRange(output, lower_white, upper_white, white_mask);
	bitwise_and(output, output, white_image, white_mask);

	cvtColor(output, img_hsv, COLOR_BGR2HSV);

	//노란색 필터링
	inRange(img_hsv, lower_yellow, upper_yellow, yellow_mask);
	bitwise_and(output, output, yellow_image, yellow_mask);

	//두 영상을 합친다.
	addWeighted(white_image, 1.0, yellow_image, 1.0, 0.0, output);
	return output;
}


Mat RoadLaneDetector::limit_region(Mat img_edges) {
	/*
		관심 영역의 가장자리만 감지되도록 마스킹한다.
		관심 영역의 가장자리만 표시되는 이진 영상을 반환한다.
	*/
	int width = img_edges.cols;
	int height = img_edges.rows;

	Mat output;
	Mat mask = Mat::zeros(height, width, CV_8UC1);
	
	//관심 영역 정점 계산
	Point points[4]{
		Point((width * (1 - trap_bottom_width)) / 2, height),
		Point((width * (1 - trap_top_width)) / 2, height - height * trap_height),
		Point(width - (width * (1 - trap_top_width)) / 2, height - height * trap_height),
		Point(width - (width * (1 - trap_bottom_width)) / 2, height)
	};

	//정점으로 정의된 다각형 내부의 색상을 채워 그린다.
	fillConvexPoly(mask, points, 4, Scalar(255, 0, 0));

	//결과를 얻기 위해 edges 이미지와 mask를 곱한다.
	bitwise_and(img_edges, mask, output);
	return output;
}

vector<Vec4i> RoadLaneDetector::houghLines(Mat img_mask) {
	/*
		관심영역으로 마스킹 된 이미지에서 모든 선을 추출하여 반환
	*/
	vector<Vec4i> line;

	//확률적용 허프변환 직선 검출 함수 
	HoughLinesP(img_mask, line, 1,  CV_PI / 180, 20, 10, 20);
	return line;
}

vector<vector<Vec4i>> RoadLaneDetector::separateLine(Mat img_edges, vector<Vec4i> lines) {
	/*
		검출된 모든 허프변환 직선들을 기울기 별로 정렬한다.
		선을 기울기와 대략적인 위치에 따라 좌우로 분류한다.
	*/
	
	vector<vector<Vec4i>> output(2);
	Point ini, fini;
	vector<double> slopes;
	vector<Vec4i> selected_lines, left_lines, right_lines;
	double slope_thresh = 0.3;

	//검출된 직선들의 기울기를 계산
	for (int i = 0; i < lines.size(); i++) {
		Vec4i line = lines[i];
		ini = Point(line[0], line[1]);
		fini = Point(line[2], line[3]);

		double slope = (static_cast<double>(fini.y) - static_cast<double>(ini.y)) 
			/ (static_cast<double>(fini.x) - static_cast<double>(ini.x) + 0.00001);

		//기울기가 너무 수평인 선은 제외
		if (abs(slope) > slope_thresh) {
			slopes.push_back(slope);
			selected_lines.push_back(line);
		}
	}

	//선들을 좌우 선으로 분류
	img_center = static_cast<double>((img_edges.cols / 2));
	for (int i = 0; i < selected_lines.size(); i++) {
		ini = Point(selected_lines[i][0], selected_lines[i][1]);
		fini = Point(selected_lines[i][2], selected_lines[i][3]);

		if (slopes[i] > 0 && fini.x > img_center && ini.x > img_center) {
			right_lines.push_back(selected_lines[i]);
			right_detect = true;
		}
		else if (slopes[i] < 0 && fini.x < img_center && ini.x < img_center) {
			left_lines.push_back(selected_lines[i]);
			left_detect = true;
		}
	}

	output[0] = right_lines;
	output[1] = left_lines;
	return output;
}

vector<Point> RoadLaneDetector::regression(vector<vector<Vec4i>> separatedLines, Mat img_input) {
	/*
		선형 회귀를 통해 좌우 차선 각각의 가장 적합한 선을 찾는다.
	*/
	vector<Point> output(4);
	Point ini, fini;
	Point ini2, fini2;
	Vec4d left_line, right_line;
	vector<Point> left_pts, right_pts;

	if (right_detect) {
		for (auto i : separatedLines[0]) {
			ini = Point(i[0], i[1]);
			fini = Point(i[2], i[3]);

			right_pts.push_back(ini);
			right_pts.push_back(fini);
		}

		if (right_pts.size() > 0) {
			//주어진 contour에 최적화된 직선 추출
			fitLine(right_pts, right_line, DIST_L2, 0, 0.01, 0.01);
			
			right_m = right_line[1] / right_line[0];  //기울기
			right_b = Point(right_line[2], right_line[3]);
		}
	}
	
	if (left_detect) {
		for (auto j : separatedLines[1]) {
			ini2 = Point(j[0], j[1]);
			fini2 = Point(j[2], j[3]);

			left_pts.push_back(ini2);
			left_pts.push_back(fini2);
		}

		if (left_pts.size() > 0) {
			//주어진 contour에 최적화된 직선 추출
			fitLine(left_pts, left_line, DIST_L2, 0, 0.01, 0.01);
			
			left_m = left_line[1] / left_line[0];  //기울기
			left_b = Point(left_line[2], left_line[3]); 
		}
	}

	//좌우 선 각각의 두 점을 계산한다.
	//y = m*x + b  --> x = (y-b) / m
	int ini_y = img_input.rows;
	int fin_y = 470;

	double right_ini_x = ((ini_y - right_b.y) / right_m) + right_b.x;
	double right_fin_x = ((fin_y - right_b.y) / right_m) + right_b.x;

	double left_ini_x = ((ini_y - left_b.y) / left_m) + left_b.x;
	double left_fin_x = ((fin_y - left_b.y) / left_m) + left_b.x;

	output[0] = Point(right_ini_x, ini_y);
	output[1] = Point(right_fin_x, fin_y);
	output[2] = Point(left_ini_x, ini_y);
	output[3] = Point(left_fin_x, fin_y);

	return output;
}

string RoadLaneDetector::predictDir() {
	/*
		두 차선이 교차하는 지점(사라지는 점)이 중심점으로부터
		왼쪽에 있는지 오른쪽에 있는지로 진행방향을 예측한다.
	*/
	
	string output;
	double vx;
	double thres_vp = 10;
	
	//두 차선이 교차하는 지점 계산
	vx = static_cast<double>(((right_m * right_b.x) - (left_m * left_b.x) - right_b.y + left_b.y) / (right_m - left_m));

	if (vx < img_center - thres_vp)
		output = "Left Turn";
	else if (vx > img_center + thres_vp)
		output = "Right Turn";
	else if (vx >= (img_center - thres_vp) && vx <= (img_center + thres_vp))
		output = "Straight";

	return output;
}

Mat RoadLaneDetector::drawLine(Mat img_input, vector<Point> lane, string dir) {
	/*
		좌우 차선을 경계로 하는 내부 다각형을 투명하게 색을 채운다.
		좌우 차선을 영상에 선으로 그린다.
		예측 진행 방향 텍스트를 영상에 출력한다.
	*/
	
	vector<Point> poly_points;
	Mat output;

	img_input.copyTo(output);
	poly_points.push_back(lane[2]);
	poly_points.push_back(lane[0]);
	poly_points.push_back(lane[1]);
	poly_points.push_back(lane[3]);

	fillConvexPoly(output, poly_points, Scalar(0,230, 30), LINE_AA, 0);  //다각형 색 채우기
	addWeighted(output, 0.3, img_input, 0.7, 0, img_input);  //영상 합하기

	//좌우 차선 선 그리기
	line(img_input, lane[0], lane[1], Scalar(0, 255, 255), 5, LINE_AA);
	line(img_input, lane[2], lane[3], Scalar(0, 255, 255), 5, LINE_AA);
	
	//예측 진행 방향 텍스트를 영상에 출력
	putText(img_input, dir, Point(520, 100), FONT_HERSHEY_PLAIN, 3, Scalar(255, 255, 255),3, LINE_AA);

	return img_input;
}

 

 

 

 

< Main.cpp >

#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <string>
#include <vector>
#include "opencv2/opencv.hpp"
#include "RoadLaneDetector.h"

int main()
{
	RoadLaneDetector roadLaneDetector;
	Mat img_frame, img_filter, img_edges, img_mask, img_lines, img_result;
	vector<Vec4i> lines;
	vector<vector<Vec4i> > separated_lines;
	vector<Point> lane;
	string dir;
	
	VideoCapture video("input.mp4");  //영상 불러오기

	if (!video.isOpened())
	{
		cout << "동영상 파일을 열 수 없습니다. \n" << endl;
		getchar();
		return -1;
	}

	video.read(img_frame);
	if (img_frame.empty()) return -1;

	VideoWriter writer;
	int codec = VideoWriter::fourcc('X', 'V', 'I', 'D');  //원하는 코덱 선택
	double fps = 25.0;  //프레임
	string filename = "./result.avi";  //결과 파일

	writer.open(filename, codec, fps, img_frame.size(), CV_8UC3);
	if (!writer.isOpened()) {
		cout << "출력을 위한 비디오 파일을 열 수 없습니다. \n";
		return -1;
	}

	video.read(img_frame);
	int cnt = 0;

	while (1) {
		//1. 원본 영상을 읽어온다.
		if (!video.read(img_frame)) break;

		//2. 흰색, 노란색 범위 내에 있는 것만 필터링하여 차선 후보로 저장한다.
		img_filter = roadLaneDetector.filter_colors(img_frame);

		//3. 영상을 GrayScale 으로 변환한다.
		cvtColor(img_filter, img_filter, COLOR_BGR2GRAY);

		//4. Canny Edge Detection으로 에지를 추출. (잡음 제거를 위한 Gaussian 필터링도 포함)
		Canny(img_filter, img_edges, 50, 150);
		
		//5. 자동차의 진행방향 바닥에 존재하는 차선만을 검출하기 위한 관심 영역을 지정
		img_mask = roadLaneDetector.limit_region(img_edges);

		//6. Hough 변환으로 에지에서의 직선 성분을 추출
		lines = roadLaneDetector.houghLines(img_mask);

		if (lines.size() > 0) {
			//7. 추출한 직선성분으로 좌우 차선에 있을 가능성이 있는 직선들만 따로 뽑아서 좌우 각각 직선을 계산한다. 
			// 선형 회귀를 하여 가장 적합한 선을 찾는다.
			separated_lines = roadLaneDetector.separateLine(img_mask, lines);
			lane = roadLaneDetector.regression(separated_lines, img_frame);
			
			//8. 진행 방향 예측
			dir = roadLaneDetector.predictDir();

			//9. 영상에 최종 차선을 선으로 그리고 내부 다각형을 색으로 채운다. 예측 진행 방향 텍스트를 영상에 출력
			img_result = roadLaneDetector.drawLine(img_frame, lane, dir);
		}
		
		//10. 결과를 동영상 파일로 저장. 캡쳐하여 사진 저장
		writer << img_result;
		if (cnt++ == 10) 
			imwrite("img_result.jpg", img_result);  //캡쳐하여 사진 저장

		//11. 결과 영상 출력
		imshow("result", img_result);

		//esc 키 종료
		if (waitKey(1) == 27) 
			break;
	}
	return 0;
}

 

 

 

 

 

3. 결과 화면

좌우 차선은 노란색 선으로 나타나고, 내부 다각형은 초록색으로 투명하게 채워져 있다.

진행 방향에 따라 가운데 위에 텍스트가 나타나는 것을 볼 수 있다.

OpenCV 결과 출력 화면. Left Turn (좌회전)

 

 

 

OpenCV 결과 출력 화면. Right Turn (우회전)

 

 

 

OpenCV 결과 출력 화면. 직진 (Straight)

 

 

 

 

 

깃허브 소스코드

https://github.com/choijoohee213/OpenCV_Road_Lane_Detection

 

choijoohee213/OpenCV_Road_Lane_Detection

OpenCV를 이용한 도로 차선 검출 프로그램. Contribute to choijoohee213/OpenCV_Road_Lane_Detection development by creating an account on GitHub.

github.com

 

 

 

 

효과적인 에지 검출을 하기 위해서 어떤 순서로 구현을 해야할까 지금까지 배웠던 지식을 총동원해서 계획했다.

야외 영상이기 때문에, 노이즈와 햇빛 등 모든 변수들을 고려해야 했다. 결과적으로는 검출을 성공적으로 해서 매우 만족한다. 다만, 이 영상으로 맞추어 구현하다보니 다른 영상으로 실행해보았을 때 완전히 제대로 검출되지는 않았다.

그렇다고 엄청 오류가 심하게 나거나 검출이 완전히 이상하진 않았기 때문에 그걸로 다행이라고 생각한다.

이런 엄청난 변수들을 고려하여 완벽한 수행을 하는 실제 자율주행 자동차의 차선 인식은 어떤 방식일지 다시 한번 궁금해져서 공부해보고 싶어졌다.

반응형
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday