본문 바로가기

Lazy Coder

윈도우 파이썬에서 C코드 사용하기

  POSIT 알고리즘을 파이썬에서 사용할 일이 생겼습니다.

  파이썬의 OpenCV에서는 POSIT함수가 SolvePnP 함수로 대체되었습니다만, 예전에 C로 작성했던 코드를 직접 사용해야하는 상황이라 C코드를 파이썬에서 불러와서 사용하는 방법을 찾아보았습니다. 빠르게 사용하기 위해 웹에서 한글문서를 검색해봤지만 대부분 파이썬 메뉴얼에서 제공하는 기본 예제를 한글로 옮겨놓은 것들 뿐이었습니다. 리눅스용 예제가 많았으며, 윈도우즈 환경에서 파이썬에서 C 모듈로 Numpy 매트릭스를 넘기고 float 배열을 결과로 출력 받는 등의 원하는 내용이 나와있는 한글 문서를 찾을 수 없어 결국엔 pthon.org와 scipy-lectures의 공식문서를 참고해서 프로그램을 작성했습니다.

 

위 과정에서 습득한 방법을 포스팅합니다.

 

파이썬과 POSIT.cpp를 연결해주기 위한 POSITmodule.cpp와, C코드를 컴파일하여 파이썬에 설치해주는 setup.py를 작성합니다. C코드를 DLL모듈로 작성해서 사용하는 방법도 있지만, 여기서는 C코드를 컴파일하여 파이썬에 설치하는 방법을 사용합니다. 이 방법이 더 간단해보이더군요.

 

우선 전체 코드부터 먼저 표시하고 아래에서 설명을 이어가겠습니다.

 

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//POSITmodule.cpp
//https://docs.python.org/2/extending/extending.html
//http://www.scipy-lectures.org/advanced/interfacing_with_c/interfacing_with_c.html#numpy-support
 
#include 
#include "Python.h"
#include "numpy/arrayobject.h"
#include <math.h>
#include "POSIT.h"
 
// POSIT
#define FOCAL_LENGTH 1000
POSITObject posit_obj;
float points3d[POSIT_NUM_VERTICES*3];        // 12 points
float img_pts[POSIT_NUM_VERTICES*2];            // 12 points
int nPoints;
float m_rotation_matrix[9];
float m_translation_vector[3];
 
