본문 바로가기
OpenCV

OpenCV ] 에서 사용하는 주요 클래스들 (C++)

by eteo 2022. 8. 9.

예제 코드 출처 : OpenCV 4로 배우는 컴퓨터 비전과 머신러닝

 

 

 

유용하게 사용되는 기본 자료형 클래스

 

Point_ : 2차원 정수 좌표계에서 점의 좌표를 표현하는 클래스는 Point2i 에서 다시 Point로 재정의 됨

 

Size_  : 사각형 영역의 가로 세로 크기를 나타내는 width와 height 멤버변수를 가지고 있음

 

Rect_ : 정수형 사각형 정보를 표현하는 Rect2i 는 다시 Rect 로 재정의됨. 정수형 멤버변수 x, y, width, height를 가지고 있으며

Rect rc(10,10,60,40);
// (10,10) 좌표부터 그려지는 크기가 60x40인 사각형

 

RotatedRect : 회전된 사각형을 표현하는 클래스. 회전된 사각형의 중심점 좌표를 나타내는 center(Point2f 클래스 사용), 사각형의 크기를 나타내는 size(Size2f 클래스 사용), 회전 각도 angle(float 자료형 사용) 을 멤버변수로 가짐.

RotatedRect rr1(Point2f(40,20), Size2f(40,20), 30.f);
// 중심좌표가 (40,30) 크기는 40x20, 시계방향으로 30도 만큼 회전된 사각형

 

 

Range : 정수 단위의 범위 또는 구간을 표현하는 클래스. 범위의 시작과 끝을 나타내는 start 와 end 멤버변수를 가지고 있음

Rnage r1(0, 10);
// 0부터 9까지의 범위를 표현하고 10은 포함하지 않음

 

String : std::string과 완전히 같은 클래스인데 이전 버전과 호환성을 위해 typedef std::string String; 해두고 String으로 사용. 특정한 형식의 문자열을 만들 때는 OpenCV에서 제공하는 format() 함수를 사용함. 

Mat imgs[3];
for(int i=0; i<3; i++){
	String filename = format("test%02d.bmp", i+1);
    imgs[i] = imread(filename);
}
// test01.bmp, test02.bmp, test03.bmp 파일을 불러오기

 

 

 

자세한 설명은 주석 처리함

 

1. 행렬의 생성과 초기화

 

void MatOp1() {

	Mat img1;	// empty matrix 생성

	// Mat::Mat(int rows, int cols, int type);
	Mat img2(480, 640, CV_8UC1);	// unsigned char, 1-channel
	Mat img3(480, 640, CV_8UC3);	// unsigned char, 3-channels

	// Mat::Mat(Size size, int type);
	Mat img4(Size(640,480),CV_8UC3);	// Size(width, height)

	// Mat::Mat(int rows, int cols, int type, const Scalar& s);
	// 4번째 매개변수로 원소의 초깃값을 성정하는 인자 s가 옴
	Mat img5(480, 640, CV_8UC1, Scalar(128));	// initial values : 128
	Mat img6(480, 640, CV_8UC3, Scalar(0, 0, 255));	// initial values : red

	// 모든 원소가 0으로 초기화 된 행렬을 만드는 함수
	Mat mat1 = Mat::zeros(3, 3, CV_32SC1);	// short, 0's matrix
	// 모든 원소가 1로 초기화 된 행렬을 만드는 함수
	Mat mat2 = Mat::ones(3, 3, CV_32FC1);	// float, 0's matrix
	// 단위 행렬을 만드는 함수
	Mat mat3 = Mat::eye(3, 3, CV_32FC1);	// identity matrix

	// 외부 메모리 공간을 참조하여 Mat 객체를 생성하는 케이스, 하나의 메모리 공간을 서로 공유함
	float data[] = { 1,2,3,4,5,6 };
	Mat mat4(2, 3, CV_32FC1, data);

	// Mat_ 클래스는 Mat 객체와 상호 변환이 가능하고 << , 연산자를 이용해 원소 값을 설정하는 인터페이스를 제공
	Mat mat5 = (Mat_<float>(2, 3) << 1, 2, 3, 4, 5, 6);	// 2x3 크기의 여섯개 원소를 갖는 행렬, 아래와 같음
	//Mat_<float> mat5_(2,3);
	//mat5_ << 1,2,3,4,5,6;
	//Mat mat5 = mat5_;

	// Mat_ 클래스의 이니셜라이저를 사용한 형태
	Mat mat6 = Mat_<uchar>({ 2,3 }, { 1,2,3,4,5,6 });

	// 비어 있거나 이미 생성된 객체에 새로운 행렬 할당하기 Mat::create()
	mat4.create(256, 256, CV_8UC3);	// uchar, 3-channels
	mat5.create(4, 4, CV_32FC1);	// float, 1-channel

	// 이미 생성된 행렬의 전체 원소 값을 초기화하고 싶다면 = 연산자 또는 Mat::setTo() 함수로 가능
	mat4 = Scalar(255, 0, 0);
	// Mat::setTo() 의 두번 째 매개변수는 mask 행렬로 생략 가능
	mat5.setTo(1.f);
}

 

 

 

 

