OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝/ OpenCV 주요 클래스

  • (각 자료형의 상세한 파라미터에 대해서는 이전의 <OpenCV로 배우는 영상처리 및 응용>에서 정리하였으므로 생략)

기본 자료형 클래스

Point_ 클래스

  • Point_ 클래스는 2차원 평면 위에 있는 점의 좌표를 표현하는 템플릿 클래스
  • 2차원 좌표를 나타내는 x와 y라는 이름의 멤버변수를 갖고 있다.
// 생성 및 초기화
Point pt1; // pt1 = (0, 0)
pt1.x = 5; pt1.y = 10; // pt1 = (5, 10)
Point pt2(10, 30); // pt2 = (10, 30)

// 연산
Point pt3 = pt1 + pt2; // pt3 = [15, 40]
Point pt4 = pt1 * 2; // pt4 = [10, 20]
int d1 = pt1.dot(pt2); // d1 = 350
bool b1 = (pt1 == pt2); // b1 = false

Size_ 클래스

  • 영상 또는 사각형의 크기를 표현하는 클래스.
  • 사각형 영역의 가로와 세로 크기를 나타내는 width, height 멤버 변수를 갖고 있다.
// 생성 및 초기화
Size sz1, sz2(10, 20);  // sz1 = [0 x 0], sz2 = [10 x 20]
sz1.width = 5; sz1.height = 10;  // sz1 = [5 x 10]

// 연산
Size sz3 = sz1 + sz2;  // sz3 = [15 x 30]
Size sz4 = sz1 * 2;  // sz4 = [10 x 20]
int area1 = sz4.area();  // area1 = 200

Rect_ 클래스

  • 사각형의 위치와 크기를 표현하는 클래스
  • 사각형의 좌측 상단 점의 좌표를 나타내는 x, y와 가로 세로 크기를 나타내는 width, height 멤버 변수를 갖고 있다.
// 생성 및 초기화
Rect rc1;  // rc1 = [0 x 0 from (0, 0)]
Rect rc(10, 10, 60, 40);  // rc2 = [60 x 40 from (10, 10)]

// 연산
Rect rc3 = rc1 + Size(50, 40);  // rc3 = [50 x 40 from (0, 0)]
Rect rc4 = rc2 + Point(10, 10);  // rc4 = [60 x 40 from (20, 20)]

// 논리연산
Rect rc5 = rc3 & rc4; // rc5 = [30 x 20 from (10, 10)]
Rect rc6 = rc3 | rc4; // rc6 = [80 x 60 from (0, 0)]

RotatedRect 클래스

  • 회전된 사각형을 표현하는 클래스
  • 사각형의 중심 좌표를 나타내는 center, 가로 및 세로 크기를 나타내는 size, 회전 각도를 나타내는 angle 멤버 변수를 갖고 있다.
// 생성 및 초기화
RotatedRect rr1(Point2f(40, 30), Size2f(40, 20), 30.f);  // 아래 그림 a 참조

// 사각형의 바운딩 영역 확인
Rect br = rr1.boundingRect();  // 아래 그림 b 참조

Range 클래스

  • 범위 또는 구간을 표현하는 클래스.
  • 범위의 시작과 끝을 나타내는 start와 end 멤버 변수를 갖고 있다.
// 생성 및 초기화
Range r1(0, 10);

String 클래스

  • 문자열을 다루는 클래스
  • 본래 OpenCV에서는 자체적인 String을 정의하여 사용하였음. String 클래스는 std::string 클래스와 완전히 호환되도록 설계되어서 std::string 클래스를 다루는 방식과 유사하게 사용할 수 있었음.
  • 그러다가 OpenCV 4.0 버전부터는 자체적인 String 클래스를 삭제하고 C++ 표준 라이브러리의 std::string 클래스를 String 클래스로 재정의함.
// 생성 및 초기화
String str1 = "Hello";
String str2 = "world";
String str3 = str1 + " " + str2;  // str3 = "Hello world";

bool ret = (str2 == "WORLD");  // ret = false, == 연산자는 대소문자를 구분하므로

