살군의 보조기억 장치

Another memory device…

opencv output formatter 처리 방식

with one comment

자… 내가 처음 타겟으로 잡았던 output bug 에 가까이 가고 있다. 이제 formatter 내용에 대해 분석하기까지 왔다. 물론 여기서도 갈 길은 멀지만, 그래도 처음의 opencv 시작하기 부분을 보면, 완전 헤메고 있었는데 벌써 여기까지 왔나 싶기도 하다. 게다가 아까 한 질문에 누군가 답도 해줬다. opencv 의 output formatter 를 설명하는 예제를 한번 보자.

int main(int,char**)
{
    help();
    Mat I = Mat::eye(4, 4, CV_64F);
    I.at(1,1) = CV_PI;
    cout << "I = " << I << ";" << endl;

    Mat r = Mat(10, 3, CV_8UC3);
    randu(r, Scalar::all(0), Scalar::all(255));

    cout << "r (default) = " << r << ";" << endl << endl;
//    cout << "r (mablab) = " << format(r, "matlab") << ";" << endl << endl;
    cout << "r (python) = " << format(r,"python") << ";" << endl << endl;
    cout << "r (numpy) = " << format(r,"numpy") << ";" << endl << endl;
    cout << "r (csv) = " << format(r,"csv") << ";" << endl << endl;
    cout << "r (c) = " << format(r,"C") << ";" << endl << endl;
    ...

내가 관심있는 부분만 발췌해왔다. 전체 소스코드는 여기에서 볼 수 있다. line:11 을 보면, ostream 을 오버로딩overloading한 matrix 출력이다. 그냥 cout 에 << 연산자로 집어넣으면 바로 들어간다. 내가 살펴보려는 것이 바로 이 메커니즘이다. 저렇게 바로 넣으면 잘 들어가네. 어? 어떻게 저렇게 하지? 궁금하지 아니한가? 세상은 좋아졌고 모든 소스를 볼 수 있다. 이게 구현된 소스 내용을 살펴보자.

static inline std::ostream& operator << (std::ostream& out, const Mat& mtx) {
    Formatter::get()->write(out, mtx);
    return out;
}

class CV_EXPORTS Formatter
{
public:
    virtual ~Formatter() {}
    virtual void write(std::ostream& out, const Mat& m, const int* params=0, int nparams=0) const = 0;
    virtual void write(std::ostream& out, const void* data, int nelems, int type, const int* params=0, int nparams=0) const = 0;
    static const Formatter* get(const char* fmt="");
    static const Formatter* setDefault(const Formatter* fmt);
};

ostream 연산자 오버로딩은 거의 위와 같이 고정된 형식으로 사용한다. 자세한 내용은 다음 기회에… 간단한 함수다. line:10 과 line:11 을 보면, 이상한 문법이 보인다. 함수 뒤에 “= 0” 을 붙어놨다. 이것은 이 함수가 순수가상함수pure virtual function이라는 뜻이다. 다시말해, 이 함수는 현재 정의되어 있지않고, 누군가 상속하는 클래스에서 정의할 것이다 라고 명시적으로 알려주는 것이다. 하나 더 짚고 넘어갈 부분은 여기서 “= 0” 이라고 붙인 것이 NULL을 의미하는 것은 아니다. 자세한 사항은 여기를 참고하고 더 궁금하다면 vtable 로 검색하면 자세한 내용을 찾아볼 수 있다.

위의 코드를 따라가면, line:2 에서 Formatter::get() 으로 Formatter* 를 받아와서 line:10 의 write() 를 실행시킨다. Formatter::get() 으로 실제 어떤 포멧으로 출력할지를 정의한 하위클래스를 결정해서 이 하위 클래스에서 재정의한 write()를 수행하게 된다. 제일 상단에 있는 코드의 line:11 을 수행하면 실제로 Formatter::get() 에서는 MatlabFormatter 를 받아오게 된다.

그럼 순수가상함수가 실제 구현된 MatlabFormatter 부분을 살펴보자.

class MatlabFormatter : public Formatter
{
public:
    virtual ~MatlabFormatter() {}
    void write(std::ostream& out, const Mat& m, const int*, int) const
    {
        out << "[";
        writeMat(out, m, ';', ' ', m.cols == 1);
        out << "]";
    }

    void write(std::ostream& out, const void* data, int nelems, int type, const int*, int) const
    {
        writeElems(out, data, nelems, type, ' ');
    }
};

line:5 에서 상속받은 순수가상함수 write() 가 구현되어있다. 함수의 시그너처가 생략된 것은 이전글을 참고하자. 여기서는 별로 볼 것이 없다. 그냥 writeMat() 함수가 다시 호출된다.

static void writeMat(std::ostream& out, const Mat& m, char rowsep, char elembrace, bool singleLine)
{
    CV_Assert(m.dims <= 2);
    int type = m.type();

    char crowbrace = getCloseBrace(rowsep);
    char orowbrace = crowbrace ? rowsep : '\0';

    if( orowbrace || isspace(rowsep) )
        rowsep = '\0';

    for( int i = 0; i < m.rows; i++ )
    {
        if(orowbrace)
            out << orowbrace;
        if( m.data )
            writeElems(out, m.ptr(i), m.cols, type, elembrace);
        if(orowbrace)
            out << crowbrace << (i+1 < m.rows ? ", " : "");
        if(i+1 < m.rows)
        {
            if(rowsep)
                out << rowsep << (singleLine ? " " : "");
            if(!singleLine)
                out << "\n  ";
        }
    }
}

드디여 내가 생각하는 문제의 핵심에 다다렀다. 동시에 opencv 의 formatter 구조를 모두 살펴봤다.

line:3 을 보면 MatlabFormatter 에서 문제가 무엇인지 알 수 있다. Dimension 이 3 이상일 경우, 출력이 정상적으로 이뤄지지 않고 그냥 exception 을 던져버리도록 되어있다. 그러나 Matlab 의 경우에는 dimension 에 상관없이 결과값을 준다. 예를 들어, 아래의 코드에 대한 결과값 출력이 서로 다르게 나온다. 구조적으로 matlab formatter 와 default formatter 두 개로 나눠서 구현을 해야할 것 같다. 현재는 default formatter 를 호출하면 MatlabFormatter 가 적용되는데, 서로 format 이 다르기 때문이다. 실제 opencv 에 버그 리포팅 Broken MATLAB matrix output formatter (Bug #2789)을 보면,

Mat R = Mat(3, 3, CV_8UC2);
cv::randu(R, Scalar::all(0), Scalar::all(255));
cout << R << endl;      // Formatter::get("MATLAB")->write(cout, R);
Opencv default format:
[91, 2, 79, 179, 52, 205;
236, 8, 181, 239, 26, 248;
207, 218, 45, 183, 158, 101]
 
Matlab format:
(:,:,1) =
91 79 52
236 181 26
207 45 158
(:,:,2) =
2 179 205
8 239 248
218 183 101

Matlab 은 dimension 에 상관없이 이렇게 표시가 된다. 문제를 파악했으니 이제 구현 시작! 😉

Update
몇가지 잘못 생각한 것이 있어서 추가한다. opencv formatter 를 사용해서 N-dimensional Mat 를 출력하는 것이 크게 의미가 있는지에 대한 것이다. matlab formatter 뿐만 아니라 다른 formatter 에서도 공통적으로 사용이 되려면 2D 까지만 지원하는 것이 맞는것 같다. 굳이 N-dim 출력을 고집할 이유가 없다. 호환성을 생각한다면, 나머지 formatter 에서 지원을 못하면 의미가 없으니까. 특히 CSV 타입 같은 경우에는 표현할 수 있는 방법이 없다.

참고
Advertisements

Written by gomiski

2014/03/05 at 11:27 am

One Response

Subscribe to comments with RSS.

  1. […] + multi-channel 부분은 손댈 여지가 없을 정도로 잘 구현이 되어있다. 그러나 이전글에서 봤듯이, 3-dimension 이상은 matrix 의 내용을 쉽게 볼 수가 […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: