본문 바로가기

# 미사용/OpenCV

[OpenCV] 행렬의 생성 및 활용


 Mat 클래스 구조

Mat 클래스는 행렬 자료구조이며,

각 원소는 최대 4개의 채널로 구성된다. (rgb + alpha)


아래는 2채널 행렬 자료구조의 모식도이다.



opencv mat에 대한 이미지 검색결과




 Mat 클래스의 자료형

Mat 클래스는 템플릿은 없지만,

추가적인 파라미터를 통해 자료형이 결정되며,

데이터형 + 채널형 순서로 상수값이 정의되어 있다.


  • 데이터 형

CV_8U 		depth 0	unsigned char
CV_8S 		depth 1	char
CV_16U		depth 2	unsigned short int
CV_16S		depth 3	short int
CV_32S		depth 4	int
CV_32F		depth 5	float
CV_64F		depth 6	double

depth는 자료형을 구분하기 위한 상수라고 생각하면 편하다.



  • 채널 형

1C	b (default)
2C	bg
3C	bgr
4C	bgr + alpha


예를들어 CV_32S3C는 각 채널의 원소가 int32 형이며,

3개의 채널이 모여 하나의 화소를 나타낸다는 것을 의미한다.



example 1)

/**
 * 3차 정사각 + 3채널 Mat
 * Scalar(1, 2, 3)으로 초기화.
 */
Mat mat(3, 3, CV_32SC3, Scalar(1, 2, 3));
cout << mat;

//! output
[1, 2, 3, 1, 2, 3, 1, 2, 3;
 1, 2, 3, 1, 2, 3, 1, 2, 3;
 1, 2, 3, 1, 2, 3, 1, 2, 3]

Output을 앞에서 3개씩 묶어나가면,

Scalar(1, 2, 3)이 3 x 3 형태로 묶인 것을 볼 수 있다.



example 2)

/**
 * 2차 정사각 + 4채널 Mat
 * Scalar(1, 2, 3)으로 초기화.
 */
Mat mat(2, 2, CV_32SC4, Scalar(1, 2, 3));
cout << mat;

//! output
[1, 2, 3, 0, 1, 2, 3, 0;
 1, 2, 3, 0, 1, 2, 3, 0]

Output을 앞에서 4개씩 묶어나가면

Scalar(1, 2, 3, 0)이 2 x 2 형태로 묶인 것을 볼 수 있다.




 행렬 메모리

Mat 클래스는 각기 자신의 데이터를 가지고 있는 것이 아니라,

외부에 존재하는 행렬 데이터 영역 (= 행렬 메모리)를 가르키도록 되어있다.


이러한 이유로 Mat 클래스는 행렬 헤더라고 불린다.



즉, 어떤 서로다른 Mat 객체는

  • 같은 행렬 메모리를 공유할 수도 있고,

  • 다른 행렬 메모리를 가르킬 수도 있다.


만약, 같은 행렬 메모리를 공유하는 경우에는,

한 쪽에서 데이터를 수정했을 때, 반대편에서도 효과가 나타난다.


Mat을 복사하는 과정에서,

같은 행렬 메모리를 가르키는 행렬 헤더를 생성하는 것을 참조.


내용은 동일하지만, 다른 행렬 메모리를 가르키는 

행렬 헤더를 반환하는 것을 복사라고 한다.




 Mat 객체 생성

int data[] = {
    1, 2, 3, 4, 5, 6,
    7, 8, 9, 10, 11, 12
};

//! 행과 열을 크기지정.
Mat a1 = Mat(2, 2, CV_32SC3, Scalar(1, 2, 3));
Mat a2 = Mat(2, 2, CV_32SC3, data);

//! Size_ 객체로 크기지정.
Size size = {2, 2};
Mat b1 = Mat(size, CV_32SC3, Scalar(1, 2, 3));
Mat b2 = Mat(size, CV_32SC3, data);

//! 다른 Mat를 참조.
//! c가 변경되면 a1도 변경된다.
Mat c = Mat(a1);

//! 다른 Mat를 복사.
//! d가 변경되어도 a1은 상관없다.
Mat d = a1.clone();




 Mat 원소 읽어오기

int data[] = {
        1, 2, 3,
        2, 3, 4,
        3, 4, 5
};
Mat mat = Mat(Size(3, 3), CV_32SC1, data);

//! 원소 읽어오기.
for(int i=0; i<mat.rows; i++){
    for(int j=0; j<mat.cols; j++){
        cout << mat.at<int>(Point(i, j)) << ' ';
    }
    cout << '\n';
}




 특수 행렬 생성함수

