Mat 클래스 기초 사용법

영상의 생성과 초기화

  • Mat 클래스 객체의 생성과 초기화 예제 코드
void MatOp1()
{
	Mat img1; 	// empty matrix

	Mat img2(480, 640, CV_8UC1);		// unsigned char, 1-channel
	Mat img3(480, 640, CV_8UC3);		// unsigned char, 3-channels
	Mat img4(Size(640, 480), CV_8UC3);	// Size(width, height)

	Mat img5(480, 640, CV_8UC1, Scalar(128));		// 초기값을 지정 128
	Mat img6(480, 640, CV_8UC3, Scalar(0, 0, 255));	// 초기값을 지정 red

	Mat mat1 = Mat::zeros(3, 3, CV_32SC1);	// 0's matrix
	Mat mat2 = Mat::ones(3, 3, CV_32FC1);	// 1's matrix
	Mat mat3 = Mat::eye(3, 3, CV_32FC1);	// identity matrix

	float data[] = {1, 2, 3, 4, 5, 6};
	Mat mat4(2, 3, CV_32FC1, data);

	Mat mat5 = (Mat_<float>(2, 3) << 1, 2, 3, 4, 5, 6);
	Mat mat6 = Mat_<uchar>({2, 3}, {1, 2, 3, 4, 5, 6});

	mat4.create(256, 256, CV_8UC3);	// uchar, 3-channels
	mat5.create(4, 4, CV_32FC1);	// float, 1-channel

	mat4 = Scalar(255, 0, 0);
	mat5.setTo(1.f);
}

영상의 참조와 복사

  • Mat 객체에 대해 = 연산자는 참조(얕은 복사)를 수행
  • Mat::clone() 또는 Mat::copyTo() 함수를 이용하여 깊은 복사를 수행
void MatOp2()
{
	Mat img1 = imread("dog.bmp");

	Mat img2 = img1;
	Mat img3;
	img3 = img1;
	// img1, img2, img3 은 같은 영상을 같게 됨(참조 형태)

	Mat img4 = img1.clone(); // img1의 복사본을 만들어 img4에 대입하는 것이므로 오른쪽(img4)에서 왼쪽(img1)으로 복사가 됨
	Mat img5; 
	img1.copyTo(img5); // img1의 img5에 복사하는 것이므로 왼쪽(img1)에서 오른쪽(img5)으로 복사가 됨
	// img4, img5 모두 같은 영상을 갖게 됨(깊은 복사 형태)

	img1.setTo(Scalar(0, 255, 255));	// yellow로 바꾸는데 img1, img2, img3가 노랑색으로 바뀌는 것을 확인 할 수 있음
	// 참조(얕은 복사) 형태로 복사하면 원본이 변경되면 변경되지만, 깊은 복사 형태로 복사하면 변경되지 않음
	
	imshow("img1", img1);
	imshow("img2", img2);
	imshow("img3", img3);
	imshow("img4", img4);
	imshow("img5", img5);

	waitKey();
	destroyAllWindows();
}
  • img3는 clone 함수를 통해 완전히 메모리 공간을 새로 할당해서 픽셀 데이터를 복사함
  • 복사된 메모리 데이터의 시작 주소를 img3가 가리키게 설정하므로 완전히 분리된 메모리 공간을 사용
Mat img1 = imread("dog.bmp");
Mat img2 = img1;
Mat img3 = img1.clone();

image

부분 영상 추출

  • Mat 클래스 객체에서 부분 행렬 추출
    • Mat 객체에 대해 () 연산자를 이용하여 부분 영상 추출이 가능함
    • () 연산자 안에는 Rect 객체를 지정하여 부분 영상의 위치와 크기를 지정
    • 참조를 활용하여 ROI 연산 수행이 가능함
void MatOp3()
{
	Mat img1 = imread("cat.bmp");

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

	Mat img2 = img1(Rect(220, 120, 340, 240));
	Mat img3 = img1(Rect(220, 120, 340, 240)).clone();

	img2 = ~img2; // not 연산을 수행하여 영상을 반전함
	// img2의 영상이 반전되면서 img1에서 참조한 부분도 반전됨
	// img3는 clone을 통해 깊은 복사를 하였으므로 img2에서 영상이 바뀌어도 적용되지 않음
    
	imshow("img1", img1);
	imshow("img2", img2);
	imshow("img3", img3);

	waitKey();
	destroyAllWindows();
}

image