static PyObject *initPOSITpoints(PyObject *self, PyObject *args)
{
    PyArrayObject *in_array;
    NpyIter *in_iter;
    NpyIter_IterNextFunc *in_iternext;
    double ** in_dataptr;
    int cnt;
    int i;
    FILE *f;
 
    //  parse single numpy array argument
    if (!PyArg_ParseTuple(args, "O!"&PyArray_Type, &in_array))
        return NULL;
 
    //  create the iterator
    in_iter = NpyIter_New(in_array, NPY_ITER_READONLY, NPY_KEEPORDER,
        NPY_NO_CASTING, NULL);
    if (in_iter == NULL)
        goto failInitPOSIT;
 
    // set iterator
    in_iternext = NpyIter_GetIterNext(in_iter, NULL);
    if (in_iternext == NULL) {
        NpyIter_Deallocate(in_iter);
        goto failInitPOSIT;
    }
    in_dataptr = (double **)NpyIter_GetDataPtrArray(in_iter);
    
    // iterate over the arrays
    cnt = 0;
    do {
        points3d[cnt++= (float)(**in_dataptr);
        
    } while (in_iternext(in_iter));
    nPoints = cnt / 3;
 
    //  clean up
    NpyIter_Deallocate(in_iter);
 
    // init POSITOBJect in "POSIT.h"
    POSIT_InitPOSITObject(points3d, &posit_obj);
 
    //return the result
    Py_INCREF(Py_None);
    return Py_None;
 
failInitPOSIT:
    return NULL;
}
 
static PyObject *computePOSIT(PyObject *self, PyObject *args)
{
    PyArrayObject *in_array;
    NpyIter *in_iter;
    NpyIter_IterNextFunc *in_iternext;
    double ** in_dataptr;
    int cnt;
    float pitch;
    float yaw;
    float roll;
    int i;
    FILE *f;
 
    //  parse single numpy array argument
    if (!PyArg_ParseTuple(args, "O!"&PyArray_Type, &in_array))
        return NULL;
    //  create the iterator
    in_iter = NpyIter_New(in_array, NPY_ITER_READONLY, NPY_KEEPORDER,
        NPY_NO_CASTING, NULL);
    if (in_iter == NULL)
        goto failComputePOSIT;
 
    // set iterator
    in_iternext = NpyIter_GetIterNext(in_iter, NULL);
    if (in_iternext == NULL) {
        NpyIter_Deallocate(in_iter);
        goto failComputePOSIT;
    }
    in_dataptr = (double **)NpyIter_GetDataPtrArray(in_iter);
 
    // iterate over the arrays
    cnt = 0;
    do {
        img_pts[cnt++= (float)(**in_dataptr);
 
    } while (in_iternext(in_iter));
 
    //  clean up
    NpyIter_Deallocate(in_iter);
 
    if (cnt / 2 != nPoints)
        goto failComputePOSIT;
 
 
    POSIT_ComputePose(&posit_obj, img_pts, FOCAL_LENGTH, EPS | ITER, 
                        1.0e-4f, 100, m_rotation_matrix, m_translation_vector);
    get_pose(&pitch, &yaw, &roll);
 
    return Py_BuildValue("(fff)", pitch, yaw, roll);
 
failComputePOSIT:
    return NULL;
 
}
 
 
static struct PyMethodDef methods1[] =
{
    { "InitPOSITpoints", initPOSITpoints, METH_VARARGS, "init POSIT" },
    { NULLNULL0NULL }
};
 
static struct PyMethodDef methods2[] =
{
    { "computePOSIT", computePOSIT, METH_VARARGS, "compute POSIT" },
    { NULLNULL0NULL }
};
 
 
PyMODINIT_FUNC initPOSITmodule()
{
    (void)Py_InitModule("POSITmodule", methods1);
    (void)Py_InitModule("POSITmodule", methods2);
    
    // IMPORTANT: this must be called for numpy support
    import_array();
 
 
}
 
cs

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
#setup.py
from distutils.core import setup, Extension
import numpy
 
setup(name = "POSITmodule",
        version = "1.0",
        description = "pts to posit angle",
        author = "mutefreeze",
        author_email = "mutefreeze@email_example.com",
        url = "http://mutefreeze.tistory.com",
        ext_modules = [Extension("POSITmodule", ["POSITmodule.cpp""posit.cpp"], 
                                                include_dirs=[numpy.get_include()])]
        )
cs

 

  우선, 파이썬에서 사용하는 C모듈을 작성하기 위한 Phthon.h와 Numpy를 함수 인자로 받기 위한 numpy/arrayobject.h를 include 합니다. 여기서 주의할 점은, 그냥 arrayobject.h만 인클루드 해서는 컴파일시 헤더파일 경로를 찾을 수 없다는 에러가 출력됩니다. POSITmodule.cpp 145줄의 import_array();와 setup.py 11줄의 unclude_dirs=[numpy.get_include()]를 추가해주면 헤더파일 경로가 설정됩니다.

 

20: static PyObject *initPOSITpoints(PyObject *self, PyObject *args), 입력: Numpy, 출력: 없음

70: static PyObject *computePOSIT(PyObject *self, PyObject *args), 입력: Numpy, 출력: float 형 3개

  이 두 함수는 파이썬에서 불러와서 사용할 함수들입니다. PyObject를 함수의 입출력 인자로 사용해야합니다.

 

126: static struct PyMethodDef methods1[] ,

132: static struct PyMethodDef methods2[]

  이 두 구조체는 C함수를 파이썬 함수로 등록하기 위한 구조체입니다. 첫 번째와 두 번째 인스턴스로 파이썬에서 사용할 함수 이름, C에서 구현된 위의 함수이름을 넣어주면 됩니다.

 

145: PyMODINIT_FUNC initPOSITmodule()

  이 함수에서는 파이썬 모듈에 위의 함수를 등록해주는 함수입니다. 파이썬에서 사용할 모듈 이름을 "POSITmodule"로 두었습니다. setup.py의 11줄의 "POSITmodule"과 일치해야 등록이 됩니다.

 

 

20: static PyObject *initPOSITpoints(PyObject *self, PyObject *args)

파이썬과 C코드의 interface 이며, 파이썬에서 실제로 사용할 함수입니다.

 

31:  if (!PyArg_ParseTuple(args, "O!"&PyArray_Type, &in_array))

  여기에서는 파이썬에서 넘어온 인자를 파싱합니다.  "O!"는 함수 인자의 타입을 지정합니다. "s"는 문자열, "i"는 int형, "is"는 int형 하나, 문자열 하나의 두 개 인자를 받습니다.

https://docs.python.org/2/c-api/arg.html#c.PyArg_ParseTuple 에서 각 인자 타입별 포멧을 참고할 수 있으며, 본 예제에서는 numpy를 인자로 받기 때문에 오브젝트 타입인 "O!"를 사용합니다.

 

34~46: numpy에서 데이터를 읽어오기 위한 iterator를 만들어줍니다.

48:53: numpy에서 데이터를 float형으로 읽어옵니다. double형으로 지정하면 바로 읽어오지만만, float형으로 읽어오면 double형에서 float형으로 자동으로 형 변환이 됩니다.

 

57: numpy를 읽기 위해 사용한 iterator를 해제합니다.

60: 다른 C 파일인 POSIT.cpp에 있는 함수를 불러와서 사용합니다.

 

다른 나머지 하나의 함수도 출력에 Py_BuildValue 함수를 사용한 것을 제외하고는 비슷한 구조를 가지기 때문에 설명을 생략합니다.

 

 

 

이제 작성한 코드를 컴파일할 차례입니다.

 

setup.py의 11번 째줄이 핵심입니다. 파이썬에서 사용할 모듈명과 컴파일할 C코드 파일들을 지정해줍니다. include_dirs는 Numpy를 사용하지 않는다면 적어줄 필요가 없습니다.

 

cmd에서 해당 코드가 있는 폴더로 이동 후

 

python setup.py install

 

을 실행시킵니다. 적절한 c 컴파일러가 등록되어 있다면 컴파일이 되고 파이썬이 설치된 폴더의 Lib/site-packages에 POSITmodule.pyd파일이 생성됩니다. 그 후 파이썬에서 import하여 사용할 수 있습니다.

 

그러나, 적절한 c 컴파일러가 등록되어 있지 않다면 

 

error: unable to find vcvarsall.bat

 

이렇게 에러가 출력됩니다.

 

콘솔에 python을 타이핑하여 파이썬을 실행해보면 필요한 컴파일러 버전이 표시됩니다.

 

 

 

위의 경우 [MSC v.1500 64bit 버전을 사용합니다. 버전 별 해당 비주얼 스튜디오는 아래와 같습니다.

 

Visual C++ 4.x                  MSC_VER=1000
Visual C++ 5                    MSC_VER=1100
Visual C++ 6                    MSC_VER=1200
Visual C++ .NET                 MSC_VER=1300
Visual C++ .NET 2003            MSC_VER=1310
Visual C++ 2005  (8.0)          MSC_VER=1400
Visual C++ 2008  (9.0)          MSC_VER=1500
Visual C++ 2010 (10.0)          MSC_VER=1600
Visual C++ 2012 (11.0)          MSC_VER=1700
Visual C++ 2013 (12.0)          MSC_VER=1800
Visual C++ 2015 (14.0)          MSC_VER=1900
Visual C++ 2017 (15.0)          MSC_VER=1910

  제 시스템의 경우 1500이니까 Visual C++ 2008버전이 필요합니다. 예전 버전의 비주얼 스튜디오는 마이크로소프트 공식 홈페이지에서 다운로드받을 수 있습니다. 아니면 아래 링크에서 Python 2.7을 위한 Microsoft C++ Compiler를 제공합니다. 저는 비주얼 스튜디오 2008을 설치해서 해결했지만 아래 컴파일러만 받아서 설치해도 될 것 같습니다.

 

https://www.microsoft.com/en-us/download/details.aspx?id=44266

 

64비트 파이썬의 경우 Visual studio 2008을 설치할 때 "X64 Compiler and Tools"를 선택해지 않으면 컴파일 할 때 ValueError: [u'path']가 출력됩니다.

 

파이썬 코드에서는

 

1
2
3
4
import POSITmodule 
 
POSITmodule.InitPOSITpoints(np.zeros(4,3))
rvec = POSITmodule.ComputePOSIT(np.zeros(4,2))
cs

 

 

  위와 같이 사용합니다.

 

  이렇게 Numpy를 C코드에서 바로 받아서 처리 후 출력하는 두 개의 메소드를 가진 파이썬용 C코드 모듈을 작성했습니다.

 

  C모듈을 파이썬에서 불러와서 사용하는 방법에 대한 내용들은 웹에서 검색할 수 있지만, 함수 인자 값을 어떻게 넘겨주는지, 또 C 함수에서 원하는 타입의 데이터 형을 어떻게 출력해주는지 등 실제로 사용할 만한 예제가 없어서 직접 시행착오를 겪어보고 포스팅을 작성합니다.

 

더 자세한 내용이 필요하시면 소스코드의 첫 부분 주석에 나와 있는 공식 문서를 참고하시기 바랍니다.