OpenCV를 사용하기 위해서 가장 기본이 되는 클래스는 바로 cv::Mat 클래스 입니다.
이번부터 몇번에 걸쳐 cv::Mat 클래스에 대해 알아보겠습니다.
cv::Mat 클래스란?
cv::Mat 클래스는 행렬(matrix)를 표현하기 위한 클래스에요.n차원 단일/멀티 채널 배열을 다 표현할 수 있습니다.
OpenCV 공식 문서(https://docs.opencv.org/4.x/d3/d63/classcv_1_1Mat.html) 를 기반으로 설명 드릴께요.
cv::Mat은 실수/복소수, 행렬, 모노/컬러, 입체 벡터, 점의 군집, tensor, histogram 등을 표현할 수 있습니다.
2차원 영상 데이터를 저장하고 처리하는 용도로 가장 많이 사용됩니다.
영상처리 특성상 빠른 연산이 중요하기 때문에 interface.h을 보면 아래와 같이 기본 자료형을 매크로 상수로 정의하고 있습니다.
이름
|
의미
|
값
|
범위
|
CV_8U
|
unsigned char (uchar)
8-bit unsigned integer |
0
|
0 ~ 255
|
CV_8S
|
signed char (schar)
8-bit signed integer |
1
|
-128 ~ 127
|
CV_16U
|
unsigned short (ushort)
16-bit unsigned integer |
2
|
0 ~ 65535
|
CV_16S
|
signed short (short)
16-bit signed integer |
3
|
-32768 ~ 32767
|
CV_32S
|
signed integer (int)
32-bit signed integer |
4
|
-2147483648 ~ 2147483647
|
CV_32F
|
float
32-bit floating-point number |
5
|
|
CV_64F
|
double
64-bit floating-point number |
6
|
|
CV_16F
|
float16
16-bit floating-point number |
7
|
|
아래와 같은 규칙을 가지고 표현을 하기도 합니다.
channels 는 gray scale 이면 1, color 이면 3 을 사용합니다.
cv::Mat 생성 방법
cv::Mat 객체를 생성하고 초기화 하는 방법은 여러가지가 있습니다.
cv::Mat은 아래와 같이 정의됩니다.
//빈 행렬
cv::Mat src;
// 640(width) x 480(height) 사이즈 영상 (unsigned char, 1 channel)
cv::Mat src1(480, 640, CV_8UC1); // rows, cols 순서
cv::Mat src2;
src2 = cv::Mat(480, 640, CV_8UC1);
// 640 x 480 사이즈 영상 (unsigned char, 1 channel)
cv::Mat src3(cv::Size(640, 480), CV_8UC1); // cv::Size(cols, rows) 순서
cv::Mat src4;
src4 = cv::Mat(cv::Size(640, 480), CV_8UC1);
// 640 x 480, 125 값으로 초기화된 영상 (unsigned char, 1 channel)
cv::Mat src5(480, 640, CV_8UC1, cv::Scalar(128));
cv::Mat src6;
src6 = cv::Mat(480, 640, CV_8UC1, cv::Scalar(128));
// 640 x 480, 0 값으로 초기화된 영상 (unsigned char, 1 channel)
cv::Mat src7;
src7 = cv::Mat::zeros(cv::Size(640, 480), CV_8UC1);
저는 영상을 생성할 때 cv::Size()를 많이 애용합니다.
우리는 보통 width x heigh로 얘기를 하기 때문에 rows, clos 를 사용하는 것보다 Size()를 사용하는 것이 헷갈리지 않죠.
결과를 Image Watch로 보면
https://blog.naver.com/dorergiverny/223035739939
사이즈 및 type을 지정하지 않은 src 의 경우 아무런 값이 없고, src1, src2, src3, src4 는 size와 type을 지정하니까
205 값으로 초기화가 되어 들어가네요. src5, src6는 정상적으로 128 값이 들어가 있고,
src7은 0 값이 들어가 있어요.
이 정도만 알면 cv::Mat 객체 생성은 하실 수 있을 꺼에요.
한가지만 더 알려드리면, 배열을 이용하여 cv::Mat 형태로 변환한다면, 아래와 같이 하실 수 있습니다.
일반적으로 필터 커널을 생성한 후 cv::filter2D()를 사용할 때 많이 사용하게 됩니다.
float afData[] = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9};
cv::Mat src8(3, 3, CV_32FC1, afData);
cv::Mat 복사하기
복사하는 방법은 두가지가 있습니다.
얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy) 입니다.
1. 얕은 복사(Shallow Copy)
얕은 복사는 같은 데이터를 공유합니다. 대입 연산자, 복사 생성자를 이용하여 얕은 복사를 수행할 수있습니다.
간단한 예제를 위해 uchar로 배열을 만들고 cv::Mat으로 변환하였습니다. 그리고 대입 연산자를 이용하여 src9에 src8을 대입하였습니다.
src9 = src8;
uchar aucData[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
cv::Mat src8(3, 3, CV_8UC1, aucData);
cv::Mat src9 = src8;
src8.at<uchar>(1, 1) = 0;
분명히 src8의 가운데 값((1,1)위치)을 0으로 바꿨는데, src9의 가운데 값도 0으로 바뀌었네요.
영상에서 ROI를 따 낸 다음에 ROI에서만 처리를 하고 다시 원본 영상에 붙일 경우에 사용합니다.
2. 깊은 복사(Deep Copy)
깊은 복사는 새로운 메모리에 값을 복사하는 것 입니다.
clone()과 copyTo()가 있습니다. 이는 크기가 같고 값이 같은 독립 객체를 하나 더 생성하는 것이에요.
uchar aucData[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
cv::Mat src8(3, 3, CV_8UC1, aucData);
cv::Mat src10 = src8.clone();
cv::Mat src11;
src8.copyTo(src11);
란 뜻 입니다.
아래 결과를 보시면, src8의 값을 바꿨는데, Deep Copy한 src10, src11의 가운데 값은 바뀌지 않았습니다.
Deep Copy 하면 서로 독립이 된다는 뜻이죠.