Computer Vision and OpenCV [Day 7]

 Published On

8. 영역 기반 처리 이론 및 실습 2

8.1. 영역 기반 처리 실습

8.1.1. 모폴로지(morphology)

  • 모폴로지 연산은 영상 내부 객체의 형태와 구조를 분석하고 처리하는 기법
  • 그레이스케일 영상과 이진 영상에 모두 적용이 가능하지만, 주로 이진화된 영상에서 객체의 모양을 변형하는 용도로 사용
  • 주로 이진 영상에서 객체의 모양을 단순화시키거나 잡음을 제거하는 용도
  • 종류
    1) 침식
    • 객체 영역의 외곽을 골고루 깎아 내는 연산으로 전체적으로 객체 영역은 축소되고 배경은 확대됨
    • 구조 요소를 영상 전체에 대해 스캔하면서, 구조 요소가 객체 영역 내부에 완전히 포함될 경우 고정점 위치 픽셀을 255로 설정
    • 원래 영상에서 껍데기를 떼어내는 것, 아주 얇은 정보들(잡음일 경우가 많음)이 사라짐 -> 잡음 제거 효과
    • 입력은 0과 1만, 하나라도 0이면 0을 출력
    • 내부 잡음이 더 커질 수도 있는 단점이 있다.
    • 4방향보다 8방향 마스크가 더 침식이 됨

    2) 팽창

    • 객체 외곽을 확대하는 연산으로 객체 영역은 확대되고, 배경은 축소됨
    • 구조 요소를 영상 전체에 대해 이동하면서, 구조 요소와 객체 영역이 한 픽셀이라도 만날 경우 고정점 위치 픽셀을 255로 설정
    • 내부 잡음이 채워지면서 사라지지만, 외부의 얇은 잡음은 더 확대가 됨
  • 구조 요소는 원소값이 0 또는 1로 구성된 CV_8UC1 타입의 Mat 행렬로 표현된다.

8.1.2. 침식 연산

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

//마스크 원소와 마스크 범위 입력화소 간의 일치 여부 체크
bool check_match(Mat img, Point start, Mat mask, int mode = 0)
{
	for (int u = 0; u < mask.rows; u++) {
		for (int v = 0; v < mask.cols; v++) {
			Point pt(v, u);
			int m = mask.at<uchar>(pt); // 마스크 계수 	
			int p = img.at<uchar>(start + pt); // 해당 위치 입력화소 

			bool ch = (p == 255); // 일치 여부 비교(0이면 0, 255이면 1 -> 바이너리(0, 1) 만들어주기 위해서)
			// mode = 0, 하나라도 불일치, false
			// mode = 1, 하나라도 일치, flase
			if (m == 1 && ch == mode) return false;
		}
	}
	return true;
}

void erosion(Mat img, Mat& dst, Mat mask)
{
	dst = Mat(img.size(), CV_8U, Scalar(0));
	if (mask.empty())	mask = Mat(3, 3, CV_8UC1, Scalar(0));

	Point h_m = mask.size() / 2;

	for (int i = h_m.y; i < img.rows - h_m.y; i++) {
		for (int j = h_m.x; j < img.cols - h_m.x; j++)
		{
			Point start = Point(j, i) - h_m;
			// mode = 0 -> 모두 일치하면 true, 하나라도 불일치하면 false
			bool  check = check_match(img, start, mask, 0);
			// 모두 일치시(check값이 true인 경우) 255, 하나라도 불일치할 (check값이 false인 경우) 0
			dst.at<uchar>(i, j) = (check) ? 255 : 0;
		}
	}
}

int main()
{
	Mat image = imread("../image/morph_test1.jpg", 0);
	CV_Assert(image.data);

	Mat th_img, dst1, dst2;
	threshold(image, th_img, 128, 255, THRESH_BINARY); // 128보다 크면 255, 128보다 작으면 0으로 보낸다

	Matx < uchar, 3, 3> mask;
	mask << 0, 1, 0,
		1, 1, 1,
		0, 1, 0;

	erosion(th_img, dst1, (Mat)mask);
	morphologyEx(th_img, dst2, MORPH_ERODE, mask);

	imshow("image", image), imshow("이진 영상", th_img);
	imshow("User_erosion", dst1);
	imshow("OpenCV_erosion", dst2);

	waitKey();
	return 0;
}


8.1.3. 팽창 연산

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

// 팽창: mode = 1
bool check_match(Mat img, Point start, Mat mask, int mode)
{
	for (int u = 0; u < mask.rows; u++) {
		for (int v = 0; v < mask.cols; v++) {
			int m = mask.at<uchar>(Point(v, u));				// 마스크 계수 	
			int p = img.at<uchar>(start + Point(v, u));		// 해당 위치 입력화소 

			bool ch = (p == 255);				// 일치 여부 비교
			if (m == 1 && ch == mode) return false;
		}
	}
	return true;
}