Mat 클래스

Mat 클래스 개요

  • Mat 클래스는 일반적인 2차원 행렬뿐만 아니라 고차원 행렬을 표현할 수 있으며, 한 개 이상의 채널(channel)을 가질 수 있음.
  • Mat 클래스에는 정수, 실수, 복소수 등으로 구성된 행렬 또는 벡터(vector)를 저장할 수 있고, 그레이스케일 또는 컬러 영상을 저장할 수도 있음.
  • 경우에 따라 벡터 필드(vector field), 포인트 클라우드(point cloud), 텐서(tensor), 히스토그램(histogram) 등 정보를 저장하는 용도로 사용 됨.
  • Mat 클래스에서 행렬이 어떤 자료형을 사용하는지에 대한 정보를 깊이(depth)라고 하는데, OpenCV에서 Mat 행렬의 깊이는 다음과 같은 형식의 매크로 상수를 이용하여 표현 함.
CV_<bit_depth>{U|S|F}
  • 깊이 표현 매크로 상수 형식의 처음에 나타나는 CV_는 OpenCV를 나타내는 접두사
  • 그 뒤의 <bit_depth>에는 8, 16, 32, 64의 숫자를 지정할 수 있으며, 이는 원소 값 하나의 비트 수를 나타냄
  • 그 다음의 {U|S|F} 부분에는 U, S, F 세 문자 중 하나를 지정할 수 있는데, U는 부호 없는 정수형, S는 부호 있는 정수형, F는 부동 소수형을 의미함
  • OpenCV 라이르러리는 행렬의 깊이 표현을 위해 다음과 같은 매크로 상수를 정의하여 사용함
#define CV_8U  0  // uchar, unsigned char
#define CV_8S  1  // schar, signed char
#define CV_16U 2  // ushort, unsigned short
#define CV_16S 3  // signed short
#define CV_32S 4  // int
#define CV_32F 5  // float
#define CV_64F 6  // double
#define CV_16F 7  // float16_t
  • Mat 행렬 원소는 하나의 값을 가질 수도 있고, 여러 개의 구성된 값을 가질 수도 있다.
  • Mat 행렬 원소를 구성하는 각각의 값을 채널(channel)이라고 한다.
    • 즉, Mat 행렬은 하나의 채널을 가질 수도 있고, 여러 개의 채널을 가질 수도 있다.
    • 이때 하나의 행렬을 구성하는 각 채널은 모두 같은 자료형을 사용해야 함.
  • OpenCV에서는 Mat 행렬의 깊이 정보와 채널 수 정보를 합쳐서 Mat 객체의 타입(type)이라고 부른다. OpenCV 행렬의 타입은 다음과 같은 형식의 매크로 상수로 표현한다.
CV_<bit_depth>{U|S|F}C(<number_of_channels>)
  • 즉, Mat 행렬의 깊이 표현 매크로 뒤에 C1, C3 같은 채널 정보가 추가로 붙어 있는 형태이다.
    • 예컨대 CV_8UC1 타입은 8비트 unsigned char 자료형을 사용하고 채널이 한 개인 행렬 또는 영상을 의미한다.
    • B, G, R 세 개의 색상 성분을 가지고 있는 컬러 영상은 unsigned char 자료형을 사용하고 세 개의 채널을 가지고 있기 때문에 CV_8UC 타입이다.
    • 복소수처럼 두 개의 실수 값을 사용하는 행렬은 CV_32FC2 타입으로 만들 수 있다.

행렬의 생성과 초기화

// 생성 및 초기화
Mat img1;
Mat img2(480, 640, CV_8UC1);
Mat img3(480, 640, CV_8UC3);  // unsigned char, 1-channel
Mat img4(Size(640, 480), CV_8UC3);  // unsigned char, 3-channels
  • 행렬의 크기와 타입을 지정하여 Mat 객체를 생성하는 경우 Mat 행렬의 모든 원소는 쓰레기값(garbage value)이라고 부르는 임의의 값으로 채워지게 된다. 고로 모든 원소 값을 특정 값으로 초기화하여 사용하는 것이 안전하다.
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으로 초기화하는 경우가 많기 때문에 OpenCV에서는 모든 원소가 0으로 초기화된 행렬을 만드는 함수로써 Mat::zeros()를 제공한다.
Mat mat1 = Mat::zeros(3, 3, CV_32SC1);  // 0's matrix
  • 비슷하게 행렬의 모든 원소가 1로 초기화된 행렬을 생성하려면 Mat::ones()를 이용하면 되고, 단위 행렬(identity matrix)를 생성하려면 Mat:eye() 함수를 이용하면 된다.
Mat mat2 = Mat::ones(3, 3, CV_32FC1);  // 1's matrix
Mat mat3 = Mat::eye(3, 3, CV_32FC1);  // identity matrix

mat1 = \left[ \begin{array}{rrr} 0 & 0 & 0 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{array} \right]

mat2 = \left[ \begin{array}{rrr} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{array} \right]

mat3 = \left[ \begin{array}{rrr} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{array} \right]

  • Mat 객체를 생성할 떄, 행렬 원소를 저장할 메모리 공간을 새로 할당하는 것이 아니라 기존에 이미 할당되어 있는 메모리 공간의 데이터를 행렬 원소 값으로 사용할 수 있다.
    • 이는 외부 메모리 공간을 참조하는 방식이기 때문에 객체 생성이 빠르다는 장점이 있다. 이러한 용도의 Mat 클래스 생성자 형식은 다음과 같다.
float data[] = { 1, 2, 3, 4, 5, 6 };
Mat mat4(2, 3, CV_32FC1, data);
  • 외부 배열을 행렬 원소 값으로 사용하는 경우 외부 배열 크기와 생성할 행렬 원소 개수는 같아야 하고, 서로 사용한느 자료형도 같아야 한다.
    • 위와 같은 코드의 경우 mat4 행렬의 1행은 data 배열의 처음 세 개의 원소로 채워지고, 2행은 data 배열의 나머지 원소로 채워진다.

mat4 = \left[ \begin{array}{rrr} 1 & 2 & 3 \\ 4 & 5 & 6 \end{array} \right]

  • 사용자가 지정한 원소 값을 이용하여 Mat 객체를 생성하는 방법 중에 Mat_ 클래스를 사용하는 방법도 종종 사용된다. Mat_ 클래스는 Mat 클래스를 상속하여 만든 템플릿 클래스로 Mat_ 클래스 객체와 Mat 객체는 상호 변환이 가능하다.
    • 그런데 Mat_ 클래스는 << 연산자와 , 를 이용하여 간단하게 행렬 원소 값을 설정하는 인터페이스를 제공한다.
    • 그래서 일단 Mat_ 객체를 만들어서 << 연산자로 행렬을 지정한 후 이를 Mat 객체로 변환하여 사용하기도 한다.
Mat_<float> mat5_(2, 3);
mat5_ << 1, 2, 3, 4, 5, 6;
Mat mat5 = mat5_;

// 다음과 같이 한 줄로도 표현 가능
Mat mat5 = (Mat_<float>(2, 3) << 1, 2, 3, 4, 5, 6);

mat5 = \left[ \begin{array}{rrr} 1 & 2 & 3 \\ 4 & 5 & 6 \end{array} \right]

  • OpenCV 4.0에서는 C++ 11의 초기화 리스트(initializer list)를 이용한 행렬 초기화 방법을 사용할 수 있다.
    • Mat 클래스 또는 Mat_ 클래스의 생성자에 행렬 크기와 초깃값을 중괄호를 이용한 리스트 형태로 전달하는 방식.
    • 다만 생성된 Mat 객체의 타입을 명시적으로 지정하기 위해 Mat_ 클래스 형식으로 생성한 후 Mat 타입으로 변경하는 것이 좋다.