영상의 픽셀 값 참조

  • Mat 영상의 픽셀 값 접근하기

    • OpenCV에서 제공하는 기능이 아닌 자신만의 새로운 기능을 추가해야 하는 경우에 유용함

    • 기본적으로 Mat::data 멤버 변수가 픽셀 데이터 메모리 공간을 가리키지만, Mat 클래스 멤버 함수를 사용하는 방법을 권장

    • Mat::data는 메모리 연산이 잘못될 경우 프로그램이 비정상 종료할 수 있음

      • Mat::data : image

        (i : 행 번호, j : 열 번호. step[0] : 한 행의 바이트 크기, step[1 ]: 원소 1개의 byte 크기 )

      • Mat::at() 함수 : 좌표 지정이 직관적이고 임의 좌표에 접근할 수 있음

      • Mat::ptr() 함수 : Mat:at()보다 빠르게 동작하고, 행 단위 연산을 수행할 때 유리함

      • MatIterator_ 반복자 : 좌표를 지정하지 않아서 안전하지만 성능은 느린 편

Mat:at()
template<typename_Tp> _Tp& Mat::at(int x, int y)
  • y : 참조할 행 번호
  • x : 참조할 열 번호
  • 반환값 : (_Tp& 타입으로 캐스팅된) 참조 형태의 y행 x열 원소 값
    • 참조 형태로 반환하므로 픽셀 값을 반환하는 것 뿐만 아니라 픽셀 값을 설정할 수도 있음
Mat mat1 = Mat::zeros(3, 4, CV_8UC1); // 모든 원소의 값이 0으로 초기화되어있는 mat1 객체 생성

for (int y =0; y < mat1.rows; y++ ){
	for (int x = 0; x < mat1.cols; x++){
		mat1.at<uchar>(y, x)++;
	}
}

image

Mat::ptr()
template<typename_Tp> _Tp& Mat::ptr(int y)
  • y : 참조할 행 번호
  • 반환값 : (_Tp* 타입으로 캐스팅된) y번 행의 시작 주소
for(int y = 0; y < mat1.rows; y++){
	uchar* p = mat1.ptr<uchar>(y);
	
	for(int x =0; x < mat1.cols; x++) {
		p[x] ++;
	} 
}

image

MatIterator_ 반복자
  • OpenCV는 Mat 클래스와 함께 사용할 수 있는 반복자 클래스 템플릿 MatIterator_를 제공
  • MatIterator_는 클래스 탬플릿이므로 사용할 때에는 Mat 행렬 타입에 맞는 자료형을 명시해야 함
  • Mat::begin() 함수는 행렬의 첫 번째 원소의 위치를 반환
  • Mat::end() 함수는 행렬의 마지막 원소 바로 다음 위치를 반환
for (MatIterator_<uchar> it = mat1.begin<uchar>(); it != mat1.end<uchar>(); ++it){
	(*it)++; // it가 가리키는 값(*it)을 1씩 증가함
}

image

void MatOp4()
{
	Mat mat1 = Mat::zeros(3, 4, CV_8UC1); // 3행 4열의 uchar 타입의 행렬을 만들어 mat1 객체에 선언

	for (int y = 0; y < mat1.rows; y++) {
		for (int x = 0; x < mat1.cols; x++) {
			mat1.at<uchar>(y, x)++;
		}
	}
// at 함수를 이용해 모든 원소의 값을 1씩 증가시킴

	for (int y = 0; y < mat1.rows; y++) {
		uchar* p = mat1.ptr<uchar>(y);

		for (int x = 0; x < mat1.cols; x++) {
			p[x]++;
		}
	}
// ptr 함수를 이용해 모든 원소의 값을 1씩 증가시킴

	for (MatIterator_<uchar> it = mat1.begin<uchar>(); it != mat1.end<uchar>(); ++it) {
		(*it)++;
	}
// MatIterator_ 이용해 모든 원소의 값을 1씩 증가시킴

	cout << "mat1:\n" << mat1 << endl; // mat1의 모든 원소를 출력함
}
  • 3행 4열 0 값으로 채워진 행렬이 3행 4열 3 값으로 채워진 행렬로 바뀜

image

기초 행렬 연산

  • Mat 클래스 기본 행렬 연산
void MatOp5()
{
	float data[] = {1, 1, 2, 3};
	Mat mat1(2, 2, CV_32FC1, data);
	cout << "mat1:\n" << mat1 << endl;

	Mat mat2 = mat1.inv(); // 역행렬을 구함
	cout << "mat2:\n" << mat2 << endl;

	cout << "mat1.t():\n" << mat1.t() << endl; // transpose
	cout << "mat1 + 3:\n" << mat1 + 3 << endl; // 모든 원소에 숫자 3을 더함
	cout << "mat1 + mat2:\n" << mat1 + mat2 << endl; 
	cout << "mat1 * mat2:\n" << mat1 * mat2 << endl; // 자기 자신과 자기 자신의 역행렬을 곱하여 단위행렬 결과가 나옴
}

image