본문 바로가기
코딩/OpenCV

[C++ opencv] 이미지의 pixel 데이터 접근하기, 색상 정보 확인하기

by DIYver 2020. 7. 28.

본문 목표

영상처리를 하다보면 영상의 pixel 데이터를 확인해봐야 할 때가 생기기 마련이다.
OpenCV에서는 어떻게 pixel 데이터를 확인할 수 있는지 알아보자.



키워드 : at, ptr, data

 

 

 

 

 

알아볼 함수 원형

Mat::at

- 행렬 좌표로 픽셀 데이터 접근 ( img.at<Vec3b>(row, col) )

 for (int row = 0; row < img.rows; row++) 
 { 
 	for (int col = 0; col < img.cols; col++) 
    { 
    	uchar b = img.at<Vec3b>(row, col)[0]; 
        uchar g = img.at<Vec3b>(row, col)[1]; 
        uchar r = img.at<Vec3b>(row, col)[2]; 
        printf("\t (%d, %d, %d)", r, g, b); 
    } 
    cout << "\n" << endl; 
}


img.at<Vec3b>(row, col)[n] : (row, col) 좌표의 픽셀 데이터의 n번째 원소 값

컬러이미지는 3채널을 갖고 있다.
따라서 픽셀 하나에 3가지 값이 저장되는데, 순서대로 Blue, Green, Red 값이다.
n 번이 뜻하는 것이 바로 Blue 와 Green 과 Red 인 것이다.
n이 0이면 Blue 값을, n이 1이면 Green 값을 불러내는 것이다.

Grayscale은 1채널로 <Vec3b> 대신에 <uchar>를 사용한다.
그리고 채널이 1개 이므로, [n] 을 쓸 필요가 없다.

Mat::at 방식은 매번 픽셀의 위치를 계산하기 때문에 속도가 느리다.
반면에 포인터를 모르는 사용자에겐 가장 이해하기 쉬운 사용방법이다.



Mat::ptr

- 포인터로 픽셀 데이터 접근( img.ptr<uchar>(row) )

 for (int row = 0; row < img.rows; row++) 
 { 
 	uchar* pointer_row = img.ptr<uchar>(row); 
    for (int col = 0; col < img.cols; col++) 
    { 
    	uchar b = pointer_row[col * 3 + 0]; 
        uchar g = pointer_row[col * 3 + 1]; 
        uchar r = pointer_row[col * 3 + 2]; 
        printf("\t (%d, %d, %d)", r, g, b); 
    } 
    cout << "\n" << endl; 
 }

img.ptr<uchar>(row) : 메모리에 저장된 img 의 해당 row(행)의 주소

포인터를 이용하는 방법으로 쉽게 말하자면, 메모리 주소를 이용하는 방법이다.
이미지는 메모리상에 저장되게 되는데, 각 픽셀마다 메모리 주소를 갖게 된다.
메모리에 저장된 이미지의 같은 행은, 행에 대해서 같은 주소값을 갖게 된다.
왜냐하면 메모리도 결국엔 평면으로 되어있는 구조이고, 행렬이라 할 수 있기 때문이다.

순서를 보면, 우선 데이터를 확인할 행의 메모리 주소를 불러온 후에
해당 행의 데이터를을 확인하는 방식이다.

만약 3x3 행렬이 있다고 하고, 첫번째 행의 데이터를 접근했다고 하자.
그러면 데이터는 아래와 같이 순서를 이루고 있다.
B, G, R, B, G, R, B, G, R

왼쪽 부터 3개 씩 묶으면
[B, G, R], [B, G, R], [B, G, R] 처럼 3개의 픽셀 데이터가 된다.

아무튼, B, G, R 데이터는 3행 마다 반복되는 규칙이 있다.
따라서 행 데이터는

이런식으로 접근할 수 있는 것이다.


Mat::ptr 방식은 메모리 주소를 사용하여 즉각적으로 접근하는 방식인 셈이다.
메모리 접근을 통해서 빠르게 접근할 수 있다는 장점이 있다.





Mat::data

- 이미지 데이터로 데이터 접근

 uchar* img_data = img.data; 
 for (int row = 0; row < img.rows; row++) 
 { 
 	for (int col = 0; col < img.cols; col++) 
    { 
    	uchar b = img_data[row * img.cols * 3 + col * 3]; 
        uchar g = img_data[row * img.cols * 3 + col * 3 + 1]; 
        uchar r = img_data[row * img.cols * 3 + col * 3 + 2]; 
        printf("\t (%d, %d, %d)", r, g, b); 
    } 
	cout << "\n" << endl; 
}