다음과 같은 특수 행렬을 생성할 수 있다.

  • 전부 0으로 초기화된 행렬

  • 전부 1로 초기화된 행렬

  • 단위 행렬

//! 전부 0으로 초기화된 행렬.
Mat::zeros(2, 2, CV_32SC3);
Mat::zeros(Size(2, 2), CV_32SC3);

//! 전부 1로 초기화된 행렬.
Mat::ones(2, 2, CV_32SC3);
Mat::ones(Size(2, 2), CV_32SC3);

//! 단위 행렬
Mat::eye(2, 2, CV_32SC3);
Mat::eye(Size(2, 2), CV_32SC3);




 필드 및 메서드

  • 멤버 필드

Mat::rows   행의 개수
Mat::cols   열의 개수
Mat::data   행렬원소의 데이터 시작 포인터
Mat::step   행렬의 한 행이 차지하는 바이트 수


  • 멤버 메서드

Mat::channels()	 행렬의 채널 수 반환
Mat::depth()        행렬의 depth 반환
Mat::elemSize()	 행렬의 한 원소당 바이트 크기 반환
Mat::elemSize1()    행렬의 한 원소의 한 채널당 바이트 크기 반환
Mat::empty()        행렬 원소가 비어있는지 여부 반환
Mat::isSubmatrix()  참조 행렬인지 여부 반환
Mat::size()         행렬의 크기를 Size형으로 반환
Mat::total()	    행렬 원소의 전체 개수 반환



example)

Mat mat = Mat(2, 2, CV_32SC3, Scalar(1, 2, 3));

cout << mat.channels() << '\n';     //! 3
cout << mat.depth() << '\n';        //! 4
cout << mat.elemSize() << '\n';     //! 12
cout << mat.elemSize1() << '\n';    //! 4
cout << mat.empty() << '\n';        //! false
cout << mat.isSubmatrix() << '\n';  //! false
cout << mat.size() <<'\n';          //! [2 x 2]
cout << mat.total() << '\n';        //! 4




 할당 연산

Mat a = Mat(2, 2, CV_32SC3, Scalar(1, 2, 3));
Mat b = Mat(2, 2, CV_32SC3, Scalar(4, 5, 6));

//! 모든 원소를 주어진 Scalar 값으로 변경.
a = Scalar(7, 8, 9);

//! 행렬간 덧셈 할당.
a += b;
a -= b;

//! 참조 할당.
//! c가 변경되면 a도 같이 변경됨.
Mat c = a;




 복사 연산

Mat a = Mat(2, 2, CV_32SC3, Scalar(1, 2, 3));

//! clone()을 통한 복사.
//! 마스킹 기법을 적용할 수 없음.
Mat b = a.clone();

//! copyTo(dest, mask)를 통한 복사.
//! 마스킹 가능.
Mat c;
a.copyTo(c);

//! convertTo(dest, depth)를 통한 복사.
//! 형변환 용도로 쓰임, 마스킹 불가.
Mat d;
a.convertTo(d, CV_32SC3);




 Mat 객체의 크기 및 형태 변경

  • 행의 수 변경 (resize)

열의 수는 변경하지 않는다.
현재 행렬헤더에서 작업한다.
Mat m = Mat(2, 2, CV_32SC3, Scalar(1, 2, 3));
/*
 * [1, 2, 3, 1, 2, 3;
 *  1, 2, 3, 1, 2, 3]
 */


//! 행을 확장하거나 축소.
//! 확장되는 경우에 초기화할 스칼라 값을 넘겨줄 수 있음.
m.resize(3, Scalar(7, 8, 9));
/*
 * [1, 2, 3, 1, 2, 3;
 *  1, 2, 3, 1, 2, 3;
 *  7, 8, 9, 7, 8, 9]
 */


//! 스칼라 값을 넘겨주지 않으면 쓰레기 값으로 초기화 됨.
m.resize(4);
/*
 * [1, 2, 3, 1, 2, 3;
 *  1, 2, 3, 1, 2, 3;
 *  7, 8, 9, 7, 8, 9;
 *  ?, ?, ?, ?, ?, ?]
 */



  • 채널 및 크기 변경.  (reshape)

채널 및 크기가 변경된 참조된 행렬헤더를 반환한다.
clone하지 않는 이상, 참조방식으로 반환되는 것에 주의하자.
Mat m = Mat(2, 2, CV_32SC3, Scalar(1, 2, 3));
/*
 * [(1, 2, 3), (1, 2, 3);
 *  (1, 2, 3), (1, 2, 3)]
 */


