내맘대로 공부기록.

[ C++ ]

[ openCV | C++ ] ( 4 / 6 ) 차선검출. 이미지 전 처리. 정규화 및 이미지 픽셀(HLS 1개, LAB 1개) 합치기 작업.

fwanggus 2021. 8. 28. 22:24

차선 검출을 위한 이미지 전 처리해보기 🛣


⚙️  기본 설명  ⚙️

 

  1. 왜곡 제거(카메라 보정) 👍
  2. Perspective Transform(원본 이미지 ⏩  2D)
  3. Color Filtering(HLS, LAB color space)
  4. 픽셀 값 정규화(feat. 최댓값) 및 이미지 픽셀(HLS 1개, LAB 1개)  합치기.
  5. Window Search
  6. Show Detected Lines and Info.

개념  🧐

3. Color Filtering(HLS, LAB color space)에서 필터링 한 각 이미지는 원(One) 채널 픽셀 데이터를 가지고 있으며, 이 이미지 요소가 소스가 됩니다. 

소스 이미지의 픽셀 분포를 정리하기 위해서, 해당 이미지의 픽셀 최댓값을 이용하여 정규화해줍니다. 그리고 thresholding 작업을 위한 픽셀의 경곗값을 설정하고 황색(LAB 컬러 스페이스의 "B"채널), 백색(HLS 컬러 스페이스의 "L"채널) 라인에 대한 픽셀 데이터의 위치를 획득하여 하나의 이미지로 병합하여 표현할 수 있습니다.

 

방법  + DEMO. 🛠🚀

 

순서는 다음과 같습니다.

 

  1. 이미지 픽셀의 최댓값 구하기
  2. (255/최댓값) 의 비율로 이미지 전체 픽셀을 정규화
  3. 특정 경계값을 설정하여, thesholding 적용
  4. 두 이미지의 픽셀 데이터를 하나의 이미지로 병합

1. 이미지 픽셀의 최댓값 구하기

minMaxLoc 함수를 사용해서 최대, 최소값을 구할 수 있으며, 그 인덱스 값(픽셀 위치)도 찾아낼 수 있습니다. 하지만 이 단계에서는 최댓값 자체만 사용합니다. 특징으로는 출력값이 포인터 형태로 반환되기 때문에 변수를 대입할 때 그 점을 인식하고 대입하시기 바랍니다.

 

opencv 공홈 링크 🔗

 

OpenCV: Operations on arrays

Divides a multi-channel array into several single-channel arrays. The function cv::split splits a multi-channel array into separate single-channel arrays: If you need to extract a single channel or do some other sophisticated channel permutation, use mixCh

docs.opencv.org

 

아래의 코드와 같이 & 를 변수 앞에 붙여줌으로써 포인터 주소를 인수로 전달해주었고요. 함수가 콜 되면 최대,최소값에 대한 정보를 전달한 변수의 주소로 리턴해줍니다.

 

	...
    
	double minVal, maxVal;
	Point minLoc, maxLoc;
    
	...
    
	minMaxLoc(imgSrc, &minVal, &maxVal, &minLoc, &maxLoc);

	...

 

2. (255/최댓값) 의 비율로 이미지 전체 픽셀을 정규화

정규화는 스케일이 다른 두 대상을 비교하거나, 데이터의 분포를 사용자의 의도에 맞게 확인하는 용도로써 활용될 수 있습니다. 결과부터 얘기하자면, 이 과정에서는 픽셀 값을 최댓값에 맞춰서 정규화시키는 개념으로 접근하고 있습니다. 즉, 이미지의 최댓값을 255(이미지에서 표현할 수 있는 픽셀의 최대값) 로 스케일 업하고, 나머지 픽셀에 대해서도 같은 비율로 맞춰주는 작업입니다. 

 

위에서 계산한 최대값을 이용하여 ( 255 / maxVal ) 상수 값을 이미지 변수에 곱해줍니다. Mat 변수는 일반 상수의 경우, 모든 픽셀에 대하여 더하거나, 빼거나, 곱하거나, 나누어 줄 수 있습니다. 단, 나누는 경우는 0이 아닌 경우만 가능하겠군요.

 

	...
    
	Mat imgNormal;
    
	...
    
	// make normalized img.
	imgNormal = (255 / maxVal) * imgSrc;
    
	...

 

3. 특정 경곗값을 설정하여, thesholding 적용

