프로그래밍/OpenCV

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

eteo 2022. 8. 9. 18:38
반응형

 

 

 

 

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

 

  • Mat : 이미지 또는 행렬 데이터를 담는 컨테이너 역할을 한다. 이미지는 결국 픽셀 값으로 구성된 2차원 행렬이므로, OpenCV에서는 이 모든 데이터를 Mat 객체로 표현함
Mat img(480, 640, CV_8UC3);

 

 

  • Scalar : 하나 이상의 채널 값을 갖는 상수 벡터로, 색상 초기화나 마스크 연산 등에 사용된다.
Scalar red(0, 0, 255); // BGR 순서로 Red

 

 

  • Point : 2차원 정수 좌표계를 표현하는 클래스로, 기존에는 Point2i로 정의되어 있었지만 보다 일반적인 이름인 Point로 다시 정의되어 사용됨.
Point pt(10, 20); // x=10, y=20인 점을 생성

 

 

  • Size  : 사각형 영역의 너비(width)와 높이(height)를 나타내는 클래스로, 2차원 크기를 표현할 때 사용됨
Size sz(100, 50); // 너비 100, 높이 50의 크기

 

 

  • Rect : 사각형 영역을 정수형 좌표 및 크기로 표현하는 클래스로, Rect2i에서 Rect로 다시 정의되어 사용됨
Rect rc(10, 10, 60, 40);
// 좌표 (10,10)에서 시작하여 너비 60, 높이 40인 사각형

 

 

  • RotatedRect : 회전된 사각형을 표현하는 클래스로, 임의의 각도로 회전된 상태를 표현할 수 있음
RotatedRect rr1(Point2f(40, 30), Size2f(40, 20), 30.f);
// 중심 좌표 (40,30), 크기 40x20, 시계방향 30도 회전된 사각형

 

 

  • Range : 정수 범위를 나타내는 클래스로, 배열 인덱스 또는 슬라이싱 범위를 표현할 때 자주 사용됨
Range r1(0, 10); 
// 0 이상 10 미만, 즉 [0, 1, ..., 9] 범위를 나타냄

 

 

  • String : std::string과 동일한 클래스로, OpenCV의 이전 버전과의 호환성을 위해 typedef std::string String; 으로 정의되어 사용됨. 문자열 생성 시 OpenCV에서 제공하는 format() 함수를 사용하면 C 스타일 포맷처럼 편리하게 문자열을 만들 수 있다.
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 파일을 차례로 불러옴

 

 

 

자세한 설명은 주석 처리함

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

 

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로 배우는 컴퓨터 비전과 머신러닝

반응형