내맘대로 공부기록.

[ C++ ]

[ openCV | C++ ] 차선 검출 이미지 전처리 작업. 왜곡 현상 보정 하는 방법.

fwanggus 2021. 5. 8. 22:17

undistort 함수 사용 리뷰 🚀


  • 서론(참고한 글)
  • Camera Calibration
  • 체스보드를 이용하는 이유
  • 보정 작업 결과(with 와이드 렌즈)
  • CODE

 

이미지 프레임에 반영되어 있는 왜곡현상을 없애주기 위한 작업입니다. 이런 왜곡현상은 특정 카메라의 고유특성에 의해서 발생되는데,  opencv 함수를 이용해서 보정할 수 있습니다. opencv에서는 체스보드 이미지를 활용하는 함수가 있으며, 차선 검출을 구현하기 위한 전처리 작업으로서 많이 사용되는 것 같습니다.

 

이 글은 아래 글 들을 참고해서 학습하며 작성하였습니다. 왜곡 보정 작업 외에도 차선 검출 파이프 라인의 전반적인 내용들이 잘 설명되어 있으니 참고하면 좋을 것 같습니다.

 

1. Calibrating & Undistorting with OpenCV in C++ (Oh yeah)

 

Calibrating & Undistorting with OpenCV in C++ (Oh yeah) - AI Shack

I've already talked about camera distortions and calibrating a camera. Now we'll actually implement it. And we'll do it in C++. Why? Because it's a lot more easier and make much more sense. No more stupid CV_MAT_ELEM macros. And things will just work. But,

aishack.in

 

2. Advanced Lane Finding

 

snandasena/advanced-lane-finding

Udacity Self-Driving Car Engineer. . Contribute to snandasena/advanced-lane-finding development by creating an account on GitHub.

github.com

 

3. [Python3]OpenCV 곡선 차선 인식 프로젝트 - 차선 인식(1)

 

[Python3]OpenCV 곡선 차선 인식 프로젝트 - 차선 인식(1)

프로젝트 기간 2019.03.03 - 2019.03.05 (약 3일) 독학하였음을 밝힙니다. 03.03 : 개발 환경 구축 및 이론...

blog.naver.com

 

📍Camera Calibration :

실제 세계(사람의 눈으로 보이는 형태)와 카메라를 통해 보이는 사물의 형태는 완벽하게 같지는 않습니다. 이렇게 왜곡이 첨가되어 보이는 부분을 컴퓨터 비전으로 사용하기 위해서는 보정해줄 필요가 있고, 왜곡의 종류에 따라 아래와 같이 2가지로 분류할 수 있습니다.

  • radial distortion : 이미지 속 선들이 굽어 보이는 현상, 오목, 볼록해 보이는 현상을 나타냄.
  • tangential distortion : 실제 물체와의 거리보다 가깝거나 멀게 표현되는 경우. 또는, 실제보다 더 기울어 보이는 현상

각 왜곡 종류에 따른 수학적 의미는 아래 식으로 표현할 수 있다고 합니다. 

 

출처 - [Python3]OpenCV 곡선 차선 인식 프로젝트 - 차선 인식(1)

 

즉, 실제세계의 3차원 공간의 좌표를 화면으로 출력되는 2차원으로 가져오는 부분에서, 카메라 특성이 반영되어 실제 형상과는 다르게 보이는 것이죠. 그 두 공간 상의 차이를 표현하는 보정계수를 구해야 합니다.

 

출처 - [Python3]OpenCV 곡선 차선 인식 프로젝트 - 차선 인식(1)

 

또 하나 더 필요한 보정계수는 아래의 camera matrix(=intrinsic matrix)입니다. 유저가 사용하고 있는 카메라의 고유 특성을 나타내는 계수로써 사용됩니다.

 

"[Python3] OpenCV 곡선 차선 인식 프로젝트 - 차선 인식(1)" 에서 발췌한 내용.