//! 채널 수 변경.
m = m.reshape(2);
/*
 * [(1, 2), (3, 1), (2, 3);
 *  (1, 2), (3, 1), (2, 3)]
 */


//! 채널과 행의 수 변경.
m = m.reshape(2, 3);
/*
 * [(1, 2), (3, 1);
 *  (2, 3), (1, 2);
 *  (3, 1), (2, 3)]
 */


//! 에러 발생
//! 데이터에 딱 맞게 reshape할 수 없음.
m = m.reshape(3, 3);
/*
 * [(1, 2, 3), (1, 2, 3), (1, 2, 3);
 *  (1, 2, 3), !!!!!!!!!!!!!!!!!!!!]
 */ 



  • 채널 및 크기 변경

resize와 마찬가지로 현재 행렬헤더에서 작업한다.
Mat m = Mat(2, 2, CV_32SC3, Scalar(1, 2, 3));
/*
 * [(1, 2, 3), (1, 2, 3);
 *  (1, 2, 3), (1, 2, 3)]
 */


//! 채널 축소.
m.create(Size(2, 2), CV_32SC2);
/*
 * [(1, 2), (3, 1);
 *  (2, 3), (1, 2)]
 */


//! 채널 확장.
//! 아직 이전 데이터가 살아있음.
m.create(Size(2, 2), CV_32SC3);
/*
 * [(1, 2, 3), (1, 2, 3);
 *  (1, 2, 3), (1, 2, 3)]
 */




 로우 추가/삭제

Mat m = Mat(2, 2, CV_32SC2, Scalar(1, 2));
/*
 * [(1, 2), (1, 2);
 *  (1, 2), (1, 2)]
 */


//! 뒤에 붙일 데이터.
vector v = {4, 4, 6, 6};
Mat add = Mat(v);
/*
 * [4;
 *  4;
 *  6;
 *  6]
 */


//! 한 줄당 사이즈를 일치시켜 줌.
//! 한 줄당 사이즈가 맞지 않으면 에러 발생.
add = add.reshape(2, 1);
/*
 * [4, 4, 6, 6]
 */


//! 열 단위로 추가,
m.push_back(add);
/*
 * [1, 2, 1, 2;
 *  1, 2, 1, 2;
 *  4, 4, 6, 6]
 */


//! 끝에서 2개 열 제거.
m.pop_back(2);
/*
 * [1, 2, 1, 2]
 */




 행렬 기본연산

행렬의 기본 연산은 다음과 같다.
  • 두 행렬에서 자리가 같은 원소끼리 사칙연산.
  • 두 행렬에서 행렬의 곱셈.
  • 한 행렬에서 스칼라의 사칙연산.
  • 한 행렬에서 역행렬.
  • 한 행렬에서 전치.


example 1)

int data[] = {
        2, 2,
        1, 1
};
Mat mat = Mat(Size(2, 2), CV_32SC1, data);

//! 한 행렬에서 스칼라의 사칙연산.
Mat scalar_add = mat + 2;
Mat scalar_sub = mat - 2;
Mat scalar_mul = mat * 2;
Mat scalar_div = mat / 2;
/*
 * scalar_div :
 * [1, 1;
 *  0, 0]
 */

//! 두 행렬에서 자리가 같은 원소끼리 사칙연산.
//! 단, a/0 = 0으로 가정한다.
Mat mat_add = mat + scalar_add;
Mat mat_sub = mat - scalar_sub;
Mat mat_mul = mat.mul(scalar_mul);
Mat mat_div = mat / scalar_div;
/*
 * mat_div :
 * [2, 2;
 *  0, 0]
 */


example 2)

역행렬과 행렬의 곱셈을 통해, 아래의 연립 방정식을 풀어보자.

A^(-1)을 각 항의 왼쪽에 곱해주면 X를 얻을 수 있다.


float data1[] = {
        2, 1, 1,
        4, -6, 0,
        -2, 7, 2
};
float data2[] = {
        3,
        10,
        -5
};
Mat a = Mat(Size(3, 3), CV_32FC1, data1);
Mat b = Mat(Size(1, 3), CV_32FC1, data2);


//! 역행렬 구하기.
Mat inv = a.inv();

//! 두 행렬간 곱셈.
Mat ans = inv * b;
/*
 * [+1;
 *  -1;
 *  +2]
 */


example 3)

float data[] = {
        1,
        2,
        3
};
Mat mat = Mat(Size(1, 3), CV_32FC1, data);
/*
 * [1;
 *  2;
 *  3]
 */


//! 전치행렬 구하기.
Mat mat_t = mat.t();
/*
 * [1, 2, 3]
 */