[OpenCV] 이미지 RGB 분리하기

OpenCV에서 이미지를 로딩해서 RGB를 분리해보자

April 22, 2018 - 6 minute read -
opencv

이전 Post에서는 OpenCV에서 이미지를 로딩하는 방법과 Mat 의 기본 구조, 사용법에 대해서 알아보았다. 이번에는 이미지에서 R, G, B를 분리하는 실습을 진행할 것이다.

Previous Post

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

멀티 채널(multi-channel)과 단일 채널(single-channel)

cv::IMREAD_COLOR 로 로딩한 컬러 이미지의 경우 각 픽셀마다 R, G, B 값을 가지고 있다. cv::imread 로 오픈한 이미지는 픽셀 정보를 가지고 있는 Matrix (cv::Mat) 을 리턴하게 되는데 한 픽셀 당 3개(R, G, B)의 정보를 가지고 있으므로 3개 채널을 가지고 있다고 표현하며 RGB 컬러 이미지의 경우 3개의 채널을 각각 Red Channel, Green Channel, Blue Channel 이라고 한다. 그리고 2개 이상 채널을 가지고 있는 경우 multi-channel 이라 한다.

반면 cv::IMREAD_GRAYSCALE 로 로딩한 흑백 이미지의 경우 한 픽셀마다 완전 검은색(0), 회색(1-254), 흰색(255), 즉 하나의 채널을 가지고 있으며 single-channel 이라고 한다.

opencv result

왼쪽의 Matrix 는 cv::IMREAD_COLOR 로 로딩했을 때 이미지 행렬이고, 컬러 이미지의 3개 채널 Red Channel, Green Channel, Blue Channel 을 분리하여 오른쪽과 같이 3개의 single-channel matrix를 생성할 것이다.

그림을 보면 알 수 있듯이 OpenCV에서 컬러 이미지 Matrix 의 경우 R, G, B 형태로 저장되지 않고, B, G, R 순서로 저장이 되어 있다. 이를 주의하자.

Multi-Channel -> Single-Channel

OpenCV는 split 함수를 제공하는데 멀티 채널 Matrix를 여러 개의 싱글 채널 Matrix로 바꿔준다. 즉, 컬러 이미지의 경우 Red, Green, Blue 3개의 채널을 가지고 있으므로 각각 3개의 싱글 채널로 분리해준다.

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

int main(int argc, char* argv[]) {
    // load an image
    cv::Mat lena = cv::imread("../src/lena.jpg", cv::IMREAD_COLOR);

    // create a matrix for split
    cv::Mat bgr[3];

    // split image into 3 single-channel matrices
    cv::split(lena, bgr);

    // create 4 windows
    cv::namedWindow("Original Image");
    cv::namedWindow("Red Channel");
    cv::namedWindow("Green Channel");
    cv::namedWindow("Blue Channel");

    // show 4 windows
    cv::imshow("Original Image", lena);
    cv::imshow("Red Channel", bgr[2]);  // caution : the red channel index is 2!
    cv::imshow("Green Channel", bgr[1]);
    cv::imshow("Blue Channel", bgr[0]);

    cv::waitKey(0);
    return 0;
}

위와 같이 이미지를 로딩한 다음 cv::split 함수로 이미지를 분리시켜준다. 첫 번째 인자로 multi-channel matrix를 넣고, 두 번째 인자로 결과 값을 저장할 multi-channel matrix의 채널 개수만큼의 cv::Mat 배열을 넣어준다. 컬러 이미지의 경우 RGB 3개 채널이므로 cv::split의 destination 파라미터는 3개 cv::Mat 배열을 생성해야 한다.

또 주의할 점은 위에서 말했듯이 순서가 RGB 가 아니라 BGR 이다. 따라서 Red Channel 은 2번째 index이며, Blue Channel 은 0번째 index 에 저장되어 있다.

결과는 다음과 같다.

opencv result

원본 이미지가 대강 보기에도 붉은 색이 많이 보이는데 RGB 로 분리했더니 확실히 Red Channel 이 밝게 보이는 것을 확인할 수 있었다. 그런데 분리한 이미지가 흑백으로 나오는 이유는 무엇일까? 왜냐하면 흑백이 single-channel 이기 때문이다. 컬러 이미지 3개 채널을 single-channel 3개로 바꾸었으니 그 single-channel 이미지를 출력하니깐 흑백으로 나오는 것이다.