이 왜곡들은 카메라의 Intrinsic Parameter, 그리고 Extrinsic Parameter에 의해 결정되어 나타납니다. Intrinsic Parameter는 카메라 자체의 특성이며, focal length(fx, fy)와 optical center(cx, cy) 등등이 있습니다. 이 Intrinsic Parameter를 이용하여 다음의 수식으로 Camera Matrix를 구할 수 있는데, 완성된 Camera Matrix는 렌즈 왜곡을 보정하는데에 결정적으로 쓰이게 됩니다.

 

출처 - [Python3]OpenCV 곡선 차선 인식 프로젝트 - 차선 인식(1)

 

📍FOCAL LENGTH, OPTICAL CENTER 에 대한 참고 그림.

 

OPTICAL CENTER, FOCAL LENGTH 설명그림.

 

이렇게 최종적으로 이미지 프레임의 왜곡을 보정하기 위한 보정계수 matrix는 두 가지(camera matrix, distortion coefficients)입니다. 실제 구현에서도 이 두 매트릭스를 인수로써 보정된 이미지를 가져오게 됩니다.

구현 방법은 일정한 간격을 가진 체스보드 이미지를 이용해서 보정계수를 찾아주게 됩니다.

 

📍체스보드를 이용하는 이유 

체스보드를 이용하면, 흰색 검은색의 명확한 명암대비(Gray Color Spcae)를 이용할 수 있고, 체스보드 칸의 일정한 간격을 이용해서 3차원 공간의 좌표와 2차원 공간의 좌표 계산이 용이하기 때문입니다. 그리고 당연하겠지만 opencv에서 체스보드를 이용한 함수를 제공하기 있기 때문이겠죠.

 

📍왜곡된 체스보드 이미지에 대해서, 다음의 함수를 이용할 수 있습니다.

  • findChessBoardCorners() : 체스보드의 꼭지점을 찾아준다.
  • drawChessBoardCorners() : 찾아준 꼭지점 값을 이미지에 그려준다.
  • undistort() : 계산한 보정계수를 이용해서, 왜곡됀 이미지를 보정한다.

순서는 findChessBoardCorners로 보정 계수 매트릭스를 계산 한 다음, 그 값을 이용해서, undistort 함수를 적용해서, 이미지의 왜곡 형상을 보정합니다.

처음엔 오픈소스로 공개된 주행데이터를 활용하려고 했습니다. 하지만, 주행 데이터가 있으면, 그 카메라로 찍은 체스보드가 있는지 없는지 조사하고, 데이터 소스를 찾는 것이 어려웠고, 찾는다고 해도 그 정확성이 어느 정도 일지 의심이 가기 때문에, 그런 짓은 도중에 그만뒀습니다. 그래서 만약 이 글을 보시는 분 중에 블랙박스로 체스보드 이미지 촬영이 가능하다면, 그 데이터로 진행해도 됩니다. 그게 가장 좋을 것 같네요. 본인은 여건이 되지 않기 때문에 컴퓨터 웹캠(맥북에어)으로 보정작업만 해봤습니다.

 

이미지 소스는 다음의 이미지를 사용했으며, 아이패드에 저장시킨 다음 카메라에 보이게 위치했습니다.

 

체스보드 이미지 데이터.

 

왜곡 전 데이터와 왜곡 보정 후 이미지를 비교해보면, 별로 다른 점은 없어 보였습니다. 예상한대로네요. 그래서, 아래와 같은 와이드 렌즈를 맥북 카메라에 씌어서 다시 시도해 보았습니다. 핸드폰으로 광각 촬영하려고 예전에 샀던 와이드 렌즈입니다. 어쨌든 렌즈가 한 겹 더 들어가기 때문에 조금의 희망을 가지고 시도해 보았습니다.

 

 

📍보정 작업 결과(with 와이드 렌즈)

 

