[Computer Vision] Mat 클래스

2023. 10. 11. 00:07Run/Computer Vision

 

1. Mat 클래스

  • 2차원 행렬(이미지) 뿐 아니라 고차원 행렬을 표현할 수 있음 (2차원 영상 데이터가 가장 많음)
  • 하나 이상의 channel 가질 수 있어서 grayscale 또는 color 영상 저장할 수 있음
  • Mat 클래스에는 정수, 실수, 복소수 등으로 구성된 행렬 or 벡터를 저장할 수 있음
  • dims(차원), rows(행 개수), cols(열 개수) 멤버 변수 (rows * cols 는 width * height 와 순서 반대!)
  • data(데이터 저장된 메모리 공간 가리킴) 멤버 변수
  • 3차원 이상 행렬에서는 rows, cols 값 -1, 크기 정보는 size 멤버 변수 통해 참조
CV_8U unsigned char
CV_8S signed char
CV_16U unsigned short
CV_16S signed short
CV_32S int
CV_32F float
CV_64F double
CV_16F float16_t
  • 행렬을 구성하는 각 channel은 같은 자료형 사용해야 함
  • 깊이 정보 + 채널 수 정보 = Mat 객체의 type (ex. CV_8UC3)

 

2. 행렬 생성과 초기화

  • 행렬 크기 지정할 때 Size 클래스 사용할 수도 있음
  • 크기, 타입 지정해 Mat 객체 생성할 경우 원소 값 garabage value로 채워짐 (무조건 0으로 초기화되는 것 X)
  • 또는 4번째 인자로 Scalar 초기값 설정 가능 (B, G, R)
  • Mat 객체 생성할 때 데이터 메모리 공간 새로 할당하지 않고 기존에 할당된 공간 데이터를 원소 값으로 사용할 수 있음
    • float data[] = { 1, 2, 3, 4, 5, 6 };
    • Mat mat4(2, 3, CV_32FC1, data); // 포인터 사용
    • 원소 개수, 자료형 같아야 함
    • 데이터 값이 상호공유 되어 한쪽에서 값 변경하면 다른 쪽에서도 값 변경됨
    • Mat 객체가 소멸할 때 자동으로 메모리 해제되지 않으므로 직접 해제해야 함

 

  • Mat::zeros(rows, cols, type) / Mat::ones(rows, cols, type) / Mat::eye(rows, cols, type)
  • Mat 객체 생성하기 위해 Mat_ 클래스를 사용할 수도 있는데, 이때 자료형 명시적으로 지정해야 함
    • Mat_<float> mat1(2, 3);
    • mat1 << 1, 2, 3, 4, 5, 6;
    • Mat mat2 = mat1; // 얕은 복사
    • 위 세 줄을 축약해서 Mat mat2 = (Mat_<float>(2, 3) << 1, 2, 3, 4, 5, 6); 해도 됨
    • 초기화 리스트를 이용할 수도 있음 Mat mat3 = Mat_<float>({2, 3}, {1, 2, 3, 4, 5, 6});
  • 비어 있는 Mat 객체 또는 이미 생성된 Mat 객체에 새로운 행렬(새로운 크기와 타입) 할당하려면 create() 사용
  • create()는 초기화 기능이 없으므로 = 연산자 재정의 또는 setTo() 멤버 함수 사용

 

3. 행렬 복사

구분 기준: 포인터

  • Mat 클래스 객체에 저장된 영상 또는 행렬을 복사하는 가장 간단한 방법 = 복사 생성자 or 대입 연산자 사용
    • Mat img1 = imread("dog.bmp");
      Mat img2 = img1;
      // 복사 생성자 → 얕은 복사 (Mat img2(img1)도 동일함)
    • Mat img1 = imread("dog.bmp");
      Mat img2;
      img2 = img1;
      // 대입 연산자 (할당 연산자) → 얕은 복사
  • 복사본 영상 새로 생성하는 방법 = clone() or copyTo() 사용
    • Mat img1 = imread("dog.bmp");
      Mat img2 = img1.clone(); // 깊은 복사 (자기 자신과 동일한 Mat 객체 새로 만들어서 반환)
    • Mat img1 = imread("dog.bmp");
      Mat img2;
      img1.copyTo(img2); // 깊은 복사 (인자로 전달된 행렬에 자기 자신 복사, 크기/타입 다르면 새로 생성 후 값 복사)

 

4. 부분 행렬 추출

  • Mat 행렬에서 특정 사각형 영역 (Region Of Interest) 추출하고 싶을 때, 괄호 연산자 재정의 사용
    • Mat img1 = imread("cat.bmp");
      Mat img2 = img1(Rect(220, 120, 340, 240));
  • 괄호 연산자를 통해 얻은 부분 영상은 얕은 복사 형식
  • 영상 반전 (밝은 픽셀은 어둡게, 어두운 픽셀은 밝게) : 변수 앞에 ~ 연산자 붙임
  • 독립적인 복사본 사용하고 싶으면 img1(Rect(220, 120, 340, 250)).clone(); 이런 식으로
  • addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype);
    • alpha + beta = 1 : 밝기 변화 X / alpha + beta > 1 : 밝아짐 / alpha + beta < 1 : 어두워짐
    • dst(I) = saturate(src1(I) * alpha + src2(I) * beta + gamma)
  • rowRange() : 지정된 범위의 행으로 구성된 행렬 반환
  • colRange() : 지정된 범위의 열로 구성된 행렬 반환
  • row() : 1행짜리 행렬 생성 / col(): 1열짜리 행렬 생성

 

