OpenCV-Python 바인딩은 어떻게 동작할까? (How OpenCV-Python Bindings Works?)
https://docs.opencv.org/3.2.0/da/d49/tutorial_py_bindings_basics.html의 내용을 번역
첫번째로, modules/python/CmakeFiles.txt는 CMake 스크립트로서 python으로 확장되는 모듈들을 #체크한다. CMake 스크립트는 자동적으로 확장되는 모든 모듈들을 체크하고 모듈의 헤더파일들을 잡아둔다(기억한다). 이 헤더파일들은 특정 모듈에 대한 모든 클래스, 함수, 상수 등의 목록을 포함한다.
두번째로, 이 헤더파일들은 modules/python/src2/gen2.py 스크립트로 전달된다. 이 스크립트는 python bindings generator 스크립트이다. 이 스크립트는 다른 modules/python/src2/hdr_parser.py 스크립트를 호출한다. 이 스크립트는 header parser 스크립트이다. 이 header parser는 헤더파일을 작은 python 리스트들로 나눈다. 나눠진 리스트들은 특정한 함수, 클래스 등에 대한 상세한 내요을 담고있다. 예를 들어, 한 함수는 함수의 이름, 반환 타입, 입력 변수들, 변수 타입 등의 정보를 포함하는 리스트로 분석(#파싱)된다. 마지막 리스트는 그 헤더파일 안의 모든 함수, 구조체, 클래스 등의 정보를 담는다.하지만 header parser는 헤더파일 안에 있는 모든 함수/클래스를 분석하지 않는다. 개발자가 어느 함수들이 python으로 확장되어야 하는지 특정해야한다. 그러기 위해, header parser가 확장되어야 하는 함수인지 식별하는 매크로가 선언의 앞부분에 추가되어 있다. 이 매크로들은 특정한 함수를 프로그래밍하는 개발자가 추가해야한다. 즉, 개발자는 어느 함수들이 python으로 확장되어야 하고 아닌지를 결정해야한다. 이러한 매크로의 상세한 설명은 다음 장에서 설명한다.
그래서, header parser는 함수들이 분석된 하나의 큰 리스트를 반환한다. generator 스크립트(gen2.py)는 header parser로부터 분석된 모든 함수/클래스/열거형/구조체들의 wrapper 함수를 생성한다(이 헤더파일들은 컴파일 중에 build/modules/python/ 폴더에서 pyopencv_generated_*.h로 생성된다). 그러나 Mat, Vec4i, Size와 같은 OpenCV의 기본적인 데이터 타입은 수동으로 확장되어져야 한다. 예를 들어, Mat 타입은 Numpy의 array 타입으로 확장돼야하고, Size 타입은 2개의 int의 tuple 타입으로 확장돼야 한다. 비슷하게 복잡한 구조체/클래스/함수들도 수동으로 확장돼야 한다. 이러한 모든 수동의 wrapper 함수들은 modules/python/src2/cv2.cpp에 있다.
큰 클래스 역시 CV_EXPORTS_W가 사용된다. 클래스의 멤버함수를 확장하기위해, CV_WRAP이 사용된다. 비슷하게, CV_PROP은 클래스 필드를 위해 사용된다.
오버로딩된 함수들은 CV_EXPORTS_AS 매크로를 사용해 확장될 수 있다. 그러나 각 함수가 python에서 그 함수의 이름으로 호출되기위해 새로운 이름을 넘겨줘야 한다. 아래의 integral 함수의 경우를 보자. 현재 3개의 함수가 사용가능하다. 각각의 함수는 python에서는 다른 접미를 붙여 이름을 붙인다. 비슷하게 CV_WRAP_AS는 오버로딩된 멤버함수에 사용하여 확장할 수 있다.
작은 클래스/구조체들은 CV_EXPORTS_W_SIMPLE을 사용해 확장할 수 있다. 이 구조체들은 값으로 C++ 함수에 전달된다. 예를들, KeyPoint, Match 등이 있따. 작은 클래스/구조체들의 멤버함수들은 CV_Wrap을 이용해 확장되며 필드는 CV_PROP_RW를 이용해 확장된다.
목표
내용:
- OpenCV-Python 바인딩은 어떻게 생성될까? (How OpenCV-Python bindings are generated?)
- OpenCV의 새로운 모듈을 어떻게 Python으로 확장하나? (How to extend new OpenCV modules to Python?)
How OpenCV-Python bindings are generated?
OpenCV에서는 모든 알고리즘이 C++로 구현되어 있다. 하지만 이러한 알고리즘들은 Python, Java 등의 다른 언어로 사용될 수 있다. Binding generator들이 바로 이러한 작업을 가능하게 한다. 이러한 generator들은 C++와 Python 사이의 다리 역할을 하며 사용자가 Python으로 부터 C++ 함수를 호출 할 수 있게 해준다. 어떠한 작업이 뒤에서 돌아가는지 완벽히 알기 위해서는 Python과 C에 대한 깊은 지식이 필요하다. C++ 함수를 Python으로 확장하는 간단한 예제는 Python 공식 문서에서 확인 할 수 있다[1]. OpenCV의 모든 함수들을 Wrapper 함수를 작성하여 Python으로 확장하는 것은 시간을 많이 잡아먹는 작업이다. 그래서 OpenCV는 좀 더 지능적인 방법을 사용한다. OpenCV는 modules/python/src2에 위치하고 있는 python 스크립트를 이용해 C++ 헤더로 부터 자동적으로 Wrapper 함수를 생성한다. python 스크립트가 어떠한 작업을 하는지 알아보자.
첫번째로, modules/python/CmakeFiles.txt는 CMake 스크립트로서 python으로 확장되는 모듈들을 #체크한다. CMake 스크립트는 자동적으로 확장되는 모든 모듈들을 체크하고 모듈의 헤더파일들을 잡아둔다(기억한다). 이 헤더파일들은 특정 모듈에 대한 모든 클래스, 함수, 상수 등의 목록을 포함한다.
두번째로, 이 헤더파일들은 modules/python/src2/gen2.py 스크립트로 전달된다. 이 스크립트는 python bindings generator 스크립트이다. 이 스크립트는 다른 modules/python/src2/hdr_parser.py 스크립트를 호출한다. 이 스크립트는 header parser 스크립트이다. 이 header parser는 헤더파일을 작은 python 리스트들로 나눈다. 나눠진 리스트들은 특정한 함수, 클래스 등에 대한 상세한 내요을 담고있다. 예를 들어, 한 함수는 함수의 이름, 반환 타입, 입력 변수들, 변수 타입 등의 정보를 포함하는 리스트로 분석(#파싱)된다. 마지막 리스트는 그 헤더파일 안의 모든 함수, 구조체, 클래스 등의 정보를 담는다.하지만 header parser는 헤더파일 안에 있는 모든 함수/클래스를 분석하지 않는다. 개발자가 어느 함수들이 python으로 확장되어야 하는지 특정해야한다. 그러기 위해, header parser가 확장되어야 하는 함수인지 식별하는 매크로가 선언의 앞부분에 추가되어 있다. 이 매크로들은 특정한 함수를 프로그래밍하는 개발자가 추가해야한다. 즉, 개발자는 어느 함수들이 python으로 확장되어야 하고 아닌지를 결정해야한다. 이러한 매크로의 상세한 설명은 다음 장에서 설명한다.
그래서, header parser는 함수들이 분석된 하나의 큰 리스트를 반환한다. generator 스크립트(gen2.py)는 header parser로부터 분석된 모든 함수/클래스/열거형/구조체들의 wrapper 함수를 생성한다(이 헤더파일들은 컴파일 중에 build/modules/python/ 폴더에서 pyopencv_generated_*.h로 생성된다). 그러나 Mat, Vec4i, Size와 같은 OpenCV의 기본적인 데이터 타입은 수동으로 확장되어져야 한다. 예를 들어, Mat 타입은 Numpy의 array 타입으로 확장돼야하고, Size 타입은 2개의 int의 tuple 타입으로 확장돼야 한다. 비슷하게 복잡한 구조체/클래스/함수들도 수동으로 확장돼야 한다. 이러한 모든 수동의 wrapper 함수들은 modules/python/src2/cv2.cpp에 있다.
그럼 남은 마지막 작업은 이 wrapper 파일들을 컴파일하고 cv2 module을 만드는 것이다. res=equalizaHist(img1,img2) 함수를 python에서 호출할 때, 두 개의 numpy array가 입력으로 들어갈 것이고 하나의 numpy array가 출력으로 예상된다. 이 numpy array들은 cv::Mat으로 변환될 것이고,C++의 equalizeHist() 함수가 호출될 것이다. 마지막으로 res는 다시 numpy array로 변환될 것이다. 한마디로, 거의 모든 연산들은 C++에서 이루어진다. 따라서 연산의 속도 역시 C++과 거의 같다.
이 것이 "How OpenCV-Python bindings are generated?"의 기초적인 설명이다.How to extend new modules to Python?
Header parser는 함수의 선언 아에 추가되는 wrapper 매크로를 이용하여 헤더파일들을 분석한다. 열거형 상수는 wrapper 매크로가 필요없고 자동적으로 wrapping된다. 하지만 함수, 클래스들은 wrapper 매크로가 필요하다.
함수들은 CV_EXPORTS_W 매크로를 이용하여 확장된다. 아래의 예시를 보자.
Header parser는 InputArray, OutputArray와 같은 키워드로 부터 입력변수와 출력변수를 알수있다. 그러나 가끔씩, 입력변수와 출력변수를 하드코딩해야 할 수도 있다. 이를 위해, CV_OUT, CV_IN_OUT과 같은 매크로가 사용된다.
함수들은 CV_EXPORTS_W 매크로를 이용하여 확장된다. 아래의 예시를 보자.
CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst );
Header parser는 InputArray, OutputArray와 같은 키워드로 부터 입력변수와 출력변수를 알수있다. 그러나 가끔씩, 입력변수와 출력변수를 하드코딩해야 할 수도 있다. 이를 위해, CV_OUT, CV_IN_OUT과 같은 매크로가 사용된다.
CV_EXPORTS_W void minEnclosingCircle( InputArray points, CV_OUT Point2f& center, CV_OUT float& radius );
큰 클래스 역시 CV_EXPORTS_W가 사용된다. 클래스의 멤버함수를 확장하기위해, CV_WRAP이 사용된다. 비슷하게, CV_PROP은 클래스 필드를 위해 사용된다.
class CV_EXPORTS_W CLAHE : public Algorithm { public: CV_WRAP virtual void apply(InputArray src, OutputArray dst) = 0; CV_WRAP virtual void setClipLimit(double clipLimit) = 0; CV_WRAP virtual double getClipLimit() const = 0; }
오버로딩된 함수들은 CV_EXPORTS_AS 매크로를 사용해 확장될 수 있다. 그러나 각 함수가 python에서 그 함수의 이름으로 호출되기위해 새로운 이름을 넘겨줘야 한다. 아래의 integral 함수의 경우를 보자. 현재 3개의 함수가 사용가능하다. 각각의 함수는 python에서는 다른 접미를 붙여 이름을 붙인다. 비슷하게 CV_WRAP_AS는 오버로딩된 멤버함수에 사용하여 확장할 수 있다.
CV_EXPORTS_W void integral( InputArray src, OutputArray sum, int sdepth ); CV_EXPORTS_AS(integral2) void integral( InputArray src,OutputArray sum,
OutputArray sqsum, int sdepth = -1, int sqdepth = -1 ); CV_EXPORTS_AS(integral3) void integral( InputArray src, OutputArray sum,
OutputArray sqsum, OutputArray tilted,int sdepth = -1, int sqdepth = -1 );
작은 클래스/구조체들은 CV_EXPORTS_W_SIMPLE을 사용해 확장할 수 있다. 이 구조체들은 값으로 C++ 함수에 전달된다. 예를들, KeyPoint, Match 등이 있따. 작은 클래스/구조체들의 멤버함수들은 CV_Wrap을 이용해 확장되며 필드는 CV_PROP_RW를 이용해 확장된다.
class CV_EXPORTS_W_SIMPLE DMatch { public: CV_WRAP DMatch(); CV_WRAP DMatch(int _queryIdx, int _trainIdx, float _distance); CV_WRAP DMatch(int _queryIdx, int _trainIdx, int _imgIdx,
float _distance); CV_PROP_RW int queryIdx; // query descriptor index CV_PROP_RW int trainIdx; // train descriptor index CV_PROP_RW int imgIdx; // train image index CV_PROP_RW float distance; };
몇몇 다른 작은 클래스/구조체들은 CV_EXPORTS_W_MAP을 이용해 확장될 수 있다. CV_EXPORTS_W_MAP은 작은 클래스/구조체를 python native dictionary로 내보낸다.
Moments()는 그 예시이다.
Moments()는 그 예시이다.
class CV_EXPORTS_W_MAP Moments { public: CV_PROP_RW double m00, m10, m01, m20, m11, m02, m30, m21, m12, m03; CV_PROP_RW double mu20, mu11, mu02, mu30, mu21, mu12, mu03; CV_PROP_RW double nu20, nu11, nu02, nu30, nu21, nu12, nu03; };
OpenCV에서는 이러한 주요 확장 매크로들을 사용할 수 있다. 일반적으로, 개발자는 적절한 매크로를 적절한 위치에 놓아야 한다. 나머지는 generator 스크립트가 해결해준다. 가끔씩, generator 스크립트가 wrapper를 생성하지 못하는 예외적인 경우가 생길지도 모른다. 그 함수들은 수동으로 pyopencv_*.hpp를 작성하 다루어야며 이 파일을 제작한 모듈의 misc/python 하위폴더에 두어야 한다. 그러나 대부분의 경우, OpenCV 코딩 가이드라인에 따라 작성한 코드는 자동으로 generator 스크립트가 wrapper를 만든다.
댓글
댓글 쓰기