Mat mat6 = Mat_<float>({2, 3}, {1, 2, 3, 4, 5, 6});
  • 비어 있는 Mat 객체 또는 이미 생성된 Mat 객체에 새로운 행렬을 할당하려면 Mat 클래스의 Mat::create() 멤버 함수를 사용할 수 있다.
    • 이미 행렬 데이터가 할당되어 있는 Mat 객체에서 Mat::create() 함수를 호출할 경우, 만약 Mat::create() 함수의 인자로 지정한 행렬 크기와 타입이 기존 행렬과 모두 같으면 Mat::create() 함수는 별다른 동작을 하지 않고 그대로 함수를 종료한다.
    • 반면 새로 만들 행렬의 크기 또는 타입이 기존 행렬과 다른 경우, Mat::create() 함수는 일단 기존 메모리 공간을 해제한 후 새로운 행렬 데이터 저장을 위한 메모리 공간을 할당한다.
mat4.create(256, 256, CV_8UC3);  // 256 x 256, uchar, 3-channels
mat5.create(4, 4, CV_32FC1);  // 4 x 4, float, 1-channels
  • Mat:create() 함수는 새로 만든 행렬의 원소 값을 초기화하는 기능이 없기 때문에 행렬을 생성한 후 행렬 전체를 초기화하고 싶다면 = 연산자 재정의 또는 Mat::setTo() 멤버 함수를 이용하여 행렬 전체 원소 값을 한꺼번에 설정할 수 있다.
    • Mat:setTo() 함수는 두 개의 인자를 가지고 있지만 두 번째 인자 mask는 기본값을 가지고 있으므로 생략할 수 있다.
mat4 = Scalar(255, 0, 0);
mat5.setTo(1.f); 

행렬의 복사

  • Mat 클래스 객체에 저장된 영상 또는 행렬을 복사하는 가장 간단한 방법은 복사 생성자 또는 대입 연산자를 사용하는 방법이다.
Mat img1 = imread("dog.bmp");

Mat img2 = img1;  // 복사 생성자 - 얕은 복사

Mat img3;
img3 = img1; // 대입 연산자 - 얕은 복사
  • 만일 복사본 영상을 새로 생성할 때 픽셀 데이터를 공유하는 것이 아니라 메모리 공간을 새로 할당하여 픽셀 데이터 전체를 복사하고 싶다면 Mat::clone() 또는 Mat::copyTo() 함수를 사용해야 한다.
    • Mat::clone() 함수는 자기 자신과 동일한 Mat 객체를 완전히 새로 만들어서 반환한다. 
    • Mat::copyTo() 함수는 인자로 전달된 m 행렬에 자기 자신을 복사한다. 만약 Mat::copyTo() 함수를 호출한 행렬과 인자로 전달된 행렬 m이 서로 크기와 타입이 같으면 원소 값 복사만 수행한다.
    • 반면 서로 크기 또는 타입이 다르면 Mat::copyTo() 함수 내부에서 행렬 m을 새로 생성한 후 픽셀 값을 복사한다.
Mat img4 = img1.clone();  // 깊은 복사

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

부분 행렬 추출

  • Mat 클래스로 정의된 행렬에서 특정 사각형 영역의 부분 행렬을 추출하고 싶을 때는 Mat 클래스에 저으이된 괄호 연산자 재정의를 사용한다.
Mat img1 = imread("cat.bmp");
Mat img2 = img1(Rect(220, 120, 340, 240)); // 220, 120 좌표부터 340 x 240 크기만큼의 사각형 부분 영상 추출
  • 부분 영상 추출은 얕은 복사이기 때문에 부분 영상을 추출한 후 부분 영상의 픽셀 값을 수정하면 원본 영상의 픽셀 값도 함께 변경 됨.
  • 영상의 반전은 Mat 클래스 타입의 변수 앞에 ~ 연산자를 붙이는 방식으로 할 수 있다.
img2 = ~img2;
  • Mat 클래스의 부분 영상 참조 기능은 입력 영상에 사각형 모양의 관심 영역 (ROI, Region Of Interest)을 설정하는 용도로 사용할 수 있다. 
    • ROI는 영상의 전체 영역 중에서 특정 영역에 대해서만 영상 처리를 수행할 때 설정하는 영역을 의미한다.
    • 사각형이 아닌 임의의 모양의 ROI를 설정하고 싶은 경우에는 마스크 연산을 응용할 수 있다.
  • 만약 독립된 메모리 영역을 확보하여 부분 영상을 추출하고자 한다면 괄호 연산자 뒤에 Mat::clone() 함수를 사용하면 된다.
Mat img3 = img1(Rect(220, 120, 340, 240)).clone();
  • Mat 행렬에서 특정 범위의 행을 추출하고자 할 때는 Mat::rowRange()를 이용하며 특정 열을 추출하고자 할 때는 Mat:colRange() 함수를 사용할 수 있다.
    • 행 또는 열의 범위는 두 개의 int 값으로 지정할 수도 있고, Range 클래스 객체를 이용하여 지정할 수도 있다.
    • 하나의 행 또는 열을 추출하여 1행짜리 또는 1열짜리 행렬을 만들고자 할때는 Mat::row() 또는 Mat::col() 함수를 사용할 수 있다.
  • Mat::rowRange(), Mat::colRange(), Mat::row(), Mat::col() 함수들은 모두 부분 행렬을 얕은 복사 형태로 반환한다.
    • 따라서 깊은 복사를 수행하려면 Mat::clone() 함수를 함께 사용해야 한다.

행렬의 원소 값 참조

Mat::at() 함수 사용 방법

  • OpenCV에서 제공하는 가장 직관적인 행렬 원소 접근 방법은 Mat::at() 멤버 함수를 사용하는 방법이다.
    • Mat::at() 함수는 보통 행과 열을 나타내는 두 개의 정수를 인자로 받아 해당 위치의 행렬 원소 값을 참조 형식으로 반환한다.
    • Mat::at() 함수는 템플릿 함수로 정의되어 있기 때문에 Mat::at() 함수를 사용할 때는 행렬 원소 자료형을 명시적으로 지정해야 한다.
Mat mat1 = Mat::zeros(3, 4, CV_8UC1);

for (int j = 0; j < mat1.rows; j++)
{
for (int i = 0; i < mat.cols; i++)
{
mat1.at<uchar>(j, i)++;
}
}
  • 위 코드에서 Mat::at() 함수가 참조하는 행렬 원소 위치는 아래 그림과 같다.

Mat::ptr() 함수 사용 방법

  • Mat:ptr() 함수는 Mat 행렬에서 특정 행의 첫 번째 원소 주소를 반환한다.
    • Mat::ptr() 함수도 템플릿으로 정의되어 있기 때문에 행렬 원소의 자료형을 명시적으로 지정해야 한다.
    • Mat::ptr() 함수는 지정한 자료형의 포인터를 반환하며, 이 포인터를 이용하여 지정한 행의 원소에 접근할 수 있다.
for (int j = 0; j < mat1.rows; j++)
{
uchar* p = mat1.ptr<uchar>(j);

for (int i = 0; i < mat.cols; i++)
{
p[i]++;
}
}

MatIterator_ 반복자 사용 방법

  • Mat 클래스와 함께 사용할 수 있는 반복자 클래스 이름은 MatIterator_ 이다.
    • MatIterator_ 클래스는 템플릿으로 정의된 클래스이므로 Mat 행렬 타입에 맞는 자료형을 명시하여 사용해야 한다.
    • MatIterator_ 클래스를 사용하는 방식은 C++의 반복자 사용 방법과 유사하다. Mat::begin() 함수를 이용하여 행렬의 첫 번째 원소 위치를 얻을 수 있고, Mat::end() 함수를 이용하여 마지막 원소 바로 다음 위치를 얻을 수 있다.
  • MatIterator_ 반복자를 사용하면 행렬의 가로, 세로 크기에 상관없이 모든 원소를 안전하게 방문할 수 있지만, Mat::ptr() 보다 느리고, Mat::at() 처럼 임의의 위치에 자유롭게 접근할 수 없기 때문에 사용성이 높지 않은 편이다.
for (MatIterator_<uchar> it = mat1.begin<uchar>(); it != mat1.end<uchar>(); ++it)
{
(*it)++;
}

행렬 정보 참조하기

  • (Mat 클래스의 멤버 변수들 설명. 생략)

행렬 연산

// mat1과 mat2 행렬 사이의 덧셈과 뺄셈 연산
mat3 = mat1 + mat2;
mat3 = mat1 - mat2;

// mat1 행렬의 각 원소와 슼라라 s1 사이의 덧셈 및 뺄셈 연산
mat3 = mat1 + s1;
mat3 = mat1 - s1;
mat3 = s1 + mat1;
mat3 = s1 - mat1;

// mat1 행렬의 각 원소에 -1을 곱함
mat3 = -mat1;

// mat1과 mat2 행렬의 곱셈 연산
mat3 = mat1 * mat2;

// mat1 행렬의 각 원소에 실수 d1을 곱함
mat3 = mat1 * d1;
mat3 = d1 * mat1;

// mat1과 mat2 행렬의 같은 위치 원소끼리 나눗셈
mat3 = mat1 / mat2;

// mat1 행렬의 각 원소와 실수 d1끼리 나눗셈 연산
mat3 = mat1 / d1;
mat3 = d1 / mat1;
  • 위의 연산 중 * 연산은 행렬의 수학적 곱셈 연산을 의미하며, 만일 두 행렬의 같은 위치에 있는 원소끼리 곱셈 연산을 수행하려면 Mat::mul() 함수를 사용해야 한다.
  • 행렬의 역행렬은 Mat::inv() 함수를 이용해서 구할 수 있다.
    • Mat::inv() 함수는 method 인자를 통해 역행렬 계산 방법을 지정할 수 있다.
    • 역행렬이 존재하는 일반적인 행렬이라면 가우스 소거법을 사용하는 DECOMP_LU를 사용할 수 있으며, 이 값은 기본값으로 지정되어 있으므로 생략할 수 있다.
    • 역행렬이 존재하지 않는 경우 DECOMP_SVD를 지정하면 특잇값 분해(singular value decomposition) 방법을 이용하여 의사 역행렬(pseudo-inverse matrix)를 구할 수 있다.
    • DECOMP_EIG와 DECOMP_CHOLESKY는 각각 고윳값 분해와 촐레스키(Cholesky) 분해에 의한 역행렬 계산을 의미한다.
  • 행렬의 전치행렬은 Mat::t()를 이용하여 구할 수 있다.

크기 및 타입 변환 함수

  • 행렬의 타입을 변경할 때는 Mat::convertTo() 함수를 사용한다.
    • Mat::convertTo() 함수는 행렬 원소의 타입을 다른 타입으로 변경하고 추가적으로 모든 원소에 일정한 값을 더하거나 곱할 수 있다.
    • Mat::convertTo() 함수에 의해 생성되는 출력 행렬 m의 원소 값은 다음 수식에 의해 결정된다.

m(x, y) = saturate_cast<rtype>(alpha \times (*this)(x, y) + beta)

  • 일반적으로 영상은 픽셀 값을 uchar 자료형을 이용하는데, 만일 일련의 복잡한 연산을 수행해야 하는 경우 연산의 정확도를 높이기 위해 픽셀 값을 float, double 같은 실수형으로 변환하여 내부 연산을 수행해야 하는 경우가 있을 수 있다.
  • 이러한 경우에 Mat::convertTo() 함수를 사용하여 CV_8UC1 타입의 행렬을 CV_32FC1 타입으로 변경할 수 있다.
Mat img1 = imread("lenna.bmp", IMREAD_GRAYSCALE);

Mat img1f;
img1.convertTo(img1f, CV_32FC1);
  • Mat::reshape() 함수는 주어진 행렬의 크기 또는 채널 수를 변경한다.
    • Mat::reshape() 함수는 행렬 원소 데이터를 복사하여 새로운 행렬을 만드는 것이 아니라 하나의 행렬 원소 데이터를 같이 참조하는 행렬을 반환한다. 그러므로 Mat::reshape() 함수에 의해 반환된 행렬 원소 값을 변경하면 원본의 값도 함께 바뀌게 된다.
