[스터디] OpenCV 소개와 간단 함수

5050·2021년 9월 23일
0

OpenCV

목록 보기
2/2

OpenCV

오픈소스로 개발되고 있는 컴퓨터 비전 및 머신 러닝 라이브러리이다.

2500개가 넘는 최신 컴퓨터 비전 알고리즘과 머신 러닝 알고리즘을 포함하고 있다.

공식 웹 사이트 주소 https://opencv.org/

OpenCV는 실시간 처리를 고려하여 만들어졌기 때문에 다양한 하드웨어 플랫폼에서 매우 빠르게 동작한다.

World 모듈은 개별적으로 나뉘어 있는 OpenCV 모듈을 모두 합쳐 하나의 모듈로 재구성한 것이다.

그러므로 world 모듈 하나만 프로젝트에 추가한다면 OpenCV에서 제공하는 모든 기능을 사용할 수 있다.

추가 모듈은 github에서 opencv_contrib 저장소를 통해 따로 배포되고 있다.

OpenCV에서 영상 데이터는 Mat 클래스를 이용하여 표현한다.

Mat 클래스는 다양한 자료형의 행렬을 표현할 수 있는 범용 행렬 클래스다.

영상을 읽어올 땐 imread 영상을 쓸 땐 imwrite

영상이 비었는 지 체크할 땐 empty

영상이 출력될 창을 지정할 땐 namedWindow

창을 끄고 싶을 땐 destroyWindow, destroyAllWindow

지정된 위치로 옮기고 싶을 땐 moveWindow

창의 크기를 변경할 땐 resizeWindow (창의 전체크기가 아닌 뷰의 영역에 나타나는 크기를 의미한다.)

이미지를 창에 띄우고 싶을 땐 imshow

키의 입력을 받고 싶을 땐 waitKey 함수를 이용한다.

OpenCV에서는 자주 사용되는 다양한 자료형 클래스를 지원한다.

Point 클래스

자료형(정수, 실수 등)에 맞는 x, y값을 멤버변수로 갖는 클래스로 멤버 메소드로는

내적이나 외적같은 다양한 연산도 갖고 있다.

수학에서 사용되는 벡터처럼 실수배 벡터간의 연산처럼 사용할 수 있다.

Size 클래스

사각형의 가로와 세로를 나타내는 width, height를 멤버 변수로 갖는 클래스로

Aread와 같은 멤버 함수를 갖는다.

Point와 마찬가지로 자료형에 맞게 정의되어 있지만 기본형은 정수이다.

마찬가지로 스칼라배, 덧셈 연산이 지원된다.

Rect 클래스

사각형의 위치와 크기를 나타내는 클래스이다.

좌측 상단 점을 반환하는 Tl()

우측 하단 점을 반환하는 Br()

사각형의 크기를 반환하는 Size()

면적을 반환하는 Area()

점이 사각형 내부에 존재하는 지 반환하는 Contains()

등의 메소드를 지원하며, 사각형의 시작점을 +(Point타입으로)연산으로 옮기거나

사이즈를 + (Size타입으로 더할 경우)로 옮길 수 있다.

&연산을 활용한다면 교차하는 사각형의 영역도 구할 수 있고

|연산은 합집합을 구할 수 있다.

RotatedRect 클래스

회전된 사각형을 표현하는 클래스이다. 사각형의 중심, 가로, 세로, 회전 각도 정보를 가진다.

boundingRect와 같은 함수로 회전된 사각형을 포함하는 최소의 사각형을 반환한다.

Range 클래스

범위 또는 구간을 표현하는 클래스로 시작과 끝을 나타내는 멤버 변수를 가진다.

all 함수는 시작과 끝 지점이 정수의 최저, 최댓값으로 설정한 Range 객체를 반환한다.

String 클래스

std::string 클래스와 완전히 호환되도록 설계되어 있으며, 문자열을 저장하고 처리할 수 있다.

하지만 OpenCV4.0부터는 std::string 클래스를 재정의하여 사용되고 있다.

format() 함수를 사용하여 C에서 print("%d\n", 3); 처럼 지정한 형식으로 값을 저장할 수 있다.

★Mat 클래스

OpenCV에서 가장 많이 사용되는 클래스로 행렬을 나타내는 클래스이다.

2차원 행렬뿐만 아니라 고차원 행렬을 표현할 수 있고, 한 개 이상의 채널을 가질 수 있다.