흑백 이미지 = Single Channel Matrix
RGB 컬러 이미지 = 3-Channel (Blue Channel, Green Channel, Red Channel) Matrix

그러면 Red-Channel은 빨간색으로, Green-Channel은 초록색으로, Blue Channel은 파란색으로 나오게 하려면 어떻게 해야할까? 이 분리한 이미지를 다시 컬러 이미지로 변환하면 된다. 즉, Red-Channel의 경우 Blue, Green 의 값이 모두 0인 3개 채널 Matrix 로 변환하면 되고, Green-Channel의 경우 Red, Blue의 값이 모두 0인 3개 채널 Matrix 로 변환하면 될 것이다.

Single-Channel -> Multi-Channel

Single-Channel Matrix 를 합쳐서 Multi-Channel 을 만들어주는 함수는 대표적으로 merge 함수가 있다.

+ merge 함수

merge 함수는 인자로 cv::Mat 배열, 배열 크기, Output Matrix를 넘겨 주는데 cv::Mat 배열의 개수만큼 채널을 생성해서 Output Matrix 에 넣어준다.

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

int main(int argc, char* argv[]) {
    // load an image
    cv::Mat lena = cv::imread("../src/lena.jpg", cv::IMREAD_COLOR);

    // create a matrix for split
    cv::Mat bgr[3];

    // create a zero matrix
    cv::Mat zFillMatrix = cv::Mat::zeros(lena.size(), CV_8UC1);

    // split image into 3 single-channel matrices
    cv::split(lena, bgr);

    // create 3 channel Color Matrix
    cv::Mat R[] = {zFillMatrix, zFillMatrix, bgr[2]};
    cv::Mat G[] = {zFillMatrix, bgr[1], zFillMatrix};
    cv::Mat B[] = {bgr[0], zFillMatrix, zFillMatrix};
    cv::merge(R, 3, bgr[2]);
    cv::merge(G, 3, bgr[1]);
    cv::merge(B, 3, bgr[0]);

    // create 4 windows
    cv::namedWindow("Original Image");
    cv::namedWindow("Red Channel");
    cv::namedWindow("Green Channel");
    cv::namedWindow("Blue Channel");

    // show 4 windows
    cv::imshow("Original Image", lena);
    cv::imshow("Red Channel", bgr[2]);  // caution : the red channel index is 2!
    cv::imshow("Green Channel", bgr[1]);
    cv::imshow("Blue Channel", bgr[0]);

    cv::waitKey(0);
    return 0;
}

위의 코드를 실행해보면 다음과 같이 R, G, B 가 분리된 컬러 이미지를 얻을 수 있다.

opencv result
cv::Mat zFillMatrix = cv::Mat::zeros(lena.size(), CV_8UC1);

zFillMatrix 는 위와 같이 이미지와 동일한 크기의 모두 0으로 채워진 single-channel matrix 이다.
(CV_8UC1 = 픽셀 하나에 0-255 값, 1개 채널)

cv::Mat R[] = {zFillMatrix, zFillMatrix, bgr[2]};
cv::Mat G[] = {zFillMatrix, bgr[1], zFillMatrix};
cv::Mat B[] = {bgr[0], zFillMatrix, zFillMatrix};

컬러 이미지는 B, G, R 로 이루어진 3개 채널이므로 각각 싱글 채널 3개를 모아서 cv::merge 함수를 이용해서 만들어야 한다. 이 때, RGB에서 각 색마다 다른 색상은 0으로 채우기 위해 아까 선언한 zFillMatrix (0으로 채워진 Matrix) 로 둔다.

cv::merge(R, 3, bgr[2]);
cv::merge(G, 3, bgr[1]);
cv::merge(B, 3, bgr[0]);

cv::merge 함수를 사용해서 N개의 싱글 채널멀티 채널(N개 채널) 1개로 합친다. 위 코드를 보면 Single-Channel 3개이므로 3개의 채널을 가진 Multi-Channel 1개 Matrix 를 생성한다. cv::split 함수를 설명한 아래 사진의 역과정이라고 이해하기 쉬울 것이다.

opencv result