OpenCV를 정적 라이브러리(static library)로 사용한 c++ 배포용 프로그램 만들기

프로그래밍/opencv 2013. 3. 28. 22:03

opencv 를 사용하여 프로그램을 만들 때 보통은 동적 라이브러리(shared dll) 버전을 사용합니다. 그런데, 동적 라이브러리를 사용하면 자신이 만든 프로램을 배포할 때 opencv에 딸린 dll들도 같이 보내야 합니다. 또는 아무 컴퓨터에서나 실행파일 하나만 가지고 프로그램을 돌렸으면 좋겠는데, opencv dll들도 같이 들고 다녀야 하니 여간 불편합니다. 이럴 때, 사용할 수 있는 방법이 opencv 정적 라이브러리(static library) 버전을 사용하는 것입니다.


그런데, opencv에 기본적으로 포함된 static library를 사용할 경우에는 다음과 같은 문제점이 있습니다. ① win32 콘솔 어플리케이션으로 하면 문제가 없지만, mfc 어플리케이션을 생성하면 highgui와 라이브러리 충돌이 발생한다. ② 기본 static library에는 ipp나 tbb와 같은 고속/병렬처리 라이브러리가 포함되어 있지 않기 때문에 매우 느리다.


따라서, 본 글에서는 파일 하나만 있으면 언제 어디서나 실행 가능한 프로그램을 만들어 보려고 합니다. 제가 목표로 하는 최종 프로그램 파일 형태는 다음과 같습니다.

  • 실행파일(.exe)외에 어떤 부가 파일도 필요로 하지 않는다.
  • opencv가 설치되지 않은 컴퓨터에서도 실행되어야 한다.
  • visual studio가 설치되지 않은 컴퓨터에서도 실행되어야 한다.
  • win7 뿐만 아니라 vista, win xp 등에서도 모두 실행가능해야 한다.
  • opencv의 풀 기능을 사용하여 프로그램이 동작한다 (tbb, ipp 포함)


제가 개발한 프로그램들을 블로그에 올릴 때 사용할 목적으로 이러한 번거로운 변환작업을 시작하게 되었는데 정말 난관이 한두가지가 아닙니다. 정적 빌드를 왜 이렇게 어렵게 해 놓았는지 모르겠습니다. 일단 저의 작업 환경은 다음과 같습니다.


※ (2015.7.2) 외부 배포용이 아니라 연구개발용, 테스트용이라면 굳이 아래와 같은 복잡한 과정을 거칠 필요가 전혀 없습니다. 그냥 동적 라이브러리(dll)로 빌드하여 사용하시면 됩니다. 또한 opencv의 함수에 따라서 병렬처리를 지원하는 것도 있고 지원하지 않는 것도 있으며 병렬처리를 지원하는 함수의 경우에만 속도향상이 됩니다. 병렬처리를 지원하는 opencv 함수는 소스코드에 parallel_for_가 포함되어 있습니다.


OpenCV 기본 static library 그대로 이용하기


opencv를 다운받아 압축을 해제하면 미리 컴파일해 놓은 static library 버전이 기본적으로 포함되어 있습니다. 위치는 예를 들어, 저처럼 32비트OS visual c++ 9.0에서 사용할 경우 static library 위치는 opencv2.4.4\build\x86\vc9\staticlib 입니다. 여기에 있는 lib 파일들을 링크시키고 컴파일하면 opencv_XXX.dll들 없이도 실행 가능한 프로그램이 만들어집니다.  이 때, 주의할 사항들은 다음과 같습니다.

  • visual studio에서 프로젝트를 생성할 때 win32 어플리케이션으로 생성. mfc 어플리케이션을 생성하면 highgui와 라이브러리 충돌이 발생함 (제 경우는 그런데 다른 컴퓨터에서도 그런지는 잘 모르겠네요. 혹시 되는 분 있음 알려주세요)
  • 프로젝트 속성을 /MT(release), /MTd(debug)로 변경 (visual studio 기본 설정인 /MD 나 /MDd를 그대로 사용하면 엄청난 라이브러리 충돌 메시지를 보게 됩니다. 설정 위치는 프로젝트속성-C/C++-코드생성-런타임라이브러리입니다)
  • 사용할 opencv 모듈들(opencv_corexxx.lib, opencv_imgprocxxx.lib, ...) 뿐만 아니라 IlmImf.lib, libjasper.lib, libjpeg.lib, libpng.lib, libtiff.lib, zlib.lib도 모두 링크시켜야 함. opencv의 staticlib 폴더에 같이 들어있음. (debug 버전이면 뒤에 d 붙은 파일들 링크)
  • 추가적으로 vfw32.lib, comctl32.lib도 링크시켜야 함 (highgui 사용시 필요, debug/release 공통)