좌 : 보정 전, 우 : 보정 후

 

결과는 보정 전 후 딱히 달라진게 없다고 생각합니다. 

하지만, 보정계수들을 확인해본 결과, focal length와 optical center값은 문제없이 계산된 것을 확인했습니다. 맥북 카메라 그대로 적용한 경우(위)와, 와이드 렌즈를 맥북 카메라에 씌운 후(아래) 보정계수를 구한 결과(텍스트 파일 형태로 출력함)입니다. 

 

보정계수 결과 -> 위 : 맥북 카메라 그대로, 아래 : 와이드 렌즈를 맥북 카멜에 씌운 경우.

 

와이드 렌즈를 씌었을 경우, optical center 위치는 10mm 내외로 크게 변하지 않았지만, focal length는 약 300mm 씩 짧아진 것을 확인할 수 있습니다.

 

맥북 카메라로는 사람의 눈으로 구별할 수 없을 정도의 왜곡이 발생하는 것 같습니다. 하지만, 왜곡된 정도를 눈으로 확인해보고 보정 과정을 보다 명확하게 확인하기 위해 인터넷 상에서 왜곡된 이미지를 검색했습니다. 아래 이미지를 소스로 이용하여 보정계수를 계산해봤습니다.

 

인터넷 상에서 찾은 이미지 소스.(인풋)

 

기존의 카메라 입력이였던 부분을 하나의 이미지 입력으로 변경해서 이미지 소스를 피딩해줍니다.

결과는 성공했다고 할 수 있을 까요? 결과 이미지는 다음과 같습니다.

 

좌 : 이미지 원본, 우 : 보정 후 이미지

 

왜곡된 부분을 보정하긴 했지만, 같은 이미지 사이즈로는 모든 부분을 표현해 주지 못했습니다. 보정이 들어간 이미지 변수를 애초부터 사이즈를 크게 잡으면 되지 않을까 하는 생각을 했지만, 매트릭스 계산 상 소스 이미지와 같은 크기를 가져야 하기 때문에 불가능한 것 같습니다. 이때 보정 계수의 값은 아래와 같습니다.

 

인터넷에서 찾은 이미지 소스로 보정계수를 계산함.

 

인터넷 상에서 찾은 이미지로 보정을 완료했습니다. 원래라면, 이렇게 구한 보정계수를 다시 읽어와서, perspective transform 전처리 등의 작업에 사용할 수 있을 것 같습니다. 블랙박스로 적용해 본 분들은 한 번 시도해보는 것도 좋을 것 같네요. 아쉬운 대로 방금 구한 보정계수를 맥북 카메라에 적용시켰을 때 어떻게 재 왜곡(하드웨어가 다르기 때문에 맞지 않은 보정계수로 인해 재 왜곡이 될 것으로 예상.) 되는지 확인해 보겠습니다. 보정계수를 적용하기 위해서 undistort 함수를 사용합니다.

undistort 함수를 사용하기 위해서는, camera Matrix(=intrinsic matrix)와 distortion coeffiecient만 있으면 됩니다. 이 작업을 하기 위해 첫 번째로는 체스보드 이미지 소스를 이용해 보정계수를 구한 다음 텍스트 파일로 저장합니다. 두 번째로는 저장한 텍스트 파일(보정계수 값)을 읽어 들여서, undistort 함수 한 번으로 간편하게 왜곡을 부여하거나, 상쇄시키거나 할 수 있습니다.

 

위에서 구한 체스보드의 보정계수를 카메라 입력에 적용해서 출력해보았습니다. 저 같은 경우는 보정 계수 계산에 사용한 카메라(인터넷에서 찾은 이미지 소스)와 검증용 카메라 스펙(맥북 카메라)이 다르기 때문에, 오히려 왜곡이 생길 것으로 예상하고 실행했습니다.

결과는 예상한 바와 같이 왜곡이 더 발생했습니다. 결과 이미지는 아래와 같습니다.

 

