ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [OpenCV 4] 3.Mat 클래스
    Computer Vision/OpenCV 2020. 10. 17. 02:31

    OpenCV에서 가장 많이 사용하는 클래스는 행렬을 나타내는 Mat 클래스이다.

     

    Mat 클래스를 이용하여 행렬 또는 영상을 생성하는 기본적인 방법부터 행렬의 복사, 부분 행렬 추출, 행렬의 정보 참조 방법 등을 설명한다.

     

    또한 Mat 클래스에 저장된 행렬의 원소 혹은 영상의 픽셀 값에 접근하는 방법과 일반적인 행렬 연산 방법도 예제 코드와 함께 설명한다.


    Mat 클래스 개요

     

    Mat 클래스는 일반적인 2차원 행렬뿐만 아니라 고차원 행렬을 표현할 수 있으며, 한 개 이상의 채널(channel)을 가질 수 있다. Mat 클래스에는 정수, 실수, 복소수 등으로 구성된 행렬 또는 벡터(vector)를 저장할 수 있고, 그레이스케일 또는 컬러 영상을 저장할 수도 있다.  실제적으로는 2차원 영상 데이터를 저장하고 처리하는 용도로 가장 많이 사용되고 있다.

     

      class Mat
    02    {
    03    public:
    04        Mat();
    05        Mat(int rows, int cols, int type);
    06        Mat(Size size, int type);
    07        Mat(int rows, int cols, int type, const Scalar& s);
    08        Mat(Size size, int type, const Scalar& s);
    09        Mat(const Mat& m);
    10        ~Mat();
    11     
    12        void create(int rows, int cols, int type);
    13     bool empty() const;
    14     
    15        Mat clone() const;
    16        void copyTo(OutputArray m) const;
    17     Mat& setTo(InputArray value, InputArray mask=noArray());
    18     
    19     static MatExpr zeros(int rows, int cols, int type);
    20     static MatExpr ones(int rows, int cols, int type);
    21     
    22        Mat& operator = (const Mat& m);
    23     Mat operator()( const Rect& roi ) const;
    24     
    25        template<typename _Tp> _Tp* ptr(int i0 = 0);
    26        template<typename _Tp> _Tp& at(int row, int col);
    27     
    28        int dims;
    29        int rows, cols;
    30        uchar* data;
    31     MatSize size;
    32        ...
    33    };

    간략화한 Mat 클래스 정의다.

     

     Mat::dims 멤버 변수는 Mat 행렬의 차원을 나타내며, Mat::rows는 행렬의 행 개수를 나타내고, Mat::cols는 열 개수를 나타낸다. 

     

    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 행렬의 깊이(depth)가 CV_8U라는 것은 이 행렬의 원소가 unsigned char 자료형을 사용한다는 의미다. 만약 행렬 원소를 float 자료형으로 표현하려면 깊이가 CV32F인 행렬을 사용해야 한다.

     

    영상의 개념과 자료형을 생각하기 좋은 표이다. 1byte = 8bit 이다.

     

    일반적으로 흑백영상은 채널1이고 픽셀을 0~255로 생각해서 uchar로 해주면 된다.


    행렬의 생성과 초기화

     

    장 기본적인 Mat 객체 생성 방법은 Mat 클래스의 기본 생성자를 이용하는 방법이다. 기본 생성자는 아무런 인자를 받지 않으며 실제 코드를 작성할 때는 단순히 Mat 클래스 타입의 변수를 선언하는 형태다.

     

    Mat img;

     

    이렇게 생선된 img1 객체는 비어 있는 행렬이다. 즉 img1.rows와 img1.cols 값은 0이고, img1.data에도 0이 저장된다.

     

    Mat::Mat(int rows, int cols, int type);
    • rows
    
    새로 만들 행렬의 행 개수(영상의 세로 크기)
    
    • cols
    
    새로 만들 행렬의 열 개수(영상의 가로 크기)
    
    • type
    
    새로 만들 행렬의 타입

    이 생성자는 행 개수가 rows이고, 열 개수가 cols인 2차원 행렬(또는 영상)을 생성한다. 이 생성자의 세 번째 인자 type에는 Mat 객체의 타입을 나타내는 매크로 상수를 전달한다.

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

    Mat 클래스 생성자에 영상의 크기 정보를 지정할 때 가로 크기, 세로 크기 순서가 아니라 세로 크기, 가로 크기 순서인 점을 생각해야 된다. 일반적으로 rows(행) cols(열)이 순서대로 나오는다. 영상에서 행의 개수는 세로 크기의 개수이고 열의 개수는 가로 크기의 개수이다. 행렬을 생각해보면 된다.

     

    Mat::Mat(int rows, int cols, int type, const Scalar& s);
    Mat::Mat(Size size, int type, const Scalar& s);
    • rows
    
    새로 만들 행렬의 행 개수(영상의 세로 크기)
    
    • cols
    
    새로 만들 행렬의 열 개수(영상의 가로 크기)
    
    • size
    
    새로 만들 행렬의 크기
    
    • type
    
    새로 만들 행렬의 타입
    
    • s
    
    행렬 원소 초깃값

    초깃값 s의 타입으로 사용된 Scalar 클래스는 네 개의 실수 값을 저장할 수 있는 OpenCV 클래스이며, 주로 영상의 픽셀 값을 표현하는 용도로 사용된다. Scalar 클래스가 그레이스케일 영상의 픽셀 값을 표현할 때에는 하나의 멤버 변수만을 사용하고, 3채널 컬러 영상의 픽셀 값을 표현할 때에는 세 개의 멤버 변수를 사용한다. 

    Mat img5(480, 640, CV_8UC1, Scalar(128));          // initial values, 128
    Mat img6(480, 640, CV_8UC3, Scalar(0, 0, 255));    // initial values, red

    이 코드에서 그레이스케일 영상 img5는 모든 픽셀 밝기가 128로 설정된다. 그리고 컬러 영상 img6에 대해서는 초깃값으로 Scalar(0, 0, 255)를 지정하였으며, 이는 순수한 빨간색을 나타낸다. Scalar 클래스를 이용하여 컬러 영상의 색상을 지정할 때에는 파란색(B), 녹색(G), 빨간색® 색상 성분 순서로 값을 지정한다.

     

    static MatExpr Mat::zeros(int rows, int cols, int type);
    static MatExpr Mat::zeros(Size size, int type);
    • rows
    
    새로 만들 행렬의 행 개수(영상의 세로 크기)
    
    • cols
    
    새로 만들 행렬의 열 개수(영상의 가로 크기)
    
    • size
    
    새로 만들 행렬의 크기
    
    • type
    
    새로 만들 행렬의 타입
    
    • 반환값
    
    모든 원소가 0으로 초기화된 행렬 표현식

    0으로 초기화된 3×3 정수형 행렬을 생성하려면 다음과 같이 코드를 작성하면 된다.

    Mat mat1 = Mat::zeros(3, 3, CV_32SC1); // 0s matrix

     

    static MatExpr Mat::ones(int rows, int cols, int type);
    static MatExpr Mat::ones(Size size, int type);
    • rows
    
    새로 만들 행렬의 행 개수(영상의 세로 크기)
    
    • cols
    
    새로 만들 행렬의 열 개수(영상의 가로 크기)
    
    • size
    
    새로 만들 행렬의 크기
    
    • type
    
    새로 만들 행렬의 타입
    
    • 반환값
    
    모든 원소가 1로 초기화된 행렬 표현식

     

    static MatExpr Mat::eye(int rows, int cols, int type);
    static MatExpr Mat::eye(Size size, int type);
    • rows
    
    새로 만들 행렬의 행 개수(영상의 세로 크기)
    
    • cols
    
    새로 만들 행렬의 열 개수(영상의 가로 크기)
    
    • size
    
    새로 만들 행렬의 크기
    
    • type
    
    새로 만들 행렬의 타입
    
    • 반환값
    
    단위 행렬을 표현하는 행렬 표현식

    Mat mat2 = Mat::ones(3, 3, CV_32FC1); // 1s matrix

    Mat mat3 = Mat::eye(3, 3, CV_32FC1); // identity matrix

     

    위의 코드를 작성하면

     

    다음 그림을 볼 수 있다. 이 내용은 너무 쉬우므로 이 정도만 정리하겠다.

     


    행렬의 복사

     

     Mat 클래스 타입의 변수에 저장된 행렬 객체를 다른 행렬 객체에 대입하거나 복사하는 방법에 대해 알아보겠다.

     

    간단히 예제로 설명하겠다.

     

    void MatOp2()
    02    {
    03        Mat img1 = imread("dog.bmp");
    04     
    05        Mat img2 = img1;
    06        Mat img3;
    07        img3 = img1;
    08     
    09        Mat img4 = img1.clone();
    10        Mat img5;
    11        img1.copyTo(img5);
    12     
    13        img1.setTo(Scalar(0, 255, 255));    // yellow
    14     
    15        imshow("img1", img1);
    16        imshow("img2", img2);
    17        imshow("img3", img3);
    18        imshow("img4", img4);
    19        imshow("img5", img5);
    20     
    21        waitKey();
    22        destroyAllWindows();
    23    }

    dog 이미지를 img1에 저장하고

    img2 랑 img3을 대입 연산자(얕은 복사)로 만들고

     

    img4와 img5를 깊은 복사로 만든다

    .

     

    그림이 흑백이라 결과값이 이상한데 img1,2,3은 노랑색으로 나오고

    img 4,5는 dog 가 나온다. 포인터 개념과 유사하게 생각하면 된다. img4,5는 새로운 주소로 Mat을 생성해서 별개로 된다. img1,2,3은 대입연산자니까 하나를 바꾸면 나머지도 바뀐다.

     


    부분 행렬 추출

    Mat 클래스로 정의된 행렬에서 특정 사각형 영역의 부분 행렬을 추출하고 싶을 때에는 Mat 클래스에 정의된 괄호 연산자 재정의를 사용한다.

     

    Mat Mat::operator()(const Rect& roi) const;
    Mat Mat::operator()(Range rowRange, Range colRange) const;
    • roi
    
    사각형 관심 영역
    
    • rowRange
    
    관심 행 범위
    
    • colRange
    
    관심 열 범위
    
    • 반환값
    
    추출한 부분 행렬 또는 영상. 부분 영상의 픽셀 데이터를 서로 공유합니다.
    Mat img1 = imread(“cat.bmp”);
    Mat img2 = img1(Rect(220, 120, 340, 240));

    3채널 영상의 cat 이미지를 받아서 Rect로 범위를 설정해서 그 부분만 영상을 추출한다.

    여기서 주의할점은 부분 영상을 추출한 후 부분 영상의 픽셀 값을 변경하면 추출한 부분 영상뿐만 아니라 원본 영상의 픽셀 값도 함께 변경된다는 점이다.

     


    행렬의 원소 값 참조

    OpenCV는 Mat 클래스에 저장된 행렬 원소 값을 참조하고 값을 변경할 수 있는 다양한 인터페이스를 제공한다. 그 중 3가지 픽셀 값 접근 방법에 대해 알아보자.

     

    Mat::at() 함수

     

    Mat::at() 함수는 보통 행과 열을 나타내는 두 개의 정수를 인자로 받아 해당 위치의 행렬 원소 값을 참조 형식으로 반환한다. Mat::at() 함수는 템플릿을 사용하는 템플릿 함수로서 여러 가지 형태로 재정의가 되어 있다.

     

    emplate<typename _Tp> _Tp& Mat::at(int y, int x)
    • y
    
    참조할 행 번호
    
    • x
    
    참조할 열 번호
    
    • 반환값
    
    (_Tp& 타입으로 형 변환된) y번째 행, x번째 열의 원소 값(참조)
    
    

    Mat::at() 함수는 템플릿 함수로 정의되어 있기 때문에 Mat::at() 함수를 사용할 때에는 행렬 원소 자료형을 명시적으로 지정해야 한다. Mat 행렬의 타입이 CV_8UC1이면 uchar 자료형을 지정하고 CV_32FC1 타입의 행렬이라면 float 자료형을 지정해야 한다. 만약 CV_8UC3 타입을 사용하는 3채널 컬러 영상이라면 OpenCV에서 정의한 Vec3b 자료형을 명시해서 사용한다

     

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

    위와 같은 코드를 돌려보면

     

    다음 그림과 같이 이중 포문을 이용해 행렬의 원소를 파악 할 수 있다.

     

    Mat::ptr( ) 함수

     

    Mat::ptr() 함수는 Mat 행렬에서 특정 행의 첫 번째 원소 주소를 반환한다. Mat::ptr() 함수는 여러 가지 형식으로 재정의되어 있지만, 가장 널리 사용하는 Mat::ptr() 함수 형식은 다음과 같다.

     

    template<typename _Tp>
    _Tp* Mat::ptr(int y)
    • y
    
    참조할 행 번호
    
    • 반환값
    
    (_Tp 타입으로 형 변환된) y번째 행의 시작 주소

    행렬 정보 참조하기

    Mat 객체에서 가장 자주 참조하는 정보는 행렬 또는 영상의 크기 정보다. Mat::rows 멤버 변수는 행렬의 행 개수를 나타내고, Mat::cols 멤버 변수는 열 개수를 나타낸다. Mat 객체에 영상이 저장되어 있는 경우라면 Mat::rows는 영상의 세로 픽셀 크기이고, Mat::cols는 영상의 가로 픽셀 크기를 나타낸다. 이들 멤버 변수는 모두 public 접근 지시자로 선언되어 있기 때문에 클래스 외부에서도 자유롭게 접근할 수 있다.

     

    .

    행렬 연산과 크기 및 타입 변환은 쉬운 관계로 생략하겠다.

     

    참고자료 : OpenCV 4로 배우는 컴퓨터 비전과 머신러닝

    댓글

Designed by Tistory.