5. 행렬 원소 값 참조

  • at()
    • 행렬 원소 자료형 명시적으로 지정해야 함
    • Mat 객체 img의 (y, x) 좌표 픽셀 값 참조하려면 img.at<uchar>(x, y) 작성
    • 행렬 크기 범위 벗어나는 경우 에러
  • ptr()
    • Mat 행렬에서 인자로 전달된 번호의 행의 첫 번째 원소 주소(포인터) 반환
  • imwrite(const String& filename, InputArray img, const vector<int>& params);

 

6. 행렬 정보 참조

  • Mat 클래스에 저장된 객체가 영상이면 imshow() 통해 화면에 표시할 수 있음
  • cols, rows, channels(), type()

 

7. 행렬 연산

  • OpenCV는 Mat 클래스가 표현하는 행렬을 수식 쓰듯 사용할 수 있도록 연산자 재정의 함수 제공
  • 최적화 되어있으므로 사용 권장
mat3 = mat1 + mat2
mat3 = mat1 - mat2
행렬 간 덧셈, 뺄셈
mat3 = mat1 + s1
mat3 = mat1 - s1
mat3 = s1 + mat
mat3 = s1 - mat
행렬의 각 원소와 스칼라 간 덧셈, 뺄셈
mat3 = -mat1 행렬의 각 원소에 -1 곱셈
mat3 = mat1 * mat2 행렬 간 곱셈
mat3 = mat1 * d1
mat3 = d1 * mat
행렬의 각 원소에 실수 곱셈
mat3 = mat1 / mat2 같은 위치 원소끼리 나눗셈
mat3 = mat1 / d1
mat3 = d1 / mat
행렬의 각 원소와 실수 간 나눗셈
mat3 = mat1.mul(mat2) 같은 위치의 원소끼리 곱셈
mat3 = mat1.inv() 역행렬 (inverse matrix)
mat3 = mat.t() 전치 행렬 (transpose matrix)

 

8. 크기 및 타입 변환 함수

  • 행렬 타입 변환 시 convertTo() 사용
    • Mat img1 = imread("lenna.bmp", IMREAD_GRAYSCALE);
      Mat img2;
      img1.convertTo(img2, CV_32FC1);
    • CV_8UC1 → CV_32FC1, normalization, color → grayscale 등에 사용
  • 행렬 크기 및 타입 변환 시 reshape() 사용
    • 주어진 행렬의 크기 또는 채널 수 변경
    • uchar data[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
      Mat mat1(3, 4, CV_8UC1, data);
      Mat mat2 = mat1.reshpae(0, 1);  // 채널 수 변경 X, row = 1 → col = 12 자동으로 결정됨
      Mat mat3 = mat1.reshape(3, 2); // 채널 수 = 3, row = 2 → col = 2 자동으로 결정됨
  • 행렬의 행 크기 변경 시 resize() 사용 (resize rows)
    • uchar data[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
      Mat mat1(3, 4, CV_8UC1, data);
      mat1.resize(5, 100); // 추가된 두 행의 값 100으로 초기화 (5 x 4 행렬)
  • 이미 존재하는 행렬에 원소 데이터 추가 시 push_back(), 제거 시 pop_back() 사용
    • mat1.push_back(mat3); // mat1 마지막 행에 mat3 추가
    • mat1.pop_back(2); // mat1 마지막 두 행 제거

 

(+) InputArray & OutputArray 클래스

  • InputArray 클래스
    • imshow() 함수도 영상을 InputArray 타입으로 전달받음
    • 재정의 : typedef const _InputArray& InputArray; // Call-by-reference (값이 밖에서도 변함)
    • 다양한 타입으로부터 생성될 수 있는 인터페이스 클래스
    • _InputArray 클래스의 인스턴스 또는 변수 생성하는 것 금지되어 있음
  • OutputArray 클래스
    • OpenCV 함수들은 영상을 입력받아 영상 처리를 수행하고 결과를 다시 영상으로 생성하여 반환
    • 이때 출력 영상을 return 구문으로 반환하는 것이 아니라 OutputArray 클래스의 참조를 함수 인자로 사용하여 결과 영상 전달
    • 재정의 : typedef const _OutputArray& OutputArray; // Call-by-reference (값이 밖에서도 변함)
    • _OutputArray 클래스는 _InputArray 클래스 상속받아 만들어짐 (출력 부분 추가된 것)
    • 다양한 타입으로부터 생성될 수 있는 인터페이스 클래스
    • _OutputArray 클래스의 인스턴스 또는 변수 생성하는 것 금지되어 있음
  • InputOutputArray 클래스: 입력, 출력 역할 동시에 수행할 때 사용
  • Mat만 넘겨줄 때: InputArray, OutputArray, InputOutputArray 중 하나 사용
  • Mat<Vector> 등 넘겨줄 때: InputArrayofArray, OutputArrayofArray, InputOutputArrayofArray 중 하나 사용