2. 행렬의 복사

 

void MatOp2() {
	
	Mat img1 = imread("dog.bmp");

	Mat img2 = img1;	// 얕은 복사
	Mat img3;
	img3 = img1;		// 얕은 복사

	Mat img4 = img1.clone();	// 깊은 복사
	Mat img5;
	img1.copyTo(img5);			// 깊은 복사

	img1.setTo(Scalar(0, 255, 255));	// yellow

	imshow("img1", img1);
	imshow("img2", img2);
	imshow("img3", img3);
	imshow("img4", img4);
	imshow("img5", img5);

	waitKey();
	destroyAllWindows();

}

 

 

 

결과창

 

 

 

 

 

 

3. 부분 행렬 추출

 

void MatOp3() {

	Mat img1 = imread("cat.bmp");

	if (img1.empty()) {
		cerr<<"Image load failed!"<<endl;
	}

	//Mat img2 = img1(Rect(220, 120, 340, 240));	// (220, 120) 좌표부터 340x240 크기만큼의 사각형 부분 추출
	// 단 얕은 복사가 진행됨	

	Mat img3 = img1(Rect(220, 120, 340, 240)).clone();	// 깊은 복사

	img3 = ~img3;	// 반전하면 255 의 보수값이 들어감

	imshow("img1", img1);
	//imshow("img2", img2);
	imshow("img3", img3);

	//if (img2.isSubmatrix())
	//	cout << "img2 is a submatrix" << endl;
	//else 
	//	cout << "img2 is not a submatrix" << endl;

	waitKey();
	destroyAllWindows();
}

 

이 같은 부분 행렬 추출(부분 영상 참조) 기능은 입력 영상에 사각형 모양의 관심 영역 (ROI, Region of Interest)를 설정하는 용도로 사용됨.

 

주의 점은 Mat 클래스의 괄호 연산자를 이용하여 얻은 부분 영상은 픽셀 데이터를 공유하는 얕은 복사라서 부분 영상의 픽셀 값을 변경하면 원본 영상의 픽셀 값도 함께 변경됨.

 

독립된 메모리 영역을 확보하는 깊은 복사로 부분 영상을 추출하려면 Mat::clone() 함수를 함께 사용해야 함.

 

영상의 반전은 ~ 연산자를 붙이면 되고 255의 보수값으로 변환됨.

 

 

결과창

 

 

 

 

 

 

4. 행렬의 원소 값 참조

 

void MatOp4() {	

	Mat mat1 = Mat::zeros(3, 4, CV_8UC1);

	// Mat::at( ) 함수 사용 방법
	for (int j = 0; j < mat1.rows ; j++) {
		for (int i = 0; i < mat1.cols; i++) {
			mat1.at<uchar>(j, i)++;
		}
	}

	// Mat::ptr( ) 함수 사용 방법
	for (int j = 0; j < mat1.rows; j++) {
		uchar* p = mat1.ptr<uchar>(j);
		for (int i = 0; i < mat1.cols; i++) {
			p[i]++;
		}
	}

	// MatIterator_ 반복자 사용 방법
	for (MatIterator_<uchar> it = mat1.begin<uchar>(); it != mat1.end<uchar>(); it++) {
		(*it)++;
	}

	cout << "mat1:\n" << mat1 << endl;
    
}

 

at, ptr, MatIterator 모두 전부 얕은 복사가 진행되었기 때문에 결과값이 3임

 

속도는 Mat::ptr() 함수가 빠르지만 행 단위가 아니라 임의 좌표 원소에 빈번하게 접근하는 경우는 Mat::at() 함수를 사용하는 것이 편리함.

함수 인자 값이 행렬의 크기를 벗어나는 일이 없도록 MatIterator를 쓰기도 함

 

결과창

 

 

 

 

 

 

행렬 정보 참조하기

 

void MatOp5() {

	//Mat img1 = imread("lenna.bmp");
	Mat img1 = imread("lenna.bmp", IMREAD_GRAYSCALE);	// 흑백으로 불러오기
	
	cout << "Width: " << img1.cols << endl;
	cout << "Height: " << img1.rows << endl;
	cout << "Channels: " << img1.channels() << endl;

	if (img1.type() == CV_8UC1)
		cout << "img1 is a grayscale image" << endl;
	else if(img1.type()==CV_8UC3)
		cout << "img1 is a truecolor image" << endl;

	//imshow("img1", img1);

	float data[] = { 2.f, 1.414f, 3.f, 1.732f };
	Mat mat2(2, 2, CV_32FC1, data);
	cout << "mat2: \n" << mat2 << endl;

}

 

Mat::cols 열 개수, Width

Mat::rows 행 개수, Height

 