이와 같이 하면 opencv 없이도 exe 파일 하나만으로 실행 가능한 프로그램이 만들어집니다. 그런데, 문제는 CDialog 등 mfc 개발환경을 이용할 수 없으며 또한 tbb(threading building blocks)나 ipp (intel performance primitives)와 같은 고속 병렬처리 라이브러리를 이용할 수 없다는 점입니다. 특히 요즘 컴퓨터는 대부분 멀티코어이기 때문에 tbb를 사용했을 때와 안했을 때의 프로그램 속도 차이는 엄청납니다 (단, opencv 함수에 따라서 병렬처리를 지원하는 것도 있고 지원하지 않는 것도 있으며 병렬처리를 지원하는 함수의 경우에만 속도향상이 됩니다).


참고로 opencv 2.4.3 버전부터는 병렬처리를 위해 universal parallel_for_를 구현하여 tbb뿐만 아니라 OpenMP, Concurrency를 모두 지원합니다 (그 이전 버전에서는 tbb만 지원했었음). Concurrency는 visual studio 2010, 2012에 내장된 기능으로서 특별히 다른 처리를 안해도 visual studio가 알아서 병렬처리 코드로 컴파일해 준다고 합니다. 저는 아직 visual studio 2008이라서 이런 기능을 체험해 보지는 못했습니다. opencv에서 기본 제공되는 static library 버전을 그대로 사용해서 프로그램을 visual studio 2010 이상에서 컴파일했을 때, opencv 루틴들이 병렬로 처리될지는 아직 확인해 보지 못해서 모릅니다. 만일 그렇다면 굳이 아래와 같은 복잡한 일을 안해도 되겠네요.


본격적으로 설명을 하기 전에 앞서서 기본적으로 알아야 될 사항들이 몇 가지 있습니다.


/MT와 /MD 차이


컴파일 옵션중에 /MT(multi-threaded), /MD(multi-threaded dll)라는게 있습니다 (설정위치: 프로젝트속성-C/C++-코드생성-런타임라이브러리). 특히나, 정적 라이브러리를 사용할 경우에는 이것 때문에 무척 골치가 아픕니다. XXX is already defined in YYY 어쩌구 하는 라이브러리 충돌 컴파일 에러들은 모두 이놈들 때문에 발생합니다. 사용한 라이브러리가 하나는 /MT로 생성되었고 하나는 /MD로 생성되었다면 짤없이 라이브러리 충돌 에러 메시지를 보게 됩니다. http://msdn.microsoft.com/ko-kr/library/2kzt1wy3(v=vs.90).aspx에 보면 /MT, /MD가 설명되어 있지만 핵심 내용은 컴파일시 정적 버전의 C 런타임 라이브러리를 사용할 것이냐(/MT) 아니면 동적 버전을 사용할 것이냐(/MD)의 차이입니다.


/MT: 응용 프로그램에서 다중 스레드 정적 버전의 런타임 라이브러리를 사용하도록 지정합니다. 즉 응용 프로그램에서 입출력, 메모리 할당과 같은 C 런타임 라이브러리(CRT, C Run-Time library) 기능을 사용할 때 정적 링크 버전인 LIBCMT.lib를 사용하여 프로그램이 빌드됨.


/MD: 응용 프로그램에서 다중 스레드 DLL 전용 버전의 런타임 라이브러리를 사용하도록 지정합니다.  즉 응용 프로그램에서 C 런타임 라이브러리 기능을 사용할 때 이것의 동적 링크 버전인 MSVCRT.lib를 사용하여 프로그램이 빌드됨.