위의 소개된 다른 클래스처럼 자료형별로 저장할 수 있고 채널을 여러개 가질 수 있기 때문에

회색조, 컬러영상 모두 저장할 수 있다.

dims는 행렬의 차원을 나타내고, rows는 행렬의 행 개수, cols는 열의 개수를 나타낸다.

하지만 3차원부터는 유효하지 않은 값을 가진다.(rows, cols 모두 -1을 가진다.)

3차원 이상부터는 size 멤버 변수를 이용해 참조한다.

data는 원소 데이터가 저장되어 있는 메모리 공간을 가리키는 포인터형 멤버 변수이다.

Mat 클래스에 어떤 자료형이 저장되어 있는지 확인하려면 depth를 사용한다.

열거형으로 CV_8U ~ CV_64F 등이 있으며 총 8개가 사용되는 거 같다.

단순 생성자를 사용하여 크기와 타입만을 지정해 생성해줄 수 있지만

zeros, ones, eye와 같은 함수나 mat4 = Scalar(255, 0, 0)과 같은 방법으로 특정한 값으로 채워져있는 행렬을 만들 수 있다.

Mat mat5(2, 3);

mat5_ << 1, 2, 3, 4, 5, 6;

와 같은 방법으로도 값을 할당해줄 수 있고

생성자를 호출 시 새로운 메모리 공간이 아니라 기존에 이미 할당된 공간의 데이터를 사용할 수 있다.

ex) Float data[] = {1,2,3,4,5,6};

Mat mat4(2, 3, CV_32FC1, data);

동적 할당된 메모리를 참조시에는 늘 메모리 해제에 주의해야 한다는 점은 잊지 말자!

create()함수를 사용한다면 비어있거나 이미 할당되어 있는 메모리를 해제하고 새롭게 할당해줄 수 있다.

그리고 Mat 클래스에서 대입연산자는 얕은 복사가 일어나는데 깊은 복사를 하고 싶다면 clone, copyTo와 같은 함수를

이용해야 한다.

영상의 부분을 추출할 때에는 img1(Rect(X, Y, W, H));와 같이 많이 이용하기도 하지만 이 것도

얕은 복사인 것을 주의하자.

행렬 원소 접근 방법으로는

mat.at(y, x);

mat.ptr(y);

mat.data[y * w + x] (정수 타입에서만 지원)

등이 있는데, at과 ptr은 매번 호출시마다 주소값을 계산하므로

모든 좌표를 접근할 때마다 호출하면 안되고 한 번 호출하고 배열처럼

사용할 수 있는 포인터를 활용하는 것이 속도상 이점을 가질 수 있다.

그리고 * 연산은 행렬곱으로 정의되어 있으므로 elementwise 곱을 하고 싶다면

mul() 멤버 함수를 이용해야 한다.

역행렬을 구하고 싶다면 inv() 함수를 이용한다.

디폴트로는 가우스 소거법으로 진행되고, 매개변수를 바꿔준다면 다른 방법으로 구할 수 있다.

그 외에도 전치행렬 t(), 타입변환 convertTo(), 행렬 모양 변경 reshape() (데이터 복사 X),

행렬의 행 크기 변경 resize()

원소 데이터 추가 push_back()

맨 아래의 행 삭제 pop_back()

등의 함수를 이용하면 된다.

VideoCapture 클래스

카메라 또는 동영상 파일로부터 프레임을 받아올 때 사용한다.

동영상도 결국에는 일련의 정지 영상으로 이루어져 있기 때문에

각 프레임마다 영상처리를 적용하는 형태로 진행된다.

동영상을 불러오려면 open 함수를 이용한다.

Img001.jpg, img002.jpg와 같은 일련의 숫자로 구분되는 정지 영상 파일을 차례로 불러오고 싶을 때에는 filename인자에 “img%03d.jpg”라고 입력하면 차례로 불러올 수 있다.

또한, URL을 지정할 수도 있다.

카메라 장치를 사용할 때에는 bool open(int index, int apiPreference = CAP_ANY) 함수를 이용한다.

Index = camera_id + domain_offset_id와 같은 형태로 구성된다.

만약 한 대의 카메라만 연결되어 있다면 카메라의 id는 0이다. Domain_offset_id는 대부분 자동 선택을 의미하는 0을 사용한다. 두 대의 카메라가 연결되어 있다면 0 또는 1을 지정한다.

