[OpenCV] 이미지 로딩과 Mat 클래스 파헤쳐보기 1

OpenCV에서 이미지를 로딩하는 방법과 cv::Mat 클래스의 구조와 사용법에 대해 알아보자

April 21, 2018 - 4 minute read -
opencv

OpenCV 에서 기본적으로 이미지를 로딩하면 cv::Mat 을 리턴하게 된다. cv::Mat 에 이미지의 각 픽셀에 대한 정보가 기록되어 있으며, 프로그래머는 값을 읽고 수정할 수 있다. 이미지는 어떤 형태로 로딩을 하느냐에 따라 cv::Mat 의 데이터 구조가 다르며, 값을 읽고 수정하는 방법도 여러 가지 방법이 있다.

이미지 로딩하기

이미지 로딩 시 cv::imread 함수를 사용하는데 인자로 파일 이름flag 를 넘겨준다.

flag 는 종류가 많지만 대표적으로 IMREAD_GRAYSCALE, IMREAD_COLOR 가 있으며, 이 두 가지만 알아볼 것이다.

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
using namespace std;

int main(int argc, char* argv[]) {
    // open the image with gray scale and color
    cv::Mat grayImage = cv::imread("../src/lena.jpg", cv::IMREAD_GRAYSCALE);
    cv::Mat colorImage = cv::imread("../src/lena.jpg", cv::IMREAD_COLOR);

    // show the window named with "gray image" and "color image"
    cv::namedWindow("gray image");
    cv::imshow("gray image", grayImage);

    cv::namedWindow("color image");
    cv::imshow("color image", colorImage);
    cv::waitKey(0);

    return 0;
}

결과는 다음과 같이 나올 것이다.

opencv result

cv::Mat 파헤쳐보기

이미지를 cv::imread 를 통해 로딩하면 픽셀 정보가 담긴 cv::Mat 인스턴스를 리턴하게 되고 이 인스턴스 안에 이미지의 각 픽셀 등에 대한 정보가 담겨 있다. Mat은 Matrix의 약자이며, 말 그대로 행렬(Matrix)과 비슷한 구조를 가지고 있다. 세로로 M 픽셀, 가로로 N 픽셀로 이루어진 이미지를 MxN 행렬 형태로 저장한다.

+ 간단한 행렬 생성해보기

행렬을 생성하는데 생성자(constructor)를 통해 인자를 넘겨줄 수 있고, 먼저 선언한 후 create 메소드를 사용해서 행렬을 생성할 수 있다. 아래 코드에서 M1, M2, M3, M4 는 모두 3x4 행렬을 생성한 것이다.

// create a 3x4 matrix with signed integer data type
cv::Mat M1(3, 4, CV_32SC1);

cv::Mat M2(cv::Size(3, 4), CV_32SC1);

cv::Mat M3;
M3.create(3, 4, CV_32SC1);

cv::Mat M4;
M4.create(cv::Size(3, 4), CV_32SC1);
}

행렬을 생성할 때 CV_32SC1 를 넘겨주었는데 이는 행렬 안에 있는 데이터의 데이터 타입(data type)을 정의한 것으로

CV_[데이터 비트 수][S: signed, U: unsigned]C[데이터 개수(채널 수)]

를 나타낸다.

즉, CV_32SC1

데이터 비트 수 = 32
S: signed, U: unsigned = S (즉, signed)
데이터 개수(채널 수) = 1

이므로 각 행렬 요소 당 32비트, Signed (일반적으로 c++ 에서 우리가 아는 int 랑 동일하다 생각하면 된다) 의 데이터 1개가 저장된다고 생각하면 된다.

데이터 비트 수에는 8, 16, 32, 64 등이 있고, 데이터 개수는 말 그대로 행렬의 각 요소당 데이터가 몇 개 들어가는지에 대한 정보 이며 우리는 이것을 보통 채널 수(the number of channels)라고 표현한다. 이후에 후술하겠지만 컬러 이미지의 경우 각 이미지 픽셀에 R, G, B 세 개의 컬러 정보가 들어가기 때문에 이를 위해 행렬 요소 하나에 여러 개의 데이터(마치 배열처럼)가 들어갈 수 있도록 똑똑하게 만들어놨다.

기본적인 행렬은 2차원(2 dimension) 이지만 3차원 또는 그 이상의 고차원적인 행렬을 생성할 수 있는데 이를 생성하기 위해서는 몇 차원인지와 각각 dimension 마다 크기가 정의된 배열을 넘기면 된다.

// create 3x4x5x6 matrix (4-dimension)
int sizes[] = {3, 4, 5, 6};
cv::Mat M(4, sizes, CV_32SC1);

0행렬, 1로 채워진 행렬, 단위 행렬(Identity Matrix)같은 경우는 다음과 같이 생성할 수도 있다.

// create a 2x2 matrix filled with 0
cv::Mat O = cv::Mat::zeros(2, 2, CV_32SC1);
// create a 2x2 matrix filled with 1
cv::Mat N = cv::Mat::ones(2, 2, CV_32SC1);
// create a 2x2 identity matrix
cv::Mat E = cv::Mat::eye(2, 2, CV_32SC1);

+ 행렬 출력하기

행렬에 어떤 값들이 들어가 있는지 알아보기 위해 다음과 같이 c++ 에서 제공하는 std::cout 으로 출력하면 된다.

cv::Mat E = cv::Mat::eye(2, 2, CV_32SC1);
std::cout << M << std::endl;

결과는 다음과 같이 출력된다.

[1, 0;
 0, 1]

+ 이미지와 행렬의 관계

아까 말했듯이 이미지를 로딩하면 행렬을 반환하는데 행렬에 픽셀에 대한 정보가 들어있다고 서술했다.

cv::IMREAD_GRAYSCALE로 로딩한 경우 데이터 타입이 CV_8UC1이 되는데

데이터 비트 수 = 8
S: signed, U: unsigned = U (즉, unsigned)
데이터 개수(채널 수) = 1

이므로 각 픽셀마다 0-255 값, unsigned (즉, unsigned char이다!) 1개가 저장된다고 할 수 있다.

cv::IMREAD_GRAYSCALE은 이미지를 흑백으로 로딩을 하는데 각 픽셀마다 0-255 값, 즉, 0은 검은 색, 255은 흰색, 그 사이값은 회색을 나타낸다고 보면된다.


cv::IMREAD_COLOR로 로딩한 경우 데이터 타입이 CV_8UC3이 되는데

데이터 비트 수 = 8
S: signed, U: unsigned = U (즉, unsigned)
데이터 개수(채널 수) = 3

이므로 각 픽셀마다 0-255 값, unsigned 3개가 저장된다고 할 수 있다.

cv::IMREAD_COLOR은 이미지를 RGB 컬러로 로딩을 하는데 각 픽셀마다 0-255 값 3개를 나타낸다. 다만 주의해야 할 것이 있는데 픽셀이 RGB 형태로 저장되는게 아니라 BGR 형태로 저장된다. (…) 이후에 행렬 값 변경하기에서 후술할 것이다.

Next Post

[OpenCV] 이미지 로딩과 Mat 클래스 파헤쳐보기 2