인터넷 이미지소스에서 보정계수 계산 후 ->  좌 : 보정계수 적용 전(맥북카메라 그대로 출력), 우 : 보정계수 적용 후

 

물리적으로 이 현상의 차이를 생각해보면, 보정 계산을 할 때 중앙이 볼록한 형태의 이미지를 가지고 꼭짓점 부분을 펴서 평평하게 보정을 해줬습니다. 그 보정계수를 가지고, 원래 평평한 이미지(왜곡이 거의 없는 이미지)에 보정을 걸어줬으니, 보정계수의 특성이 반영되어 왜곡이 적은 이미지 중앙 부분은 그대로 형태를 유지하고, 이미지 꼭지점 부근으로 갈수록 이미지에 대한 보정이 증가하여, 오히려 중앙이 오목한 형태로 보이게 되고 이미지의 꼭지점 방향으로는 길게 늘어지는 형태의 왜곡이 발생한 것이 아닌가 하는 생각이 듭니다.

 

추후 차선 검출 부분에서 왜곡 보정 유무에 대한, 차선 검출 정확도 의존성이 어느 정도 나타날지 찾아보는 것도 좋을 것 같습니다.

 

👨🏻‍💻 CODE

보정계수를 계산하는 파트와 보정계수를 적용하는 파트의 두 개의 파일로 구성하였습니다.

 

📍findChessBoardCorners() 함수 :: 공식문서

        cvtColor(img, imgGray, COLOR_BGR2GRAY);
        bool found = findChessboardCorners(
            img,
            board_sz,
            corners,
            CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FILTER_QUADS);
        // find the corners base on board_sz(horizontal, vertical direction) size.
        // detected corner pixel is stored in corners array.
        // 마지막 파라미터 : to improve the chances of detecting the corners.

 

원본 이미지를 이진화(cvtColor) 후 findChessboardConrners 함수로 데이터를 피딩합니다. 함수명 그대로 체스보드의 코너(꼭짓점)를 찾아주고, 해당 좌표를 corners에 저장합니다. corners 변수는 아래와 같이 선언했습니다.

 

vector<Point2f> corners;

 

📍drawChessBoardCorners() 함수 :: 공식문서

if (found)
        {
            cornerSubPix(
                imgGray,
                corners,
                Size(11, 11),
                Size(-1, -1),
                TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, 30, 0.1));
            drawChessboardCorners(imgGray, board_sz, corners, found);
        }

 

이 함수 역시 함수명 그대로, 지정한 이미지 소스에 찾아준 corners 데이터를 참조하여, 코너 위치를 표시해줍니다. 체스보드에 픽셀을 표시하기 전에 cornerSubPix 함수가 사용되었고, 이 함수는 코너 픽셀이 가리키는 위치를 더 정확하게 찾아주는 역할을 합니다.

그리고 drawChessboardCorners함수에 사용된 board_sz는 다음과 같이 미리 선언 후 사용했습니다. 체스보드의 전체 꼭짓점의 패턴 사이즈를 나타냅니다.

 

Size board_sz = Size(numHorzCorner, numVertCorner);

 

📍undistort() 함수 :: 공식문서

  while(true)
        ...
        cap >> img;
        // img = imread("data/cb_src.png"); [when read a img file]
        undistort(img, imgUndistorted, intrinsicRead, distCoeffsRead);
        ...

 

위의 코드는 카메라 스트림(cap 변수)을 입력으로 받고 있습니다. 카메라의 보정계수를 미리 계산 후, 텍스트 파일로 작성 후 보정계수를 다시 읽어 들였습니다. 각각의 매트릭스 값은 intrinsicRead, distCoeffsRead 변수로 저장했으며, undistort함수에 전달해주었습니다.

 

📍전체 코드 내용은 아래에서 확인할 수 있습니다.(깃헙 링크)

반응형