동영상의 사용이 끝나면 반드시 release() 함수를 사용하여 사용하던 자원을 해제하여야 한다.

동영상을 정상적으로 열고난 후 한 프레임을 받아오기 위해서는 >>, read 함수를 사용한다.

여러 대의 카메라 동기화를 고려할 땐 read나 >> 대신 grab() 호출 후 retrieve() 함수를 호출하는 것이 좋다.

Retrieve()함수는 grab보다 수행 속도가 느린 편이다.

Grab은 카메라 장치에 다음 프레임을 획득하라는 명령을 내리는 함수고, retrieve는 획득한 프레임을 실제로 받아 오는 함수이다 read는 grab과 retrieve를 합쳐 놓은 것으로 볼 수 있다.

Get() 함수를 이용해 동영상 파일로부터 여러 가지 정보를 받아 올 수 있다. (영상의 크기, 현재 프레임 위치, 초당 프레임 수 등)

Set() 함수를 이용한다면 비디오 파일 재생과 관련된 속성 값을 설정할 수 있다.

VideoWriter 클래스

동영상 파일을 저장할 때 사용한다.

우선, bool VideoWriter::open(const String& filename, int fourcc, double fps, Size frameSize, bool isColor = true);를 이용한다.

Fourcc는 4 문자 코드의 약자로 네개의 문자로 구성된 코드이다.

Fourcc는 동영상 파일의 코덱, 압축 방식, 색상 혹은 픽셀 포맷 등을 정의하는 정수 값이며, 코덱을 표현하는 네 개의 문자를 묶어서 fourcc를 생성한다.

Static int VideoWriter::four(char c1, char c2, char c3, char c4);

이 함수를 사용하여 생성할 수 있다.

VideoWriter::four(‘F’, ‘M’, ’P’, ’4’); // FFMPEG MPEG4 코덱

여러 코덱을 생성할 수 있다.

열려있는 동영상에 새로운 프레임을 추가하기 위해서는 <<, write() 함수를 이용한다.

프레임 저장이 완료되었다면 release()를 사용해 열려있던 파일을 닫는다.

그외의 부가 기능

직선 그리기 line(), 화살표 그리기 arrowedLine(), 직선으로 이루어진 마커를 그리기 drawMarker() // MARKER_CROSS, MARKER_TILTED_CROSS 등의 타입이 있다.

사각형 그리기 rectangle(), 원 그리기 circle(), 타원 그리기 ellipse(), 임의의 다각형 그리기 polylines() 함수를 이용한다. // 꼭지점의 좌표를 전달해야 하는데, vector 를 이용한다.

문자열 출력하기 putText(), 문자열 폰트 종류, 폰트 크기를 반환하는 getTextSize()

키보드 입력을 확인하는 함수 waitKey(), 특정 창에 마우스 콜백 함수를 등록할 때에는 setMouseCallback() 함수를 사용한다. //onMouse는 void 반환에 (int event, int x, int y, int flags, void* userdata)인자를 가진다.

트랙바를 생성하려면 createTrackbar()하수를 사용한다. // onChange는 void 반환에 (int pos, void* userdata)를 인자로 가진다. 트랙바의 현재 위치를 얻고 싶다면 getTrackbarPos()를, 트랙바를 특정 위치로 이동시키고 싶다면 setTrackbarPos()를 사용할 수 있다.

일반적으로 Mat 클래스에 저장된 영상 데이터는 imwrite 함수를 사용하여 BMP, JPG, PNG 등 영상 파일로 저장이 가능하지만, int, float, double 등의 자료형을 쓰는 행렬은 imwrite로 저장할 수 없다.

FileStorage 클래스

데이터 파일 입출력을 담당하는 클래스이다.

이 클래스는 일반적인 C/C++ 자료형 데이터를 XML, YAML, JSON 등 파일 형식으로 저장하는 기능을 제공한다.

Bool FileStorage::open(const String& filename, int flags, const String& encoding=String())

함수를 사용하여 사용할 파일을 연다.

확장자 명에 따라 파일 형식이 자동으로 결정되며, “data.xml.gz” 처럼 뒤에 gz를 붙혀준다면 xml로 저장후 gz 형식으로 압축한다.

Flags에 FileStorage::READ, WRITE 등의 열거형으로 열기 모드를 정해줄 수 있다.

