단일차로인식 - 1차

nugurii0·2022년 9월 26일
0

다중차로인식

목록 보기
11/11

단일차로인식을 진행하려 한다.블로그를 하나 잡아 레퍼런스삼아 어떠한 방식으로 진행했는지 살펴보고 구현한 뒤 개선하나가는 방향으로 진행해보고자 한다.

Hands-On Tutorial on Real Time Lane Detection using OpenCV

해당 블로그 내용을 참고하면서 차선 검출을 진행해보았다.

블로그는 python으로 되어있긴 한데 대충 로직만 보고 C++로 바꿔가면서 구현해보자.

진행 순서

해당 블로그는 lane detection을 5 단계로 나누었다.

  1. Understanding the Concept of Lane Detection
  2. Formulating the Problem Statement
  3. What is a Frame Mask?
  4. Image Pre-processing for Lane Detection
  5. Implementing Lane Detection using OpenCV in Python

블로그에서는 먼저 ROI를 설정하며 시작했다.

다음과 같이 ROI를 잡아서(마스킹을 하여) 불필요한 객체가 탐지되는 것을 막겠다는 것이다.

그리고 이진화를 진행하고

허프변환으로 차선 검출하고 마무리.

이렇게 진행했다. 대충 생각했던거랑 비슷해서 이제 하나하나 구현해보면 될 것 같다.

트러블슈팅 - 1

mask = Mat::zeros(h, w, CV_32F);
polylines(mask, pts, true, Scalar(128), -1);

마스크 행렬을 만들어 출력했는데, 에러가 났다.

libc++abi: terminating with uncaught exception of type cv::Exception: 
OpenCV(4.5.5) /tmp/opencv-20220905-49823-epewzm/opencv-4.5.5/modules/
imgproc/src/drawing.cpp:2029: error: (-215:Assertion failed) 
pts && npts && ncontours >= 0 && 0 <= thickness && thickness <= MAX_THICKNESS 
&& 0 <= shift && shift <= XY_SHIFT in function 'polylines'

thickness를 -1 또는 FILLED로 지정하면 다각형의 내부를 채우는 건데

왜인지 에러로그를 보면 0보다 커야만 한다고 써있다.

찾아보니 다각형을 채워서 그리는 별도의 함수 fillPoly() 가 존재했다.

fillPoly(mask, pts, Scalar(128), LINE_AA);

이렇게 구현하여 해결했다.

마스크 구현

vector<Point> pts;
pts.push_back(Point(50, 270));
pts.push_back(Point(220, 160));
pts.push_back(Point(360, 160));
pts.push_back(Point(480, 270));

mask = Mat::zeros(h, w, CV_8U);
fillPoly(mask, pts, Scalar(255), LINE_AA);
mask = ~mask;

processed = frame.clone();
processed.setTo(Scalar(0, 0, 0), mask);

마스크는 참고한 블로그에서 사용한 고정값을 그대로 가져와서 사용하였다.

이진화

cvtColor(frame, masked, COLOR_BGR2GRAY);   // 3채널 -> 1채널
adaptiveThreshold(masked, binarization, 255,
	ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 3, 5); // 이진화
binarization.setTo(Scalar(0, 0, 0), mask); // 마스킹

속이 차있지 않고 에지검출 한 것 처럼 테두리만 감지했다.

순서 조정

마스킹 후 이진화를 거치니 마스킹 한 경계면이 에지로 인식되어 직선검출을 하는데 방해가 됐다.

이진화를 먼저 거치고 마스킹을 하여 문제를 해결했다.

직선 검출

Canny(binarization, edge, 50, 150); // 에지 검출
HoughLinesP(closed_edge, lines, 1, CV_PI / 180, 10, 5, 5); // 직선 검출

직선 검출을 하기에 충분한 양의 에지가 나오지 않아서 threshold와 minline값을 계속 낮춰가며 설정하였다.

차선을 검출하기는 하지만

threshold값도 낮고, line의 최소 길이도 낮으니 여러 잡음까지 모두 검출해냈다.

방법을 찾기로 했다.

모폴로지 닫기연산