만일 사용하는 외부 라이브러리는 /MT로 빌드되었는데, 현재 응용 프로그램은 /MD로 빌드하면 동일한 기능에 대해 서로 다른 두 버전의 라이브러리가 동시에 사용되기 때문에 XXX is already defined in YYY 어쩌구 하는 무수한 충돌 메시지를 보게 됩니다. 외부에서 가져온 라이브러리라면 /MD, /MT를 맞추거나 /NODEFAULTLIB:library 외에는 별 방법이 없지만(이렇게 해도 해결 안되는 경우가 많음), 자신이 직접 정적 라이브러리를 만들 경우에는 라이브러리든, 응용프로그램이든 모조건 다 /MT 옵션으로 맞추면 됩니다(debug용은 /MTd).


target 윈도우 버전


만든 응용 프로그램이 win7, vista, win xp 관계없이 다 동작하기 위해서는 프로그램 컴파일시 WINVER과 _WIN32_WINNT를 win xp에 맞추어 주어야 합니다. Visual studio로 만든 프로젝트에 보면 targetver.h 파일이 자동 생성됩니다. 여기에서 타겟 윈도우 버전을 다음과 같이 win xp로 수정합니다.

#define WINVER 0x0501

#define _WIN32_WINNT 0x0501

만일 targetver.h 파일이 없을 경우에는 프로젝트속성에서 컴파일 옵션에 /D 매크로를 사용해서 두 상수를 추가로 정의해 주면 됩니다. 각 윈도우별 버전은 win 2000은 0x0500, win xp는 0x0501, windows server 2003은 0x0502, vista는 0x0600, win 7은 0x0601입니다.



그럼 이제 본격적으로 단독 실행 가능한 프로그램을 위한 opencv 정적 라이브러리 만드는 방법을 차례차례 설명하겠습니다.


1. tbb를 static library로 변환하기


opencv는 병렬처리 라이브러리인 tbb를 지원합니다. 그래서, tbb를 포함해서 opencv를 빌드(build)하면 멀티코어 컴퓨터에서 고속으로 영상처리 루틴의 처리가 가능해집니다. opencv 배포판에 있는 미리 빌드된 파일들(동적/정적 모두 해당됨)에는 tbb가 포함되어 있지 않기 때문에 속도가 느립니다. tbb를 사용할 경우 약 4~10배까지 opencv 속도가 향상됩니다. 그런데, tbb를 사용하기 위해서는 자신이 직접 opencv 소스코드를 다시 컴파일해야 합니다.


tbb는 공개(free) 라이브러리로 http://threadingbuildingblocks.org/에서 소스코드 및 바이너리를 다운받을 수 있습니다. 그런데, 문제는 tbb가 동적 라이브러리(shared dll) 형태로만 배포된다는 것입니다. tbb 홈페이지에 보면 static 버전을 사용하면 원치않는 여러가지 문제가 발생할 수 있기 때문에 자기들은 dll 버전만을 지원한다고 명시되어 있습니다. 따라서, tbb를 사용하면 tbb.dll 파일도 항상 같이 가지고 다녀야 한다는 말이 됩니다.


하지만, 제가 원하는 것은 어떤 부가 파일도 필요없이 exe 파일 하나만으로 실행되는 프로그램이기 때문에 tbb도 직접 static library 버전으로 만들기로 했습니다.


tbb를 static library로 빌드하기 위해 필요한 사항들은 다음과 같습니다.

  1. tbb 홈페이지에서 tbb 소스코드를 다운로드 받는다.
  2. visual studio에서 win32 static library 프로젝트를 하나 생성한다.
  3. tbb가 설치된 경로를 TBB라고 했을 때, dll버전을 컴파일하기 위한 프로젝트 파일인 TBB\build\vsproject\tbb.vcproj을 참조하여 동일하게 소소코드 파일들(cpp, h)을 프로젝트에 포함시킨다. tbb.vcproj를 열면 tbb, tbbmalloc, tbbmalloc_proxy 이렇게 3개의 프로젝트가 한 workspace에 같이 들어있는데, 첫번째 tbb만 참조하면 된다. 리소스 파일은 포함시킬 필요 없음.
  4. 프로젝트 설정에서 코드생성 옵션을 릴리즈 버전은 /MT로, 디버그 버전은 /MTd로 설정한다(설정위치: 프로젝트설정-C/C++-코드생성-런타임라이브러리).
  5. 프로젝트 설정에서 preprocessor definitions에 __TBB_BUILD, USE_WINTHREAD, __TBB_SOURCE_DIRECTLY_INCLUDED 이렇게 3개를 추가해 준다. (설정위치: 프로젝트속성-C/C++-전처리-전처리정의). 컴파일 옵션에 /D 매크로를 사용해서 명령행으로 추가해도 됨.
  6. 프로젝트 설정에서 컴파일 옵션에 WINVER과 _WIN32_WINNT을 모두 0x0501로 정의해 줌. 0x0501은 windows xp에 해당하는 버전번호로서 이걸 해 주지 않으면 tbb가 vista 이상에서만 동작하게 됨. xp에서도 실행되도록 하기 위해서는 target window version을 이렇게 명시해 주어야 함. 컴파일 전처리 옵션에 추가하기 위해서는 /D 매크로를 사용하여 명령행 옵션란에 /DWINVER=0x0501와 /D_WIN32_WINNT=0x0501를 추가해 줌 (설정위치: 프로젝트속성-C/C++-명령행라인-추가옵션)
  7. tbb_misc.cpp 파일에서 __TBB_machine_store8_slow 함수 부분을 주석처리한다. 요놈 때문에 아무리 해도 컴파일이 안되었었는데 호출되는 곳을 보니 Linux 머신에서 사용할 때 뿐이라서 과감히 삭제했습니다. 실제 프로그램을 돌려보니 삭제해도 이상없이 잘 돌아갑니다.