Mat 클래스의 data에 직접 접근하는 방법이다.
방법은 포인터(주소값)를 이용한 것과 비슷하다.
따라서 걸리는 시간도 비슷하다.

앞서 다룬 포인터만 하더라도 행과 열로 데이터가 구성되어 있었다면,
data는 그 데이터가 일렬로 되어 있는 것이다.

예를 들면, 3x3 이미지가 있다고 한다면
총 9 픽셀이고, 각 픽셀에 대한 데이터 3개씩 있으므로
총 데이터는 27 개가 된다.

위에서 나온 27개의 데이터를 한 줄로 세웠다면
1행, 1열 의 Blue 데이터는 그 중에서 첫 번째 데이터일 것이다.
컴퓨터에서는 숫자를 0부터 세므로, 0번째 데이터인 것이다.
그렇다면 1행 1열의 Green 데이터는 몇 번째 데이터일까?
당연히 1번째 데이터가 되는 것이다.

살짝 꼬아서, 2행 1열의 Red 데이터는 몇 번째에 위치해 있을까?

1행에서 총 9개의 데이터를 지나쳐야 된다.
0번 부터 순서를 메긴다면 0~8 번 데이터가 1행의 데이터인 것이다.
그럼 9번부터 17번 까지가 2행의 데이터인 것이고,
Red 데이터는 11번 데이터인 것이다.

왜냐하면 9번 데이터는 2행 1열의 Blue 데이터이고,
10번 데이터는 2행 1열의 Green 데이터이기 때문이다.

아무튼 데이터를 통해서 픽셀 데이터에 접근할 수 있다.





 

 

 

코드 테스트 결과

- CODE

#include <opencv2/opencv.hpp> 
using namespace cv; 
using namespace std; 
int main(int ac, char** av) 
{ 
	Mat img = imread("color_3x3.png"); 
    printf(" \t(R, G, B) data : Mat::at \n"); 
    for (int row = 0; row < img.rows; row++) 
    { 
    	for (int col = 0; col < img.cols; col++) 
        { 
        	uchar b = img.at<Vec3b>(row, col)[0]; 
            uchar g = img.at<Vec3b>(row, col)[1]; 
            uchar r = img.at<Vec3b>(row, col)[2]; 
            printf("\t (%d, %d, %d)", r, g, b); 
        } 
    	cout << "\n" << endl; 
    } 
    printf("\n\n\n"); 
    printf(" \t(R, G, B) data : Mat::ptr \n"); 
    for (int row = 0; row < img.rows; row++) 
    { 
    	uchar* pointer_row = img.ptr<uchar>(row); 
    	for (int col = 0; col < img.cols; col++) 
        { 
        	uchar b = pointer_row[col * 3 + 0]; 
            uchar g = pointer_row[col * 3 + 1]; 
            uchar r = pointer_row[col * 3 + 2]; 
            printf("\t (%d, %d, %d)", r, g, b); 
        } 
        cout << "\n" << endl; 
    } 
    printf("\n\n\n"); 
    printf(" \t(R, G, B) data : Mat::data \n"); 
    uchar* img_data = img.data; 
    
    for (int row = 0; row < img.rows; row++) 
    { 
    	for (int col = 0; col < img.cols; col++) 
        { 
        	uchar b = img_data[row * img.cols * 3 + col * 3]; 
            uchar g = img_data[row * img.cols * 3 + col * 3 + 1]; 
            uchar r = img_data[row * img.cols * 3 + col * 3 + 2]; 
            printf("\t (%d, %d, %d)", r, g, b); 
        } 
        cout << "\n" << endl; 
    } 
return 0; 
}

 

- RESULT

 

사용한 이미지는 위와같은 색상 조합을 이용하였다.
3x3 의 크기이다.

맨 위는 Red, Green, Blue 원색으로 구성하였고,
중간 층은 White, Black, Yellow 원색으로 구성하였다.
마지막 층은 각각의 값을 섞어서 구성하였다.

위의 이미지를
각각의 방법으로 B G R 데이터에 접근해보았다.

결과를 보면 각각이 픽셀 위치에 색상 정보가 출력되었다.

우리는 흔히 RGB 로 읽으므로, 출력 순서를 그렇게 조정하였다.

세가지 방법 모두 동일한 결과를 보여주었다.



결론.

이미지를 구성하는 픽셀의 데이터에 접근하는 방법은 3가지가 존재한다.

속도는 pointer 와 data 를 통해 접근하는 것이 빠르다.










도움이 되었거나, 문제가 있는 경우 댓글로 알려주세요~!
감사의 댓글은 작성자에게 큰 힘이 됩니다 ^^

댓글