지난주에 OpenCV를 이용해 화상카메라로부터 영상을 받아, 움직이는 물체를 검출해 보는 코드를 작성해 보았는데, 여기에 자동으로 움직이는 물체가 있을 시에 그 영상을 캡춰받아 자동으로 저장하도록 해보았다.
회사에서 퇴근하는 아무개님(초상권을 위해 모자이크 처리 ㅡㅡ;)문제는 영상에서 움직이는 물체가 있는지 없는지의 기준이 필요한데, 그 기준을 동적화소변이율을 사용하였다. 동적화소변이율은 영상을 구성하고 있는 전체 화소값(0~255)에서 변화된 화소값의 차이를 다시 백분율로 계산한 것이다. 이 동적화소변화율은 그냥 어찌 저찌 해서 직접 만들어낸 공식이므로 영상관련 책을 암만… 찾아봐도 않나온다. ㅡ_ㅡ; 그래서 자세한 수식을 제시하면…
동적화소변이율=((모든 바뀐 픽셀 차이값의 합)/(영상을 구성하는 픽셀수*255))*100
사용하고 있는 화상카메라를 기준으로 했을때 동적화소변이율이 0.5% 미만일 경우 화면상에 움직이는 물체가 없다고 판단했을때 정확히 일치했다. 그리고 0.5이상일때부터 영상에 움직이는 물체가 나타나기 시작했다가 1% 이상일때 움직이는 물체가 화면의 중앙에 위치한 상태였다. 동적화소변이율이 1%일때의 영상을 날짜와 시간을 파일명으로 해서 저장하도록 했다.
다음 단계는 움직이는 물체가 “누구(Who)”인지를 자동으로 판별해 내는 것..! 두둥~! 이를 위해 필요한 알고리즘은 무엇인가?
작년 8월쯤 (회사 입사때쯤이라 기억하고 있음)에 마소 잡지를 뒤적이던 중에 OpenCV라는 오픈소스의 Computer Vision 라이브러리를 알게 되었다. 이게 “물건”이구나라는 것을 느꼈고, 다운로드 받아 대충이라도 살펴 보았지만, 잠시 시간의 흐름과 함께 내 관심밖으로 밀려나갔다. 그러던중에… 요즘 오픈소스인 Ogre3D도 분석해보고 하던차에, OpenCV가 생각났다. GIS에서 위성영상이나 항공영상으로부터 데이터를 추출하는 등, 이미지 처리 분야는 매우 중요하고 앞으로 더욱 중요해질 것이다.
먼저 OpenCV가 뭐에 쓰는 물건인지는 알아서들 알아내시고…(뜨헉~ -_-;) 이번엔 OpenCV의 기능중 빙산의 일각인 화상카메라로부터 영상을 취득한 후에, 취득한 영상에서 움직이는 물체를 검출해보는 기능을 작성해 보았다. 매우 다양하고 효율적인 방법이 많이 있을지 모르겠지만, 당장 생각나는 방법은 현재 프레임 영상과 바로 이전 프레임 영상에 대한 같은 위치의 화소값을 비교해서 다르면 움직이는 물체에 대한 화소일 것이라는 아이디어로 접근해 보았다.
저거 내 왼손~
하지만 동일한 위치의 화소값이 두 프레임 이미지가 정확히 일치라는 비교는 무리가 있었다. 화상카메라에서 취득하는 영상은, 비록 움직이는 물체가 없을지라도 미세하게나마 연속된 프레임일지라도 다르게 취득되었다. 그래서 동일한 위치의 화소값의 차이가 어느 정도의 차이가 날때 움직이는 물체다.. 라는 기준으로 접근을 해 보았다. 결과는 처음치곤 만족할 만했다.
대략 설명해 본다면, 우선 OnFrame 함수는 OpenCV에서 화상카메라로부터 영상이 들어올때 실행되는 Callback 함수이다. 영상은 24비트로 들어오는데 속도와 계산을 간단하게 하기 위해서 8비트인 Gray로 변환하기 위해 cvGray를 사용하였다. 연속되는 프레임 영상의 같은 위치의 화소값을 비교하여 움직이는 물체에 대한 화소인지의 비교한 차이값으로 15를 사용하였음을 알 수 있다. 차이값이 15이상인 화소의 위치에 White Box를 그려주었다.
HRESULT __fastcall AnsiToUnicode(LPCSTR pszA, LPOLESTR* ppszW) {
ULONG cCharacters;
DWORD dwError;
// If input is null then just return the same.
if (NULL == pszA)
{
*ppszW = NULL;
return NOERROR;
}
// Determine number of wide characters to be allocated for the
// Unicode string.
cCharacters = strlen(pszA)+1;
// Use of the OLE allocator is required if the resultant Unicode
// string will be passed to another COM component and if that
// component will free it. Otherwise you can use your own allocator.
*ppszW = (LPOLESTR) CoTaskMemAlloc(cCharacters*2);
if (NULL == *ppszW)
return E_OUTOFMEMORY;
// Covert to Unicode.
if (0 == MultiByteToWideChar(CP_ACP, 0, pszA, cCharacters,
*ppszW, cCharacters))
{
dwError = GetLastError();
CoTaskMemFree(*ppszW);
*ppszW = NULL;
return HRESULT_FROM_WIN32(dwError);
}
return NOERROR;
}
SOAP, SOAP, SOAP, SOAP.. 지금 내 머리속엔 비누방울이 아른 아른~ 아침 출근할때 감기 바이러스가 몸에 침투를 했나부다. 몸에 열… 점심때 후식으로 먹은 빵이 불량식품이였나부다. 속이 부글부글.. 뼈를 싹인다는 Coco Cola로 일단 속을 달래는 중….
으.. 아까 콜라 사러 1층 편의점에 가려고 엘리베이터타려고 기다리는데 (사무실은 11층).. 이 놈의 건물의 엘리베이터의 움직이는 속도가 정말 장난이 아니다.. 가끔 엘리베이터를 기다리는 건지… 한없이 감감 무소식으로 오지 않는 지하철을 기다리는건지….. 성질 급한 사람은 가끔 화가 날 법도 하다.. 나같이 오늘처럼 말이다..
이 함수자를 사용하면, EventSrc 클래스의 Fire 함수가 아래처럼 무척 깔끔하게 작성됩니다.
void Fire(int a)
{
std::for_each(m_listObserver.begin(), m_listObserver.end(), Functor(a));
}
하지만 이런 방식은 Fire 함수를 깔끔하게 만들어준다는 사소한 장점은 있지만, 다른 어플리케이션에 적용할때 매번 함수자를 각각의 경우에 맞게 새롭게 코딩해줘야 하는 수고로움이 더욱 많습니다. 또한 여전이 Observed에서는 자신이 관리하고 있는 Observer 객체들이 호출해야할 함수가 무엇인지를 알 길이 없습니다.
하지만 여기서 곰곰이 생각해 보면 이 함수자를 Observed 클래스의 inner class로 정의해보는 것에 대한 아이디어가 떠오릅니다. 게다가 이 함수자 역시 template로 정의해서 Observed 클래스가 관리하고 있는 Observer 객체들이 호출해야할 함수를 타입으로 받아 버린다면 Observed가 알아야할 Observer의 정보를 모두 Observed에게 넘겨줄 수 있게되어, Fire의 책임을 Observed가 맡을 수 있게 됩니다. 그러면 더 이상 EventSrc는 필요치 않게 되고요. 이러한 아이디어에 착안해서 새롭게 구현된 Observed 클래스는 다음과 같습니다.
굵은 청색 폰트로 된 것이 수정되거나 새롭게 추가된 것입니다. Observed가 관리하고 있는 Observer의 호출함수의 반환값과 인자, 그리고 호출함수에 대한 타입을 template 인자인 T_result와 T_arg1으로 받고, 호출함수는 생성자에서 받아 맴버변수인 m_pMemFunc에 저장하도록 하였습니다.
이렇게 새롭게 구현된 Observed를 이용해서 Client 측에서 사용하는 코드는 다음과 같습니다.
int _tmain(int argc, _TCHAR* argv[])
{
Observed *pES =
new Observed(&Observable::OnEvent);
Observable_A *pOA = new Observable_A();
Observable_B *pOB = new Observable_B();
pES->RegisterObserver(pOA);
pES->RegisterObserver(pOB);
pES->Fire(99);
delete pOA;
delete pOB;
delete pES;
return 0;
}
이제 마지막으로 하나 더 짚고 정리를 하겠습니다. 여기서 한가지 큰 문제가 있는데 그것은 Observed가 관리하고 있는 Observer의 호출해야할 함수의 인자 개수에 관한 문제입니다. 지금가지의 경우는 단지 하나의 인자만을 받는 경우지만 두개 이상의 인자를 받는 경우에 대한 처리도 필요하지요. 하지만 이것 역시 그리 어렵지 않게 해결할 수 있습니다. Observed의 inner class인 함수자의 이름이 Firer1인 이유는 하나의 인자를 받는 함수자이기 때문에 Firer 뒤에 1을 붙인 것입니다. 그렇다면 이제 2개의 인자를 받는 Firer2 함수자를 정의하고 인자를 2개를 받는 Fire 맴버함수를 하나더 만들어 두면 됩니다.
이제 Client는 1단계처럼 관리하고자 하는 Observer에 대해 EventSrc와 Observable 클래스 모두를 정의할 필요가 없이, Observable 클래스 단하나만 신경 쓰면 되게 되었습니다. 그리고 Observer에 대한 모든 관리에 대한 책임을 오직 Observed가 맡게 되어 SRP를 지키게 되었습니다.