<< 연산자를 이용해 std::cout 표준 출력 스트림으로 행렬을 전달하면 아래 형태로 출력됨.

대괄호 안에 행렬이 출력되고 각 행은 세미콜론으로 구분됨.

 

 

 

결과창

 

 

 

 

 

 

5. 행렬 연산

 

void MatOp6() {

	float data[] = { 1,1,2,3 };
	Mat mat1(2, 2, CV_32FC1, data);
	cout << "mat1:\n" << mat1 << endl;

	Mat mat2 = mat1.inv();	// 역행렬. 디폴트 매개변수가 DECOMP_LU 가우스 소거법 사용
	cout << "mat2:\n" << mat2 << endl;

	cout << "mat1.t():\n" << mat1.t() << endl;	// 전치행렬
	cout << "mat1 + 3:\n" << mat1 + 3 << endl;
	cout << "mat1 + mat2:\n" << mat1 + mat2 << endl;
	cout << "mat1 * mat2:\n" << mat1 * mat2 << endl;	// 행렬 * 역행렬 = 단위행렬

}

 

 

 

 

 

결과창

 

 

 

 

 

6. 크기 및 타입 변환 함수

 

void MatOp7() {

	Mat img1 = imread("lenna.bmp", IMREAD_GRAYSCALE);

	Mat img1f;
	// Mat::converTo(OutputArray m, int rtype ...) Mat 클래스의 크기 또는 타입 변화시키는 함수
	img1.convertTo(img1f, CV_32FC1);

	uchar data1[] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	Mat mat1(3, 4, CV_8UC1, data1);

	// Mat::reshape(int cn, int rows=0) 채널은 그대로 행을 1개로 변경
	Mat mat2 = mat1.reshape(0, 1);
	cout << "mat1:\n" << mat1 << endl;
	cout << "mat2:\n" << mat2 << endl;



	Mat mat3 = Mat::ones(1, 4, CV_8UC1) * 255;
	// 이미 존재하는 행렬에 원소 데이터 추가하기. 단 기존 행렬과 열 개수가 같아야함
	mat1.push_back(mat3);
	cout << "mat1:\n" << mat1 << endl;

	// Mat::resize(size_t sz, const Scalar& s); 새로운 행 개수와 새로 생성되는 행 원소의 초기값 지정해 변경
	mat1.resize(6, 100);
	cout << "mat1:\n" << mat1 << endl;

	// 행 제거. 단 기존 행 개수보다 크면 안됨
	mat1.pop_back(3);
	cout << "mat1:\n" << mat1 << endl;

}

 

결과창

 

 

 

 

 

 

 

7. Vec과 Scalar 클래스

 

void MatOp8() {

	Vec3b p1, p2(0, 0, 255);
	p1[0] = 100;	// p1.val[0] 으로 사용해도 되지만 [] 연산자가 재정의 되어있어 이런방식으로 많이 씀

	cout << "p1: " << p1 << endl;
	cout << "p2: " << p2 << endl;

	Scalar gray = 128;
	cout << "gray: " << gray << endl;	// Scalar는 4개의 멤버변수를 가지고 있는 클래스

	Scalar yellow(0, 255, 255);
	cout << "yellow: " << yellow << endl;

	Mat img1(256, 256, CV_8UC3, yellow);

	for (int i = 0; i < 4; i++)
		cout << yellow[i] << endl;	// [] 연산자가 재정의 되어있음

	imshow("img1", img1);
	waitKey();
	destroyAllWindows();
}

 

Vec<uchar, 3> 형식은 Vec3b 로 재정의 되어있고 3채널 컬러 영상의 픽셀 값을 표현하는 용도로 자주 사용됨.

 

Scalar와의 차이점은 Scalar 클래스는 크기가 4인 double 형 배열 val을 멤버변수로 가지고 있는 자료형인 반면 Vec 클래스는 템플릿으로 정의되어 있어서 Vec2w, Vec4i 등 채널(num of data)와 자료형을 지정할 수 있음.

 

Scalar는 상수값을 저장하는 용도, Vec 는 변수 개념 용도로 많이 쓰임

 

 

 

결과창

 

 

 

 

 

8. InputArray 와 OutputArray

 

void printMat(InputArray _mat) {
	Mat mat = _mat.getMat();
	cout << mat << endl;
}

void MatOp9() {
	uchar data1[] = { 1,2,3,4,5,6 };
	Mat mat1(2, 3, CV_8U, data1);
	printMat(mat1);

	vector<float> vec1 = { 1.2f, 3.4f, -2.1f };
	printMat(vec1);
}

OpenCV에선 InputArray / OutputArray / InputOuputArray 타입의 인자 또는 리턴값을 사용하는 함수를 자주 볼 수 있는데 그런 경우 Mat 클래스 객체 또는 vector<T> 타입의 변수를 전달하거나 대입해줘야 함

 

 

예제 코드 출처 : OpenCV 4로 배우는 컴퓨터 비전과 머신러닝