2. ipp 사용하기


ipp(intel performance primitives)는 인텔에서 제공하는 라이브러리로서 인텔 cpu 칩에 최적화된 연산 루틴을 제공한다고 합니다. opencv에서 ipp 사용을 지원하고 있는데 ipp를 사용하면 tbb만큼은 아니지만 opencv 루틴의 처리 속도가 어느 정도는 향상됩니다. ipp를 사용하기 위해서는 http://software.intel.com/intel-ipp에서 30일 평가판을 무료로 다운로드 받습니다. ipp에서는 tbb와 달리 static library 버전도 같이 제공됩니다. 평가판이긴 하지만 우리가 필요로 하는 것은 몇몇 static lib 파일들과 include 헤더 파일들이기 때문에 이 파일들만 따로 복사해 놓고 무한히 사용할 수 있습니다. opencv에서 필요로 하는 ipp 파일들은 ippcc_l.lib, ippcore_l.lib, ippcv_l.lib, ippi_l.lib, ipps_l.lib, ippvm_l.lib 이렇게 6개 뿐입니다. 정적 라이브러리 버전이기 때문에 dll 파일도 필요없으니 ipp 설치 폴더의 include 폴더에 있는 헤더 파일들과 이 6개 파일만 있으면 OK 입니다.


☞ (2015.6.15) 그런데 요즘 나오는 최신 ipp 버전에서는 위 static lib들이 사라졌다고 하는 얘기가 있기 때문에 위 6개 lib 파일들을 구하기 위해서는 예전버전(7.XX 이하)을 구해야 할 것 같습니다. 정확히 어떤 버전부터 사라졌는지는 잘 모르겠습니다만 7.0까지는 있었던 것 같습니다.


3. OpenCV 정적 라이브러리(static library) 직접 빌드하기


opencv를 소스코드로부터 직접 빌드하기 위해서는 cmake가 필요합니다. 정적 라이브러리 뿐만 아니라 기본 제공되는 동적 라이브러리 dll 버전도 tbb나 ipp를 포함하고 있지 않기 때문에 어차피 한번은 직접 빌드를 해 주는게 좋습니다.


cmake는 http://www.cmake.org/에서 다운로드 받을 수 있는 free 소프트웨어입니다. cmake 사용법은 다루지 않겠습니다만, opencv를 static library로 빌드하기 위해서 저는 다음과 같이 cmake를 설정했습니다. 중요한 사항은 BUILD_SHARED_LIBS는 체크해제하고(정적 라이브러리를 빌드할 것이므로) BUILD_WITH_STATIC_CRT도 체크해제(아니면 엄청난 라이브러리 충돌이 발생함)합니다. 그리고 BUILD_PACKAGE도 체크해제합니다(아니면 컴파일하다가 날 샙니다). WITH_IPP와 WITH_TBB는 사용할 것이기 때문에 체크해 줍니다. 그러면 ipp, tbb에 대한 include, lib 경로를 물어보는데 적절히 세팅해 주면 됩니다. tbb의 경우에는 앞에서 static 버전으로 컨버팅한 lib 파일 경로를 입력해 줍니다. png, tiff, jpeg, zlib 등도 체크해 줍니다.