void dilation(Mat img, Mat& dst, Mat mask)
{
	dst = Mat(img.size(), CV_8U, Scalar(0));
	if (mask.empty())	mask = Mat(3, 3, CV_8UC1, Scalar(0));

	Point h_m = mask.size() / 2;
	for (int i = h_m.y; i < img.rows - h_m.y; i++) {
		for (int j = h_m.x; j < img.cols - h_m.x; j++)
		{
			Point start = Point(j, i) - h_m;
			// mode = 1 -> 모두 불일치하면 true, 하나라도 일치하면 false
			bool  check = check_match(img, start, mask, 1);	// 원소 일치여부 비교
			// 모두 불일치시(check값이 true인 경우) 0, 하나라도 일치할 (check값이 false인 경우) 255
			dst.at<uchar>(i, j) = (check) ? 0 : 255;
		}
	}
}

int main()
{
	Mat image = imread("../image/morph_test1.jpg", 0);
	CV_Assert(image.data);

	Mat th_img, dst1, dst2;
	threshold(image, th_img, 128, 255, CV_THRESH_BINARY);


	Matx < uchar, 3, 3> mask;
	mask << 0, 1, 0,
		1, 1, 1,
		0, 1, 1;

	dilation(th_img, dst1, (Mat)mask);
	morphologyEx(th_img, dst2, MORPH_DILATE, mask);

	imshow("image", image);	imshow("User_dilation", dst1);
	imshow("OpenCV_dilation", dst2);
	waitKey();
	return 0;
}


8.1.4. 열림 연산과 닫힘 연산

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

bool check_match(Mat img, Point start, Mat mask, int mode = 0)
{
	for (int u = 0; u < mask.rows; u++) {
		for (int v = 0; v < mask.cols; v++) {
			Point pt(v, u);
			int m = mask.at<uchar>(pt);					// 마스크 계수 	
			int p = img.at<uchar>(start + pt);			// 해당 위치 입력화소 

			bool ch = (p == 255);				// 일치 여부 비교
			if (m == 1 && ch == mode)	return  false;
		}
	}
	return true;
}

void erosion(Mat img, Mat& dst, Mat mask)
{
	dst = Mat(img.size(), CV_8U, Scalar(0));
	if (mask.empty())	mask = Mat(3, 3, CV_8UC1, Scalar(0));

	Point h_m = mask.size() / 2;
	for (int i = h_m.y; i < img.rows - h_m.y; i++) {
		for (int j = h_m.x; j < img.cols - h_m.x; j++)
		{
			Point start = Point(j, i) - h_m;
			bool  check = check_match(img, start, mask, 0);
			dst.at<uchar>(i, j) = (check) ? 255 : 0;
		}
	}
}

void dilation(Mat img, Mat& dst, Mat mask)
{
	dst = Mat(img.size(), CV_8U, Scalar(0));
	if (mask.empty())	mask = Mat(3, 3, CV_8UC1, Scalar(0));

	Point h_m = mask.size() / 2;
	for (int i = h_m.y; i < img.rows - h_m.y; i++) {
		for (int j = h_m.x; j < img.cols - h_m.x; j++)
		{
			Point start = Point(j, i) - h_m;
			bool  check = check_match(img, start, mask, 1);
			dst.at<uchar>(i, j) = (check) ? 0 : 255;
		}
	}
}

// 열림 연산: 침식 -> 팽창
void opening(Mat img, Mat& dst, Mat mask)
{
	Mat tmp; // 임시변수
	erosion(img, tmp, mask);
	dilation(tmp, dst, mask);
}

// 닫힘 연산: 팽창 -> 침식
void closing(Mat img, Mat& dst, Mat mask)
{
	Mat tmp;
	dilation(img, tmp, mask);
	erosion(tmp, dst, mask);
}

int main()
{
	Mat image = imread("../image/morph_test1.jpg", 0);
	CV_Assert(image.data);
	Mat th_img, dst1, dst2, dst3, dst4;
	threshold(image, th_img, 128, 255, THRESH_BINARY);

	Matx < uchar, 3, 3> mask;
	mask << 0, 1, 0,
		1, 1, 1,
		0, 1, 0;

	opening(th_img, dst1, (Mat)mask);
	closing(th_img, dst2, (Mat)mask);
	morphologyEx(th_img, dst3, MORPH_OPEN, mask);
	morphologyEx(th_img, dst4, MORPH_CLOSE, mask);

	imshow("User_opening", dst1), imshow("User_closing", dst2);
	imshow("OpenCV_opening", dst3), imshow("OpenCV_closing", dst4);
	waitKey();
	return 0;
}