uchar data1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
Mat mat1(3, 4, CV_8UC1, data1);
Mat mat2 = mat1.reshape(0, 1);

// result
// mat1: [ 1, 2, 3, 4;
// 5, 6, 7, 9;
// 9, 10, 11, 12]
// mat2: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]
  • 행렬의 모양이 아니라 단순히 행렬의 행 크기를 변경하고 싶을 때는 Mat::resize() 함수를 사용하면 된다.
    • Mat::resize() 함수는 행렬의 행 개수를 변경하는데, 입력된 개수가 기존 행 개수보다 작으면 아래쪽 행을 제거하고, 기존 행렬의 행 개수보다 크면 아래쪽에 행을 추가한다. 이때 추가하는 행 원소의 초기값을 지정할 수 있다.
mat1.resize(5, 100);

// result
// mat1: [ 1, 2, 3, 4;
// 5, 6, 7, 9;
// 9, 10, 11, 12;
// 100, 100, 100, 100;
// 100, 100, 100, 100]
  • 이미 존재하는 행렬에 원소 데이터를 추가하고 싶을 때는 Mat::push_back() 함수를 이용할 수 있다.
    • Mat::push_back() 함수 인자로 _Tp& 또는 std::vector<_Tp>& 타입을 사용할 경우, *this 행렬은 1열짜리 행렬이어야 한다.
    • 만약 Mat_<_Tp>& 또는 Mat& 타입을 인자로 사용할 경우에는 *this 행렬과 인자로 전달된 m 행렬의 열 개수가 같아야 한다.
Mat mat3 = Mat::ones(1, 4, CV_8UC1) * 255;
mat1.push_back(mat3);

// result
// mat1: [ 1, 2, 3, 4;
// 5, 6, 7, 9;
// 9, 10, 11, 12;
// 100, 100, 100, 100;
// 100, 100, 100, 100;
// 255, 255, 255, 255]
  • Mat::push_back()과 반대로 맨 아래에 있는 행을 제거할 때는 Mat::pop_back() 함수를 이용하면 된다.

Vec과 Scalar 클래스

Vec 클래스

  • 벡터는 같은 자료형을 가진 원소 몇 개로 구성된 데이터 형식이다.
// 생성 및 초기화
Vec<uchar, 3> p1, p2(0, 0, 255);
  • 매번 Vec<uchar, 3> 형태로 입력하는 것은 번거롭기 때문에, OpenCV에서는 자주 사용되는 자료형과 개수에 대한 Vec 클래스 템플릿의 이름을 재정의를 제공한다.
    • OpenCV 에서 제공하는 Vec 클래스 템플릿의 이름 재정의는 다음 형식을 따른다.
Vec<num-of-data>{b|s|w|i|f|d}
  • <num-of-data> 위치에는 2, 3, 4 등 숫자를 지정할 수 있고, {b|s|w|i|f|d} 부분에는 b, s, w, i, f, d 문자 중 하나를 지정한다.
    • OpenCV 라이브러리에 정의된 Vec 클래스의 이름 재정의는 다음과 같다.
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;

typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;

typedef Vec<ushort, 2> Vec2w;
typedef Vec<ushort, 3> Vec3w;
typedef Vec<ushort, 4> Vec4w;

typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<int, 6> Vec3i;
typedef Vec<int, 8> Vec4i;

typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec4f;

typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec4d;
  • Vec 클래스는 [ ] 연산자가 재정의되어 있기 때문에 [ ] 연산자를 이용하여 멤버 변수 val 배열에 쉽게 접근할 수 있다.