이렇게 해서 cmake로 생성한 프로젝트에서 build all을 하면 정적 라이브러리 버전의 opencv가 생성되는데, 몇 가지 세팅해 주어야 할 게 있습니다.

  • cmake로부터 기본적으로 생성된 opencv 프로젝트는 컴파일러 코드생성 옵션이 모두 /MD, /MDd로 되어 있는데 저는 opencv_core, opencv_highgui, ... 등등 opencv 모듈들의 옵션을 모두 일일히 /MT, /MTd로 바꾼 후에 컴파일했습니다.
  • 프로젝트 설정에서 컴파일 옵션에 /D 매크로를 이용해서 WINVER과 _WIN32_WINNT을 모두 0x0501로 정의해 줍니다. xp에서도 동작하게 하고 싶다면 모든 opencv 모듈을 일일히 설정해 줍니다.
  • highgui 소스파일 중 window_w32.cpp파일이 있는데 안에 보면 #include <MultiMon.h>하는 코드가 있습니다. 이놈을 주석처리합니다. 처음엔 그대로 빌드했는데 계속 라이브러리 충돌이 나서 이놈을 제거하니 잘 되네요. 저만의 문제인지 일반적인 현상인지는 잘 모르겠군요. 주석처리 하지 않아도 잘 동작한다면 굳이 주석처리할 필요 없을 것입니다.


☞ CMake를 사용한 OpenCV 빌드 방법에 대한 글을 최근 올렸습니다: OpenCV 설치 및 CMake GPU+TBB+IPP 빌드(컴파일)하기



4. 독립 실행되는 응용 프로그램 생성하기


이제 마지막으로 지금까지 생성한 static library들을 가지고 독립 실행이 가능한 opencv 응용 프로그램을 만들면 됩니다. 이 때, 주의 사항은 다음과 같습니다.

  • 만일 콘솔 어플리케이션이라면 프로젝트 속성에서 standard windows library를 사용하고, mfc 어플리케이션이라면 static library로 mfc를 사용하도록 설정한다.
  • 프로젝트 속성에서 코드생성 옵션을 릴리즈 버전은 /MT로, 디버그 버전은 /MTd로 설정한다(설정위치: 프로젝트설정-C/C++-코드생성-런타임라이브러리). 안해주면 엄청난 라이브러리 충돌이 발생합니다.
  • opencv static libary 모듈들 뿐만 아니라,  IlmImf.lib, libjasper.lib, libjpeg.lib, libpng.lib, libtiff.lib, zlib.lib, vfw32.lib, comctl32.lib, 만들어 두었던 tbb.lib, 그리고 ipp 라이브러리들인 ippcc_l.lib, ippcore_l.lib, ippcv_l.lib, ippi_l.lib, ipps_l.lib, ippvm_l.lib 들을 전부다 링크시킨다. 예를 들어 use_opencv_static.h라는 이름으로 헤더 파일을 별도로 하나 만들어서 #pragma comment(lib,"opencv_core244d.lib"), #pragma comment(lib,"vfw32.lib") 등과 같이 필요한 정적 라이브러리 파일들을 일괄적으로 링크시키면 편리합니다.


5. 테스트 프로그램


이상의 내용을 가지고 아래와 같은 샘플 프로그램을 하나 만들어 보았습니다. opencv에 있는 샘플 프로그램중의 하나인 facedetect.c 예제를 그대로 포팅한 프로그램입니다. face DB 파일과 실행파일만 달랑 하나 있는데 opencv나 visual c++ 없이도 여러분 컴퓨터에서 실행되는지 확인해 보시기 바랍니다.

StaticTestFaceDetect.zip






☞ 쓰고 보니 글이 길어졌네요. 그 만큼 제가 삽질을 많이 했다는 ... ^^ 어쨌든 지금은 모든 문제가 클리어되어서 홀가분합니다. 제 글이 opencv를 직접 build하려는 이들, opencv에서 tbb나 ipp를 사용하고자 하는 이들, opencv를 정적 라이브러리로 사용하고자 하는 이들, 자신이 만든 프로그램을 독립 실행 가능한 버전으로 만들고자 하는 이들에게 도움이 되길 바랍니다. 혹 내용중 잘못된 부분이나 관련된  경험, 코멘트 등에 대해서는 댓글로 남겨 주시기 바랍니다.

by 다크 프로그래머