특정 경계값을 기준으로 조건을 만족하는 픽셀에 대해서, 해당 위치의 픽셀 값을 사용자가 원하는 값으로 설정해줄 수 있습니다. opencv 에서 제공하는 thresholding 패턴으로는 여섯 가지 정도가 있으며, 사용자의 의도에 맞게 패턴을 정해서 threshold 작업을 해줄 수 있습니다.

 

공홈의 해당 부분은 다음과 같습니다.

 

OpenCV: Miscellaneous Image Transformations

maskOperation mask that should be a single-channel 8-bit image, 2 pixels wider and 2 pixels taller than image. Since this is both an input and output parameter, you must take responsibility of initializing it. Flood-filling cannot go across non-zero pixels

docs.opencv.org

 

아래 모식도와 각 타입에 따른 조건을 참고하세요.

Threshold Type, https://docs.opencv.org/3.4/d7/d1b/group__imgproc__misc.html#gaa9e58d2860d4afa658ef70a9b1115576

 

이번 작업에서는 지정한 경곗값보다 픽셀 값이 클 경우 픽셀 값을 1로, 그렇지 않을 경우는 0이 되게 설정하는 'THRESH_BINARY' 타입을 사용했습니다.

 

소스 이미지로부터 threshold 가 적용된 결과값은 새로운 이미지 변수로써 받아 줘야 합니다. 여기서, 결과값을 받아줄 이미지 변수는 소스 이미지와 크기, 타입(컬러 채널 종류 및 개수) 이 같아야 하며, 모든 픽셀 요소가 0 인 이미지 변수로서 정의해줍니다. 픽셀요소가 모두 0인 이미지를 정의하기 위해 Mat :: zeros 함수를 이용했습니다.

 

	...

	int lowThres = 220;
	
	...
    
	Mat imgOut = Mat::zeros(imgNormal.rows, imgNormal.cols, imgNormal.type());
	threshold(imgNormal, imgOut, lowThres, 1, THRESH_BINARY);

	return imgOut;

	...

 

thresholding 이 적용된 이미지를 imshow 함수로 확인해볼 수 있는데요. 리턴된 이미지를 그대로 출력하면 아무것도 보이지 않습니다. 0~255 픽셀의 값 중 최댓값이 1 인 이미지가 되었기 때문에 아주 약한 강도로 픽셀의 값이 표현되고 있는 상태입니다. 사람의 눈으로 찾아내긴 힘들 것 같습니다. 그래서 출력 시에는 이미지에 255 값을 곱해서 출력해 보면 어느 위치의 픽셀이 thresholding 되었는지 쉽게 확인할 수 있을 겁니다.

 

4. 두 이미지의 픽셀 데이터를 하나의 이미지로 병합

자, 이제 HLS컬러 스페이스의 'L', LAB 컬러 스페이스의 'B' 채널을 뽑아냈기 때문에, 합쳐주는 작업을 진행합니다. 그렇게 함으로써, 흰색 차선 요소(L 채널)와 중앙선 또는 갓길 경계선을 표현하는 황색 차선 요소(B 채널)의 픽셀 데이터를 한 이미지 소스로서 관리할 수 있게 됩니다.

 

방법은 간단합니다. 두 이미지의 픽셀 위치에 접근해서 해당 픽셀 값이 1이 되면 병합할 이미지의 같은 위치에 그대로 할당해주면 됩니다. 

 

	...
	//hls, lab는 thresholding이 적용된 이미지 소스를 의미함.
    
	Mat imgOut = Mat::zeros(hls.rows, hls.cols, hls.type());
	for (int i = 0; i < imgOut.rows; i++)
	{
		for (int j = 0; j < imgOut.cols; j++)
		{
			if (hls.at<uint8_t>(i, j) == 1 || lab.at<uint8_t>(i, j) == 1)
			{
				imgOut.at<uint8_t>(i, j) = 1;
			}
		}
	}
	return imgOut;
	    
	...

 

작업 결과 😎 📸

왼: BIRD VIEW 이미지, 중간: LAB의 B채널 필터, 오:HLS의 L채널 필터
필터링 된 두 채널을 하나의 이미지로 병함(픽셀 값을 재 할당)

깃헙 링크 🔗 

	Mat normalize_HLS_L(Mat unWarp);
	Mat normalize_LAB_B(Mat unWarp);
	Mat combine_threshold(Mat gray);
반응형