Scalar 클래스

  • Scalar 클래스는 4채널 이하의 영상에서 픽셀 값을 표현하는 용도로 자주 사용된다.
    • Scalar 클래스는 Scalar_ 라는 이름의 클래스 템플릿 이름 재정의이며, Scalar_ 클래스는 Vec 클래스를 상속받아 만들어졌다.
    • Scalar 클래스는 보통 네 개 이하의 채널을 갖는 영상의 픽셀 값을 표현하는 용도로 사용된다.
      • 그레이스케일 영상의 경우 Scalar 클래스의 첫 번째 원소가 픽셀 밝기를 표현하고 나머지 세 개는 0으로 설정된다.
      • 트루컬러 영상의 경우, 처음 세 개 원소가 B, G, R 색상 성분 값을 표현하고 네 번째 원소는 보통 0으로 설정된다.
      • 간혹 PNG 파일 처럼 투명도를 표현하는 알파 채널이 있는 경우 Scalar 클래스의 네 번째 원소를 이용하기도 한다.
Scalar(밝기)
Scalar(파란색, 녹색, 빨간색)
Scalar(파란색, 녹색, 빨간색, 투명도)

InputArray와 OutputArray 클래스

  • InputArray 클래스는 주로 OpenCV 함수의 입력으로 사용되고, OutputArray 클래스는 OpenCV 함수의 출력으로 사용되는 인터페이스 클래스이다.

InputArray 클래스

  • InputArray 클래스는 Mat, vector<T> 등 다양한 타입을 표현할 수 있는 인터페이스 클래스로서 OpenCV 함수의 입력 인자 자료형으로 주로 사용된다.
    • _InputArray 클래스는 Mat, Mat_<T>, Matx<T, m, n>, vector<T>, vector<vector<T>>, vector<Mat>, vector<Mat_<T>, UMat, vector<UMat>, double 같은 다양한 타입으로부터 생성될 수 있는 인터페이스 클래스이다.
    • _InputArray 클래스는 OpenCV 라이브러리 내부에서 코드 구현 편의상 사용되며, 사용자가 명시적으로 _InputArray 클래스의 인스턴스 또는 변수를 생성하여 사용하는 것을 금지하고 있다.
  • 만약 OpenCV에서 제공하는 함수처럼 사용자 저으이 함수에서 Mat 객체뿐만 아니라 vector<T> 타입의 객체를 한꺼번에 전달받을 수 있게 만들고 싶다면 사용자 저으이 함수 인자에 InputArray 타입을 사용할 수 있다.
    • 그리고 실제 함수 본문에서는 _InputArray 클래스의 멤버 함수인 _InputArray::getMat() 함수를 사용하여 Mat 객체 타입 형태로 변환해서 사용해야 한다.

OutputArray 클래스

  • _OutputArray 클래스는 클래스 계층적으로 _InputArray 클래스를 상속받아 만들어졌다. 그러므로 _OutputArray 클래스도 Mat 또는 vector<T> 같은 타입의 객체로부터 생성될 수 있다.
  • 다만 _OutputArray 클래스는 새로운 행렬을 생성하는 _OutputArray:create() 함수가 추가적으로 정의되어 있다.
    • 그래서 OpenCV의 많은 영상 처리 함수는 결과 영상을 저장할 새로운 행렬을 먼저 생성한 후, 영상 처리 결과를 저장하는 형태로 구현되어 있다.
  • OutputArray 클래스도 InputArray와 마찬가지로 사용자가 직접 OutputArray 타입의 변수를 생성해서 사용하면 안 된다.
    • OutputArray 타입으로 정의된 OpenCV 함수의 인자에는 Mat 또는 vector<T> 같은 타입의 변수를 전달하는 형태로 코드를 작성해야 한다.
  • 영상에 그림을 그리는 몇몇 OpenCV 함수는 입력 영상 자체를 변경하여 다시 출력으로 반환하는 경우가 있으며, 이러한 함수는 InputOutputArray 클래스 타입의 인자를 사용한다.
    • 이 클래스는 입력과 출력의 역할을 동시에 수행할 때 사용된다.
[ssba]

The author

지성을 추구하는 사람/ suyeongpark@abyne.com

댓글 남기기

This site uses Akismet to reduce spam. Learn how your comment data is processed.