8.1.5. 모폴로지(morphology) 심화 예제

// 팽창  침식
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

bool check_match(Mat img, Point start, Mat mask, int mode = 0)
{
	for (int u = 0; u < mask.rows; u++) {
		for (int v = 0; v < mask.cols; v++) {
			Point pt(v, u);
			int m = mask.at<uchar>(pt);					// 마스크 계수 	
			int p = img.at<uchar>(start + pt);			// 해당 위치 입력화소 

			bool ch = (p == 255);				// 일치 여부 비교
			if (m == 1 && ch == mode)	return  false;
		}
	}
	return true;
}

void erosion(Mat img, Mat& dst, Mat mask)
{
	dst = Mat(img.size(), CV_8U, Scalar(0));
	if (mask.empty())	mask = Mat(3, 3, CV_8UC1, Scalar(0));

	Point h_m = mask.size() / 2;
	for (int i = h_m.y; i < img.rows - h_m.y; i++) {
		for (int j = h_m.x; j < img.cols - h_m.x; j++)
		{
			Point start = Point(j, i) - h_m;
			bool  check = check_match(img, start, mask, 0);
			dst.at<uchar>(i, j) = (check) ? 255 : 0;
		}
	}
}

void dilation(Mat img, Mat& dst, Mat mask)
{
	dst = Mat(img.size(), CV_8U, Scalar(0));
	if (mask.empty())	mask = Mat(3, 3, CV_8UC1, Scalar(0));

	Point h_m = mask.size() / 2;
	for (int i = h_m.y; i < img.rows - h_m.y; i++) {
		for (int j = h_m.x; j < img.cols - h_m.x; j++)
		{
			Point start = Point(j, i) - h_m;
			bool  check = check_match(img, start, mask, 1);
			dst.at<uchar>(i, j) = (check) ? 0 : 255;
		}
	}
}

int main()
{
	while (1)
	{
		int no;
		cout << "차량 영상 번호( 0:종료 ) : ";
		cin >> no;
		if (no == 0) break;

		string fname = format("../test_car/%02d.jpg", no);
		Mat image = imread(fname, 1);
		if (image.empty()) {
			cout << to_string(no) + "번 영상 파일이 없습니다. " << endl;
			continue;
		}

		Mat gray, sobel, th_img, morph;
		Mat kernel(5, 31, CV_8UC1, Scalar(1));		// 열림 연산 마스크
		cvtColor(image, gray, CV_BGR2GRAY);		// 명암도 영상 변환

		blur(gray, gray, Size(5, 5));					// 블러링
		Sobel(gray, gray, CV_8U, 1, 0, 3);			// 소벨 에지 검출

		threshold(gray, th_img, 120, 255, THRESH_BINARY);	// 이진화 수행
//		morphologyEx(th_img, morph, MORPH_CLOSE, kernel);	// 닫힘 연산 수행

		Mat tmp;
		morphologyEx(th_img, tmp, MORPH_DILATE, kernel);
		morphologyEx(tmp, morph, MORPH_ERODE, kernel);

		imshow("image", image);
		imshow("중간 영상", tmp);
		imshow("이진 영상", th_img), imshow("닫힘 연산", morph);
		waitKey();
	}
	return 0;
}


8.1.6. 연습문제: Connected Component Labeling

// 흑백으로 바꾸기, 가우시안 블러, threshold로 쳐내기, 열림 연산으로 노이즈 제거
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main() {

	// 1. 이미지 불러오기
	Mat image = imread("../image/coins.jpg", 1);
	CV_Assert(image.data);

	Mat kernel(5, 5, CV_8UC1, Scalar(1));

	// 2. 흑백 영상
	Mat gray;
	cvtColor(image, gray, CV_BGR2GRAY);

	// 3. 가우시안 블러 처리
	Mat blur;
	GaussianBlur(gray, blur, Size(9, 9), 3.5);

	// 4. threshold 처리
	Mat thresh;
	threshold(blur, thresh, 70, 255, THRESH_BINARY);
	
	// 5. 열림 연산
	Mat morph;
	morphologyEx(thresh, morph, MORPH_OPEN, kernel);

	imshow("image", image);
	imshow("gray", gray);
	imshow("blur", blur);
	imshow("thresh", thresh);
	imshow("morph", morph);

	// 5. connectedComponents를 이용해 동전 개수 세기
	Mat labelImage(morph.size(), CV_32S);
	int nLabels = connectedComponents(morph, labelImage, 8);
	cout << nLabels << endl;

	// 6. 라벨링된 이미지를 각기 다른 컬러로 보여주기
	labelImage.convertTo(labelImage, CV_8U);
	labelImage = labelImage * 10; // 1부터 11까지 라벨링된 동전들은 각각 밝기값이 1부터 11까지이지만, 밝기가 너무 어두워서 곱해준다.
	imshow("labelImage", labelImage);

	waitKey();
	return 0;

}