morphologyEx(edge, closed_edge, MORPH_CLOSE, Mat()); // 모폴로지 닫기 연산
HoughLinesP(closed_edge, lines, 1, CV_PI / 180, 30, 10, 5); // 직선 검출

닫기 연산으로 구한 edge픽셀 행렬로 허프 변환을 거쳐 직선을 검출해냈다.

저러면 차선에 해당하는 하얀 박스의 내부가 조금 채워지면서 라인 검출이 조금 더 쉬워지지 않을까 싶었는데 잡음도 같이 커져서 큰 의미는 없었던 것 같다.

최종 결과물

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main()
{
    VideoCapture cap;
    int delay = 30;
    int w, h;
    Mat frame, masked, mask;
    vector<Point> pts;
    TickMeter tm;
    Mat binarization, grayscale_frame, edge, dst, closed_edge;
    vector<Vec4i> lines;
    int total_frame = 1107;

    pts.push_back(Point(50, 270));
    pts.push_back(Point(220, 160));
    pts.push_back(Point(360, 160));
    pts.push_back(Point(480, 270));

    cap.open("./src/video/content/frames/" + to_string(1) + ".png", CAP_ANY);

    w = cap.get(CAP_PROP_FRAME_WIDTH);
    h = cap.get(CAP_PROP_FRAME_HEIGHT);

    mask = Mat::zeros(h, w, CV_8U);
    fillPoly(mask, pts, Scalar(255), LINE_AA);
    mask = ~mask;

    for (int i = 1; i < total_frame; i++)
    {
        tm.reset();
        tm.start();
        cap.open("./src/video/content/frames/" + to_string(i) + ".png", CAP_ANY);

        if (!cap.isOpened())
        {
            cerr << i << " img read failed!" << endl;
            return -1;
        }

        cap >> frame;
        if (frame.empty())
            break;

        cvtColor(frame, masked, COLOR_BGR2GRAY);                                                           // 3채널 -> 1채널
        adaptiveThreshold(masked, binarization, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 3, 3); // 이진화
        binarization.setTo(Scalar(0, 0, 0), mask);                                                         // 마스킹

        Canny(binarization, edge, 50, 150);                         // 에지 검출
        morphologyEx(edge, closed_edge, MORPH_CLOSE, Mat());        // 모폴로지 닫기 연산
        HoughLinesP(closed_edge, lines, 1, CV_PI / 180, 30, 10, 5); // 직선 검출

        dst = frame.clone();

        for (Vec4i l : lines)
        {
            line(dst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 2, LINE_AA);
        }

        imshow("original", frame);
        imshow("dst", dst);
        imshow("binarization", binarization);
        imshow("closed_edge", closed_edge);

        moveWindow("dst", w, 0);
        moveWindow("binarization", 0, h);
        moveWindow("closed_edge", w, h);

        tm.stop();
        cout << "Image inverse took " << tm.getTimeMilli() << "ms." << endl;

        if (waitKey(delay) == 27)
            break;
    }

    destroyAllWindows();

    return 0;
}

책이랑 인터넷 보면서 함수 여럿 가져와서 작성한거라서 모듈화고 뭐고 아무것도 되어있지 않지만, 작동은 한다.

측정 결과 한 프레임을 처리하는데에 약 6~8ms 소요됐다.

아쉬운 점은 잡음이 발생하는 것이랑 주변에 지나가는 차에 의해 음영이 지거나 하면 이를 에지픽셀로 간주하고 직선으로 검출해낸다.

뭔가 이를 제거할 방법을 찾아야 할 것 같다.

지금 생각으로는 문제가 되는 부분이 수평으로 생기는 직선은 절대 차선이 될 수 없으니 이를 필터링해주는 작업을 추가해야 할 것 같다.

직선의 기울기가 일정 수치보다 낮다면 (x방향 벡터가 더 크다면) 이를 제외하는 방법 정도가 떠오르는데, 문제는 이러면 커브길에서 꺾여있는 차선을 검출해내기 어렵지 않을까 싶기도 하다.

일단 구현해보고 테스트케이스 넣어보면서 추가로 발생하는 문제는 이후에 차차 고치면서 진행하면 될 것 같다.

profile
개발과 보안을 공부하는 학생

0개의 댓글