이전 글에서 ( http://nextus.tistory.com/14 ) 이미지 정보를 Mat 데이터타입에 저장하여 그 속에 어떤 값들이 들어있는지 대략적으로 확인하였다.
이번에는 좀 더 자세히 Mat의 픽셀 정보에 대해 알아본다.
Mat 데이터 출력
Mat 자체를 그대로 cout을 통해 출력하면 픽셀 데이터 값들을 확인할 수 있다.
예제로 512 x 512 Lena 이미지를 사용하여 픽셀 데이터를 출력해본다.
해당 출력결과를 보기 전에 소스를 보면, 원본 Lena 파일은 3채널의 영상이며 512 x 512 픽셀 크기를 가지고 있어 이를 출력하려면 512 x 512 x 3 개수의 값들이 출력되기에 범위를 지정하여 확인해보았다.
Mat grayRoi = grayImage(Rect(5, 5, 5, 5));
이 부분이 grayImage라는 Mat에서 Roi(관심영역)을 지정하여 새로운 grayRoi 라는 Mat을 생성하는 코드이다.
( Roi 지정을 통해 Mat을 만들게 되면 원본 Mat이 저장된 메모리를 공유하여 참조하는 형태로 생성된다. )
또한 Rect(5,5,5,5)는 Rect(int x, int y, int witdh, int height) 함수로 (5, 5) 지점을 기준으로 5 x 5 크기의 영역을 지정한다.
콘솔 결과 화면을 보면 Original Mat의 가로 값은 15개이고 Gray Mat 은 5개로 나타난 것을 알 수 있다.
그 이유는 Original 영상은 3채널이고 Gray 영상은 1채널이기 때문이다.
즉, Original의 1~3번째 값 : 116, 132, 221 이 세개의 값이 (116, 132, 221) 로 하나의 픽셀을 나타낸다.
Mat Data 접근 방법
Mat에 픽셀 데이터가 어떻게 저장되어있는지는 위에서 확인하였으니 원하는 픽셀 위치의 값을 확인하고 변경하는 작업을 해보자.
이러한 학습에 있어서 대표적인 예가 "소금 뿌리기" 이다.
( 소금 뿌리기란, 영상에 랜덤한 위치에 흰색 점을 마구 생성하여 소금이 뿌려져있는 것처럼 보이게 만드는 것을 말한다. )
우선 픽셀 데이터에 접근하는 3가지 함수들이 있다
1) At
Mat image;
image.at<DATA_TYPE>(row, col); return type : DATA_TYPE
3가지 접근법 중 속도가 가장 느리지만, 유효성검사를 수행하기 때문에 안정적이다.
DATA_TYPE 에는 영상의 차원에 따라 적절한 타입을 넣어주면 된다.
2) ptr
Mat image;
image.ptr<DATA_TYPE>(row, col); return type : DATA_TYPE*
At 보다 속도가 빠르며 리턴 타입이 DATA_TYPE의 포인터이다.
3) data
Mat image;
DATA_TYPE data = (DATA_TYPE*)image.data;
// row, col 위치 일 경우
data[row*image.cols + col]
속도가 가장 빠르며 유효성검사를 수행하지 않아 부적절한 위치시 알기 힘들다.
이 함수들을 이용하여 "소금 뿌리기"를 해보자.
소스코드 편집기를 설치하지 않아서 일단 이미지로 코드를 올렸다. ( 짧아서 금방 따라할 수 있음 )
salt 함수를 보면 Parameter로 Mat 과 소금을 뿌릴 카운트를 받고 있으며, rand 함수를 이용해 랜덤한 x, y 포지션을 뽑아서 해당 위치에 접근하여 픽셀 값을 변경하는 코드이다.
mat.at<Vec3b>(y, x)[0] = 255;
mat.at<Vec3b>(y, x)[1] = 255;
mat.at<Vec3b>(y, x)[2] = 255;
이 부분을 보면 at 함수를 이용하여 접근하였고, 원본 영상이 3채널이기에 Vec3b 타입을 선언해주었다.
뒤에 [0], [1], [2] 는 채널의 번호를 의미하며 각각 B, G, R 채널을 말한다. 따라서 3개의 채널을 모두 255로 설정하면 흰색이 되어 소금이 뿌려진 것 처럼 보이게 된다.
마찬가지로 ptr, data도 비슷하게 적용하면 된다.