안녕하세요. 여러분들이 OpenCV를 이용하는 이유는 영상 및 비디오를 처리하는 모듈을 개발하기 위함일텐데요. 그렇다면 가장 기본이 되는 이미지 및 비디오를 데이터를 읽어오고 윈도우 화면에 출력해보는 시간을 갖도록 하죠.
1. OpenCV의 기본 자료형인 Mat 클래스
우리가 흔히 보는 화면은 2차원의 LED 배열로부터 화면의 색과 밝기를 표현합니다.
그림 1. LED 디스플레이 array |
다들 휴대폰 화면을 가까이서 자세히 보면 이런 형태의 모양이 보일겁니다. TV를 가까이서 보신 분들은 꽤 익숙하실겁니다.
우리는 이처럼 평면상에 점을 찍어서 하나의 Pixel(이미지의 최소 단위)을 표현을 하고 그 Pixel들을 모아 이미지를 만들어내죠. 그런데 그 크기가 매우 작기때문에 우리 눈에는 이러한 복잡하고 불연속적인 이미지가 보이는 것이 아니라 하나의 연속적인 그림으로 보이게 되는거죠.
(인간의 눈은 High frequency 성분을 구분할 수 있는 능력이 떨어지기 때문입니다 이와 관련된 영상신호처리 이론은 추후에 올릴예정입니다)
따라서 OpenCV도 화면에 이미지를 나타낼 때 값들이 모여있는 형태의 Matrix(행렬) 자료형이 필요한겁니다.
그림 2. 3D 좌표계에 표현된 RGB 색공간 |
Mat 클래스는 이러한 영상들을 간단하게 저장할 수 있도록 만들어 줍니다. Mat 오브젝트를 만드는 가장 손쉬운 방법은 생성자를 이용한 방법입니다.
Mat img(nrows, ncols, type, fillValue);
이런 형태로 선언하면 Mat 오브젝트를 생성할 수 있습니다. nrows는 행, ncols는 열, tpye은 이미지의 데이터 타입, fillValue는 이미지에 채우고 싶은 값입니다.
예시로 설명드리면
위 코드의 의미는
256(nrows) x 256(ncols) 크기의 이미지인데 픽셀당 Unsigned 8 bit integer(CV_8U)를 사용하여 스칼라 값인 255(Scalar(255))를 표현하겠다.
위 그림을 실행 시키면 화면에 아래와 같이 표현됩니다.
예시로 설명드리면
Mat img(256, 256, CV_8U, Scalar(255));
위 코드의 의미는
256(nrows) x 256(ncols) 크기의 이미지인데 픽셀당 Unsigned 8 bit integer(CV_8U)를 사용하여 스칼라 값인 255(Scalar(255))를 표현하겠다.
위 그림을 실행 시키면 화면에 아래와 같이 표현됩니다.
이미지의 데이터 타입에 들어가는 형태를 설명드리면
CV_<bit 수>{U or S or F}C<채널수>
- CV -> Computer Vision
- bit 수 -> 픽셀이 표현하는 데이터의 비트 수
- U -> Unsigned (-부호가 없는 0을 포함한 자연수를 의미)
- S -> Signed(-포함한 정수를 의미)
- F -> Floating point(실수형 표현을 의미)
- 채널 -> 이미지의 채널 수를 의미
정의는 아래와 같이 되어있습니다.
1
|
enum {CV_8U=0, CV_8S=1, CV_16U=2,CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6};
| cs |
지난 시간에 사용하였던 예제코드를 다시보면 이제는 이해가 가실겁니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char *argv[])
{
Mat img(256, 256, CV_8U, Scalar(255));
namedWindow("Mat img", WINDOW_AUTOSIZE);
imshow("Mat img", img);
waitKey(0);
return 0;
}
| cs |
한마디로 요약하면
256x256 unsigned int8의 이미지에 255값을 채운 윈도우를 띄워라 입니다.
2. 이미지 파일 로드하기
이에 따라 OpenCV는 다양한 이미지 파일 포멧을 Mat 형태로 저장할 수 있는 함수가 존재합니다.
기본 지원 파일 포멧
|
추가 파일 포멧
|
*.bmp, *.dib
*.pbm, *.pgm, *.ppm
*.sr, *.ras
|
*.jpeg, *.jpg, *.jpe
*.jp2
*.png
*.tiff, *.tif
*.webp
|
표 1. 사용 가능한 파일 포멧
이미지를 불러오는 함수는 다음과 같습니다.
1
|
Mat imread(const String& filename, int flags = IMREAD_COLOR )
| cs |
filename에 파일의 이름을 넣어주고 flags에는 이미지 변환 코덱에 주는 옵션이라고 생각하시면 됩니다.
옵션의 종류는 아래와 같습니다.
1
2
3
4
5
6
7
|
enum { IMREAD_UNCHANGED = -1, // 8bit, color or not
IMREAD_GRAYSCALE = 0, // 8bit, gray
IMREAD_COLOR = 1, // unchanged depth, color
IMREAD_ANYDEPTH = 2, // any depth, unchanged color
IMREAD_ANYCOLOR = 4, // unchanged depth, any color
IMREAD_LOAD_GDAL = 8 // Use gdal driver
};
| cs |
자신이 원하는 포멧을 정하고 파일을 읽으시면 됩니다.
이를 이용한 컬러 이미지를 읽고 흑백 이미지로 변환하여 파일로 출력하는 예제코드를 작성해보면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char *argv[])
{
Mat in, out;
in = imread("lenna.png", IMREAD_UNCHANGED);
if (in.empty())
{
cout << "File read error\n";
return -1;
}
namedWindow("lenna.png", WINDOW_AUTOSIZE);
namedWindow("lenna_gray.png", WINDOW_AUTOSIZE);
imshow("lenna.png", in);
cvtColor(in, out, COLOR_BGR2GRAY);
imshow("lenna_gray.png", out);
waitKey(0);
imwrite("lenna_gray.png", out);
return 0;
}
| cs |
강의에서 사용하는 모든 사진자료는 제 블로그 github에서 받으실 수 있습니다.
https://github.com/smail91/blogspot_repo/tree/master/TC
그림 4. 코드 실행 후 화면 |
empty()함수는 Mat의 멤버 함수로 빈 이미지일 경우 true를 반환합니다.
namedWindow("lenna.png", WINDOW_AUTOSIZE);
이 함수는 이름 그대로 윈도우에 띄울 창의 이름과 크기를 설정하는 함수입니다. 그냥 간단하게 autosize로 설정하면 이미지의 크기만큼 윈도우가 생성되어 디스플레이됩니다.
imshow("lenna.png", in);
이 함수도 역시 image를 show한다. 즉 위에서 생성한 윈도우 창 lenna.png 창에 in이라는 이미지를 출력하라는 함수입니다.
cvtColor(in, out, COLOR_BGR2GRAY);
이 함수는 convert color로 in의 color image를 gray scale로 변환하여 out에 넣어라 라는 함수입니다. 주로 color format 변환에 쓰입니다.
waitKey(0);
이 함수는 키 입력 이벤트가 일어나기 전까지 대기하여 우리가 영상을 확인할 수 있습니다. 0 대신 다른 delay값을 넣어서 시간을 조절할 수 있습니다.(ms 단위)
imwrite("lenna_gray.png", out);
이 함수는 lenna_gray.png라는 이름으로 out이미지를 파일로 출력하라는 의미입니다.
여기까지 성공하셨으면 여러분은 OpenCV 왕초보는 벗어나신겁니다. ㅎㅎ
조금 더 힘내서 비디오 캡처까지 따라가봅시다!
3. 비디오 캡처하기
왕초보를 벗어나신 여러분을 위하여 선코드부터 제시합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char *argv[])
{
Mat in_frame;
VideoCapture inVid(0);
if (!inVid.isOpened())
{
cout << "Camera error\n";
return -1;
}
namedWindow("VideoCapture", WINDOW_AUTOSIZE);
while (true)
{
inVid >> in_frame;
imshow("VideoCapture", in_frame);
if (waitKey(1000 / 30) >= 0) break;
}
inVid.release();
return 0;
}
| cs |
그림 5. 실행화면 (캠이 없어서 버추얼 아웃풋이 나오네요..) |
VideoCapture 클래스를 통해 웹캠을 읽어와서 (1000/30)밀리초마다 한번씩 이미지를 갱신해주는 코드입니다.
결국 video도 이미지를 짧은 시간동안 여러장(보통 1초에 30장 -> 30 FPS) 보여주어 움직이는 것처럼 보이게 만들기 때문에 위에 이미지를 읽는 코드와 거의 유사합니다.
여러분은 이 기본 코드를 이용하면 다양한 영상처리 프로그램을 제작하는 뼈대를 만드실 수 있을겁니다.
4. 참고문헌
[1] Learning Image Processing with OpenCV
[2] 위키피디아 LED array, RGB 색공간 그림