ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • OpenCV를 활용하여 Line과 Lane 추출하기
    프로젝트/자율주행 자동차 제작 프로젝트 2020. 9. 2. 17:42

     

    라즈베리파이를 세팅한 후에, VNC Viewer를 사용하여 원격으로 제어하여 실습을 진행했습니다.

    위 실습을 진행하기 위해서는 라즈베리파이에 USB 카메라를 연결하고, 1) OpenCV와 2)cheese를 설치해주는 사전과정이 필요합니다!


     

    카메라를 사용하는 기본 예제를 먼저 볼까요?

    #include <stdio.h>
    #include <opencv2/opencv.hpp>			//OpenCV
    #include <iostream>  
    using namespace cv;
    using namespace std;
    
    #define IMG_Width     640				//가로 픽셀값
    #define IMG_Height    480				//세로 픽셀값
    
    int main(void)
    {
    	int img_width, img_height;
    	img_width = 640;
    	img_height = 480;
    	
    	Mat mat_image_org_color;
    	Mat mat_image_org_gray;
    	Mat mat_image_gray_result;
    	Mat image;
    	
    	Scalar GREEN(0,255,0);
    	Scalar RED(0,0,255);
    	Scalar BLUE(255,0,0);
    	Scalar YELLOW(0,255,255);
       
    	//dev/video0 카메라에 접근하여 실행
    	VideoCapture cap(0);
        
        //해당 디렉토리에 저장되어 있는 영상을 읽어들여, window를 표시함
        //VideoCapture cap("/home/pi/AutoCar/C++/OpenCV/2/images/track-s.mkv");	
        
    	cap.set(CV_CAP_PROP_FRAME_WIDTH, img_width);
    	cap.set(CV_CAP_PROP_FRAME_HEIGHT,img_height);
    
    	if (!cap.isOpened()) 
    	{
    		cerr << "에러 - 카메라를 열 수 없습니다.\n";
    		return -1;
    	}
            cap.read(mat_image_org_color);		// 영상 변수에서 읽은 값들을 mat_image_org_color에 저장
        
        	//window를 표시
            namedWindow("Display window", CV_WINDOW_NORMAL);
            resizeWindow("Display window", img_width,img_height);
            moveWindow("Display window", 10, 10);
    	
    	
    	while(1)
    	{
    		
          if (!cap.isOpened()) 
    	  {
    		cerr << "에러 - 카메라를 열 수 없습니다.\n";
    		return -1;
    	   }
           cap.read(mat_image_org_color);
             
           imshow("Display window", mat_image_org_color);  
    	   if(waitKey(5) > 0)
           		break;     
    	
    	}
           cap.release();
           destroyAllWindows();
       
           return 0;
    	
    }
    

     

    카메라가 정상적으로 동작하는 것을 확인할 수 있습니다.

     

    동영상도 잘 읽어들이네요!

     

    컬러 이미지를 흑백 이미지와 이진화된 binary 이미지로도 추출해볼까요?

    #include <stdio.h>
    #include <opencv2/opencv.hpp>
    #include <iostream>  
    
    #define IMG_Width     640
    #define IMG_Height    480
    
    using namespace cv;
    using namespace std;
    
    int main(void)
    {
        int img_width, img_height;
        img_width = 640;
        img_height = 480;
    
        Mat mat_image_org_color;
        Mat mat_image_org_gray;
        Mat mat_image_gray_result;
        Mat mat_image_binary;
        Mat image;
    
        Scalar GREEN(0,255,0);
        Scalar RED(0,0,255);
        Scalar BLUE(255,0,0);
        Scalar YELLOW(0,255,255);
    
        mat_image_org_color = imread("/home/pi/Downloads/HancomMDS/AutoCar/lanecolor102.bmp"); 
    
        img_width = mat_image_org_color.size().width ;
        img_height = mat_image_org_color.size().height;
    
        namedWindow("Display window", CV_WINDOW_NORMAL);   
        resizeWindow("Display window", img_width,img_height);   
        moveWindow("Display window", 10, 10);	
    
        namedWindow("Gray Image window", CV_WINDOW_NORMAL);   
        resizeWindow("Gray Image window", img_width,img_height);   
        moveWindow("Gray Image window", 700, 10);
    
        namedWindow("Binary Image window", CV_WINDOW_NORMAL);   
        resizeWindow("Binary Image window", img_width,img_height);   
        moveWindow("Binary Image window", 10, 500);
    
        while(1)
        {
          cvtColor(mat_image_org_color, mat_image_org_gray, CV_RGB2GRAY);	// color to gray conversion
    
          //cv2.threshold(src,thresh,maxval,type)
            /*
            src: input grayscale image
            thresh: 임계값
            maxval: 임계값을 넘었을 때 적용할 value
            type: 이진화 type
            */
          threshold(mat_image_org_gray, mat_image_binary, 200, 255, THRESH_BINARY);
          if(mat_image_org_color.empty())
            {
            cerr << "빈 영상입니다.\n";
            break;	  
            }
    
          imshow("Display window", mat_image_org_color);  
          imshow("Gray Image window", mat_image_org_gray);		//흑백 이미지
          imshow("Binary Image window", mat_image_binary);     	//이진화 이미지
          
          if(waitKey(10) > 0)
            break;     
        }
    
    	destroyAllWindows();
       
    	return 0;
    	
    }
    

    GrayScale Image(오른쪽 위), Binary Image(왼쪽 아래)

     

    한 개의 line이 있는 이미지를 사용하여, canny edge도 추출해보겠습니다.

     

    Canny Algorithm은 여러 단계를 거치며 edge를 detect하는 방법으로,
    grayscale image에서 픽셀 값이 급격하게 변하는 지점들, 즉 불연속적인 픽셀 값을 가지는 지점들의 집합을 검출하여, edge를 추출해냅니다.

    다음과 같은 process로 수행됩니다.
     1) 이미지의 노이즈 제거 (Noise Reduction)
     2) Gradient 값이 높은 부분 찾기
     3) 최대 값이 아닌 픽셀은 0으로 만들기
     4) edge의 진위 여부 판단하기 (Hyteresis Thresholding)

     

    #include <stdio.h>
    #include <opencv2/opencv.hpp>
    #include <iostream>  
    
    #define IMG_Width     640
    #define IMG_Height    480
    
    using namespace cv;
    using namespace std;
    
    Mat Canny_Edge_detection(Mat img){
        Mat mat_blur_img, mat_canny_img;
    
        // 3*3 kernel을 사용하여 이미지의 노이스 감소
        blur(img, mat_blur_img, Size(3, 3));
    
        // canny 이미지로 변환
        // Canny(input,output,lowThreshold, lowThreshold*ratio, kernel_size)
        Canny(mat_blur_img, mat_canny_img, 70, 170, 3);	
    
      	return mat_canny_img;
    }
    
    int main(void)
    {
        int img_width, img_height;
        img_width = 640;
        img_height = 480;
    
        Mat mat_image_org_color;
        Mat mat_image_org_gray;
        Mat mat_image_gray_result;
        Mat mat_image_canny_edge;
        Mat image;
    	
        Scalar GREEN(0,255,0);
        Scalar RED(0,0,255);
        Scalar BLUE(255,0,0);
        Scalar YELLOW(0,255,255);
    	
        mat_image_org_color = imread("/home/pi/Downloads/line_1_0.jpg"); 
    
        img_width = mat_image_org_color.size().width ;
        img_height = mat_image_org_color.size().height;
    
        namedWindow("Display window", CV_WINDOW_NORMAL);   
        resizeWindow("Display window", img_width,img_height);   
        moveWindow("Display window", 10, 10);	
    
        namedWindow("Gray Image window", CV_WINDOW_NORMAL);   
        resizeWindow("Gray Image window", img_width,img_height);   
        moveWindow("Gray Image window", 700, 10);
    
        namedWindow("Canny Edge Image window", CV_WINDOW_NORMAL);   
        resizeWindow("Canny Edge Image window", img_width,img_height);   
        moveWindow("Canny Edge Image window", 10, 500);
    	
        while(1)
        {
    
          cvtColor(mat_image_org_color, mat_image_org_gray, CV_RGB2GRAY);	// color to gray conversion
          mat_image_canny_edge = Canny_Edge_detection(mat_image_org_gray);	// grayscale 이미지를 input으로 전달
    
          if(mat_image_org_color.empty())
          {
            cerr << "빈 영상입니다.\n";
            break;	  
          }
    
          imshow("Display window", mat_image_org_color);  
          imshow("Gray Image window", mat_image_org_gray);
          imshow("Canny Edge Image window", mat_image_canny_edge);     
          if(waitKey(10) > 0)
              break;     
    	}	
       
        destroyAllWindows();
    
        return 0;
    	
    }
    

    Canny edge Image (왼쪽 아래)

     

    다음은 Hough Transform을 이용하여, 이미지에서 line을 검출해보는 예제입니다.

    #include <stdio.h>
    #include <opencv2/opencv.hpp>
    #include <iostream>  
    
    #define IMG_Width     640
    #define IMG_Height    480
    
    using namespace cv;
    using namespace std;
    
    Mat Canny_Edge_detection(Mat img){
        Mat mat_blur_img, mat_canny_img;
    
        // 3*3 kernel을 사용하여 이미지의 노이스 감소
        blur(img, mat_blur_img, Size(3, 3));
    
        // canny 이미지로 변환
        // Canny(input,output,lowThreshold, lowThreshold*ratio, kernel_size)
        Canny(mat_blur_img, mat_canny_img, 70, 170, 3);	
    
      	return mat_canny_img;
    }
    int main(void)
    {
        int img_width, img_height;
        img_width = 640;
        img_height = 480;
    
        Mat mat_image_org_color;
        Mat mat_image_org_gray;
        Mat mat_image_gray_result;
        Mat mat_image_canny_edge;
        Mat image;
    
        Scalar GREEN(0,255,0);
        Scalar RED(0,0,255);
        Scalar BLUE(255,0,0);
        Scalar YELLOW(0,255,255);
    	
        mat_image_org_color = imread("/home/pi/Downloads/line_2_0.jpg"); 
    
        img_width = mat_image_org_color.size().width ;
        img_height = mat_image_org_color.size().height;
    
        namedWindow("Display window", CV_WINDOW_NORMAL);   
        resizeWindow("Display window", img_width,img_height);   
        moveWindow("Display window", 10, 10);	
    
        namedWindow("Gray Image window", CV_WINDOW_NORMAL);   
        resizeWindow("Gray Image window", img_width,img_height);   
        moveWindow("Gray Image window", 700, 10);
    
        namedWindow("Canny Edge Image window", CV_WINDOW_NORMAL);   
        resizeWindow("Canny Edge Image window", img_width,img_height);   
        moveWindow("Canny Edge Image window", 10, 500);
    	
        while(1)
        {
            mat_image_org_color = imread("/home/pi/Downloads/line_2_0.jpg");
            cvtColor(mat_image_org_color, mat_image_org_gray, CV_RGB2GRAY);	// color to gray conversion
            
            mat_image_canny_edge = Canny_Edge_detection(mat_image_org_gray);
            vector<Vec4i> linesP;
            
            /*
    		Probabilistic Hough Transform(확률 허프 변환)
            -> 모든 점을 대상으로 하는 것이 아니라 임의의 점을 이용하여 직선을 찾음
            -> 선의 시작점과 끝점을 return
    		*/
            
            //HoughLinesP(image, lines, rho, theta, threshold, minLineLength, maxLineGap)
            /*
                - theth: 각도(0~180 정수)
                - rho: r값의 범위(0~1 실수)
                - threshold: 만나는 점의 기준값
                	(값이 작을수록 검출되는 선들은 많아지지만 정확도가 떨어지고, 값이 클수록 정확도가 향상됨)
                - minLineLength: 선의 최소 길이
                - maxLineGap: 선과 선 사이의 최대 허용 간격
            */
            HoughLinesP(mat_image_canny_edge, linesP, 1, CV_PI/180, 70, 30, 40);
    
            for(int i = 0; i < linesP.size(); i++){
              Vec4i L = linesP[i];
              //hough transform을 통해 검출된 line들을 mat_image_org_color IMAGE에 그린다
              line(mat_image_org_color, Point(L[0], L[1]), Point(L[2], L[3]), Scalar(0, 0, 255), 3, LINE_AA);
    		}
    
            if(mat_image_org_color.empty())
            {
              cerr << "빈 영상입니다.\n";
              break;	  
            }
    
            imshow("Display window", mat_image_org_color);  
            imshow("Gray Image window", mat_image_org_gray);
            imshow("Canny Edge Image window", mat_image_canny_edge);    
            
            if(waitKey(10) > 0)
              break;     
        }
    	 
        destroyAllWindows();
    
        return 0;
    	
    }
    

     

    Hough Transform을 통해 검출된 line (왼쪽 위)

     

    마지막으로 Perspective Transform('Callibration 기법')을 사용하여, 기울어져 있는 line들을 직선의 형태로 변환해보겠습니다.

    Callibration이란 '교정, 보정' 이라는 사전적 정의를 가지는 용어로,
    자율주행 알고리즘에서는 (b) 왜곡된(원근 거리를 가지는) 카메라 이미지를 (c) 수직으로 본 평면적 이미지로 변환시키는 데에 사용됩니다.

    다음과 같은 process로 진행됩니다

     

    #include <stdio.h>
    #include <opencv2/opencv.hpp>
    #include <iostream>  
    
    #define IMG_Width     640
    #define IMG_Height    480
    #define PERSPECTIVE_IMG_W 640
    #define PERSPECTIVE_IMG_H 480
    
    using namespace cv;
    using namespace std;
    
    Point2f Source[] = {Point2f(0, 0), Point2f(-300, 340), Point(640, 0), Point(640+300, 340)};
    Point2f Destination[] = {Point2f(0, 0), Point2f(0, PERSPECTIVE_IMG_H), Point(PERSPECTIVE_IMG_W, 0), Point(PERSPECTIVE_IMG_W, PERSPECTIVE_IMG_H)};
    
    Mat Perspective(Mat img){
        Mat Matrix, result_img;
    
    	// Source의 좌표들을 Destination의 좌표들로 변환하는 변환 행렬(Matrix)를 계산해낸 뒤,
        Matrix = getPerspectiveTransform(Source, Destination);
        // warpPerspective함수를 사용하여 변환 행렬 값을 적용한 result_img를 구해낸다
        warpPerspective(img, result_img, Matrix, Size(PERSPECTIVE_IMG_W, PERSPECTIVE_IMG_H));
    
        return result_img;
    }
    
    Mat Canny_Edge_detection(Mat img){
        Mat mat_blur_img, mat_canny_img;
    
        // 3*3 kernel을 사용하여 이미지의 노이스 감소
        blur(img, mat_blur_img, Size(3, 3));
    
        // canny 이미지로 변환
        // Canny(input,output,lowThreshold, lowThreshold*ratio, kernel_size)
        Canny(mat_blur_img, mat_canny_img, 70, 170, 3);	
    
      	return mat_canny_img;
    }
    
    int main(void)
    {
        int img_width, img_height;
        img_width = 640;
        img_height = 480;
    
        Mat mat_image_org_color;
        Mat mat_image_org_gray;
        Mat mat_image_gray_result;
        Mat mat_image_canny_edge;
        Mat image;
    
        Scalar GREEN(0,255,0);
        Scalar RED(0,0,255);
        Scalar BLUE(255,0,0);
        Scalar YELLOW(0,255,255);
    	
        mat_image_org_color = imread("/home/pi/Downloads/line_2_0.jpg"); 
    
        img_width = mat_image_org_color.size().width ;
        img_height = mat_image_org_color.size().height;
    
        namedWindow("Display window", CV_WINDOW_NORMAL);   
        resizeWindow("Display window", img_width,img_height);   
        moveWindow("Display window", 10, 20);	
    
        namedWindow("Gray Image window", CV_WINDOW_NORMAL);   
        resizeWindow("Gray Image window", img_width,img_height);   
        moveWindow("Gray Image window", 700, 20);
    
        namedWindow("Canny Edge Image window", CV_WINDOW_NORMAL);   
        resizeWindow("Canny Edge Image window", img_width,img_height);   
        moveWindow("Canny Edge Image window", 10, 500);
    	
        while(1)
        {
          mat_image_org_color = imread("/home/pi/Downloads/line_2_0.jpg");
          cvtColor(mat_image_org_color, mat_image_org_gray, CV_RGB2GRAY);	// color to gray conversion
          mat_image_canny_edge = Canny_Edge_detection(mat_image_org_gray);
          
          // Canny Edge이미지에 Perspective Transform을 적용
          image = Perspective(mat_image_canny_edge);
        
          vector<Vec4i> linesP;
          
          // Perspective Transform이 적용된 이미지에서 Hough Line을 추출
          HoughLinesP(image, linesP, 1, CV_PI/180, 50, 20, 50);
          printf("Line Number : %3d\n", linesP.size());
          for(int i = 0; i < linesP.size(); i++){
            Vec4i L = linesP[i];
            
            // 추출한 Hough line들을 mat_image_org_color IMAGE에 그린다
            line(mat_image_org_color, Point(L[0], L[1]), Point(L[2], L[3]), Scalar(0, 0, 255), 3, LINE_AA);
            printf("L :[%3d,%3d] , [%3d,%3d] \n", L[0], L[1], L[2], L[3]);
          }
          printf("\n\n\n");
    
          if(mat_image_org_color.empty())
            {
            cerr << "빈 영상입니다.\n";
            break;	  
            }
    
          imshow("Display window", mat_image_org_color);  
          imshow("Gray Image window", mat_image_org_gray);
          imshow("Canny Edge Image window", mat_image_canny_edge);  
          
          if(waitKey(10) > 0)
              break;     
            }
    
        destroyAllWindows();
    
        return 0;
    
    }
    

     

    Perspective Transform이 적용된 Hough Line이 그려진 Image (왼쪽 위)


    위의 예제 파일들을 컴파일하기 위해 사용한 Makefile입니다

    CC = g++
    
    CFLAGS = -w -Wall -O2
    SRCS = *.c 
    
    PROG = test
    
    OPENCV = `pkg-config --cflags --libs opencv`
    LIBS = $(OPENCV)
    
    $(PROG):$(SRCS)
    	$(CC) $(CFLAGS) -o $(PROG) $(SRCS) $(LIBS)
    
    	$(./PROG)
    

    댓글

Designed by Tistory.