8.1.7. Otsu’s Thresholding

// 임계값 이상은 물체, 미만은 배경으로
// 모든 밝기값에 대해서 forground, background로 나누서 분산을 각각 구해서 제일 작은 값의 T값을 threshold로 출력한다.
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

String	title = "Coins";

void  calc_Histo(const Mat& image, Mat& hist, int bins, int range_max = 256)
{
	int		histSize[] = { bins };			// 히스토그램 계급개수
	float   range[] = { 0, (float)range_max };		// 히스토그램 범위
	int		channels[] = { 0 };				// 채널 목록
	const float* ranges[] = { range };

	calcHist(&image, 1, channels, Mat(), hist, 1, histSize, ranges);
}

void draw_histo(Mat hist, Mat& hist_img, Size size = Size(256, 200))
{
	hist_img = Mat(size, CV_8U, Scalar(255));
	float  bin = (float)hist_img.cols / hist.rows;
	normalize(hist, hist, 0, size.height, NORM_MINMAX);

	for (int i = 0; i < hist.rows; i++)
	{
		float  start_x = (i * bin);
		float  end_x = (i + 1) * bin;
		Point2f pt1(start_x, 0);
		Point2f pt2(end_x, hist.at <float>(i));

		if (pt2.y > 0)
			rectangle(hist_img, pt1, pt2, Scalar(0), -1);
	}
	flip(hist_img, hist_img, 0);
}

int main()
{
	Mat image = imread("../image/coins.jpg", 1);
	CV_Assert(image.data);

	Mat gray, th_img;
	cvtColor(image, gray, CV_BGR2GRAY);				// 명암도 변환 
	GaussianBlur(gray, gray, Size(5, 5), 2, 2);				// 블러링

	// 433draw_histogram.cpp
	Mat hist, hist_img;
	calc_Histo(gray, hist, 256);
	draw_histo(hist, hist_img);

	imshow("hist_img", hist_img);
	imshow("gray_image", gray);

	// otsu's thresholding
	int iTH, i, j, iMinTH = -1;
	float ave, sum, varBG, varOB, Wb, Wf, fMin = 10000;
	for (iTH = 0; iTH <= 255; iTH++)
	{
		// background < iTH
		sum = 0;		ave = 0;	varBG = 0;
		for (i = 0; i < iTH; i++)
		{
			ave += i * hist.at<float>(i);
			sum += hist.at<float>(i);
		}
		ave = ave / (float)sum;
		Wb = sum / (float)(gray.rows * gray.cols);
		for (i = 0; i < iTH; i++)
			varBG += (i - ave) * (i - ave) * hist.at<float>(i);
		varBG = varBG / (float)sum;

		// object >= iTH
		sum = 0;		ave = 0;	varOB = 0;
		for (i = iTH; i <= 255; i++)
		{
			ave += i * hist.at<float>(i);
			sum += hist.at<float>(i);
		}
		ave = ave / (float)sum;
		Wf = sum / (float)(gray.rows * gray.cols);
		for (i = iTH; i <= 255; i++)
			varOB += (i - ave) * (i - ave) * hist.at<float>(i);
		varOB = varOB / (float)sum;

		//		cout << varBG << '\t' << Wb << '\t' << varOB << '\t' << Wf << '\n';

		if (fMin > (Wf * varOB + Wb * varBG))
		{
			fMin = Wf * varOB + Wb * varBG;
			iMinTH = iTH;
		}
	}

	cout << iMinTH << '\n';

	threshold(gray, th_img, iMinTH, 255, THRESH_BINARY); // 이진화
	// threshold(gray, th_img, 130, 255, THRESH_BINARY | THRESH_OTSU); // 이진화
	morphologyEx(th_img, th_img, MORPH_OPEN, Mat());// , Point(-1, -1), 1);  // 열림연산
	Mat labels = Mat(th_img.size(), CV_32S);
	int iCC = connectedComponents(th_img, labels, 8, CV_32S);
	cout << iCC - 1 << '\n';
	labels.convertTo(labels, CV_8U);
	labels = labels * 10;
	imshow("image", image);
	imshow(title, th_img);
	imshow("labels", labels);
	waitKey();
	return 0;
}



Tags: OpenCV

Comments:

comments powered by Disqus

© 2021 - MH.Ji. All rights reserved
Built using Jekyll