마찬가지로 마지막에는 release() 함수를 통해 사용하고 있던 파일을 닫고 메모리 버퍼를 해줘야 한다.

Ex) 파일 저장 예제

FileStorage fs(“data.json”, FileStorage::WRITE);

fs << “name” << “Jane”;

{

“name” : “Jane”

}

fs.release();

ex) 파일 불러오기

String name;

FileStorage fs(“data.json”, FileStorage::READ);

fs[“name”] >> name;

cout << “name : “ << name << endl;

마스크 연산

상황에 따라 사각형이 아닌 임의의 ROI 설정이 필요하기도 한데 이 때 마스크 연산을 사용한다.

일반적으로 마스크 영상은 사람의 눈으로도 구분이 쉽도록 픽셀 값이 0 또는 255로 구성된 흑백 영상이 사용된다.

setTo()함수의 두 번째 인자 mask에 마스크 영상을 지정할 수 있다.

또한, copyTo()함수에서도 두 번째 인자에 마스크 영상을 지정할 수 있다.

copyTo에서는 mask 영상의 픽셀 값이 0이 아닌 위치에서만 *this 행렬의 원소 값을 복사한다.

시간 측정

정밀한 시간 측정을 하기위해 getTickCount() 함수와 getTickFrequency() 함수를 사용한다.

getTickCount() 함수는 컴퓨터 시스템의 특정 시점부터 현재까지 발생한 틱 횟수를 반환한다.

틱 횟수는 컴퓨터 시스템에서 발생하는 클럭처럼 매우 빠르게 증가하는 성능 측정 계수를 의미하며, 컴퓨터 성능에 따라 틱 횟수는 빠르게 증가할 수도 있고 조금 느리게 증가할 수 있다.

어떤 연산 전에 getTickCount()호출 후 연산 후에 getTickCount()를 호출하고 둘의 반환값의 차이를 빼고 틱 주파수로 나누어 주는 작업이 필요하다.

틱 주파수란 1초 동안 발생하는 틱 횟수를 의미하며 getTickFrequency()함수를 사용해 구할 수 있다.

Int64 t1 = getTickCount();

Function();

Int64 t2 = getTickCount();

Double ms = (t2 – t1) * 1000 / getTickFrequency(); // 밀리초 단위 연산을 위해 1000을 곱해준다.

TickMeter라는 클래스로도 시간 측정이 가능하다.

TickMeter tm;

Tm.start();

Function();

Tm.stop();

Double ms = tm.getTimeMilli();

시간을 측정할 때에는 릴리스 모드로 빌드하여 실행해야 한다. 디버그 모드로 빌드할 경우 디버깅을 위한 연산이 추가되기 때문에 정확한 연산 시간을 측정할 수 없기 때문이다.

행렬 부가기능

행렬의 전체 원소 합을 구할 때에는 sum(InputArray src);를 사용하고 평균을 구할 때에는 mean(InputArray src, InputArray mask = noArray());를 사용한다.

Mean()함수는 마스크 연산을 지원하므로 필요한 경우 mask 영상을 지정하여 특정 영역의 원소 평균을 구할 수 있다.

행렬의 최솟값, 최댓값을 찾을 때에는 minMaxLoc(InputArray src, double minVal, double maxVal = 0, Point minLoc = 0, Point maxLoc = 0, InputArray mask = noArray());함수를 사용한다.

minMaxLoc도 마스크 연산을 지원해 일부 영역의 최솟값과 최댓값을 구할 수 있다.

행렬의 노름값을 정규화하거나 원소 값 범위를 특정 범위로 정규화할 때 사용하는 함수는 normalize(InputArray src, InputOutputArray dst, double alpha = 1, double beta = 0, int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray()); 함수이다.

Alpha는 노름 정규화인 경우에는 목표 노름값, 원소 값 범위 정규화인 경우에는 최솟값을 의미한다. (Beta는 최댓값)

[-1, 1] 사이의 실수 행렬을 [0, 255] 정수 행렬로 바꾸려면

Mat dst;

Normalize(src, dst, 0, 255, NORM_MINMAX, CV_8UC1);

실수 값을 정수 값으로 변환할 때 주로 반올림을 사용하는데 OpenCV 내에서 반올림 연산을 위해 cvRound()라는 함수를 지원한다.

profile
하이

0개의 댓글