살군의 보조기억 장치

Another memory device…

OpenCV Mat 클래스 분석#1

with one comment

뭔가 시리즈물 글은 좋아하지 않느데… 이건 너무 거대한 거라 하나씩 끊고 가지 않으면 답이 없을 것 같아서 처음으로 시리즈를 시작한다. 물론 #2가 언제 나올지는 나도 모른다는게 함정. 지난번 글에서 이제는 코딩을 하면 될꺼라고 생각했는데 실수였다. N-dim matrix 내부를 볼려면 cv::Mat 클래스의 내용을 모르면 어찌 할 수가 없다. 당연히 내가 contribution 하려는 부분에도 문제가 생기는 거고. Matrix 출력을 matlab 처럼 하겠다는건데 내용물을 볼 수 없으면 시작도 못하는거니 말이다.

opencv 에서 cv::Mat 는 제일 기초가 되는 자료구조이다. 메뉴얼도 잘 되있어 opencv doc 페이지를 참조하면 쉽게 사용할 수 있다. 게다가 2-dimension + multi-channel 부분은 손댈 여지가 없을 정도로 잘 구현이 되어있다. 그러나 이전글에서 봤듯이, 3-dimension 이상은 matrix 의 내용을 쉽게 볼 수가 없다.

이 문제를 해결하기 위해, 우선 cv::Mat 내부를 살펴보자.

class CV_EXPORTS Mat
{
	// constructors & destructors
	// tons of overloading functions
	// ...

    enum { MAGIC_VAL=0x42FF0000, AUTO_STEP=0, CONTINUOUS_FLAG=CV_MAT_CONT_FLAG, SUBMATRIX_FLAG=CV_SUBMAT_FLAG };
	
    bool isContinuous() const;         //! returns true iff the matrix data is continuous (i.e. when there are no gaps between successive rows). similar to CV_IS_MAT_CONT(cvmat->type)
    bool isSubmatrix() const;          //! returns true if the matrix is a submatrix of another matrix
    
    size_t elemSize() const;           //! returns element size in bytes, similar to CV_ELEM_SIZE(cvmat->type)
    size_t elemSize1() const;          //! returns the size of element channel in bytes.
    size_t step1(int i=0) const;       //! returns step/elemSize1()
	
    int type() const;                  //! returns element type, similar to CV_MAT_TYPE(cvmat->type)
    int depth() const;                 //! returns element type, similar to CV_MAT_DEPTH(cvmat->type)
    int channels() const;              //! returns element type, similar to CV_MAT_CN(cvmat->type)
    bool empty() const;                //! returns true if matrix data is NULL
    
    size_t total() const;              //! returns the total number of matrix elements
    
    int flags;                    //! includes several bit-fields: - the magic signature, - continuity flag, - depth, - number of channels

    int dims;                     //! the matrix dimensionality, >= 2   
    int rows, cols;               //! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
    uchar* data;                  //! pointer to the data

    int* refcount;                //! pointer to the reference counter; when matrix points to user-allocated data, the pointer is NULL
    uchar* datastart;             //! helper fields used in locateROI and adjustROI
    uchar* dataend;
    uchar* datalimit;

    MatAllocator* allocator;      //! custom allocator

    struct CV_EXPORTS MSize
    {
        //...
        int* p;
    };

    struct CV_EXPORTS MStep
    {
        //...
        size_t* p;
        size_t buf[2];
    };

    MSize size;
    MStep step;

    //...
};

이렇게 길게 소스코드를 늘어놓고 보니, 필수요소는 생각보다 적다. 우선, line:16~18 은 matrix 의 type, depth, channel 을 얻는것이다. 예를들어 matrix 선언시에 CV_16UC3 이라고 정의를 했다면, channel=3, depth=16U 가 되고 type 은 channel과 depth 정보를 모두 반환한다. 자세한 내용은 여기를 참조하자.

Line:25~26 은 matrix 의 행rows과 열cols 그리고 dimension 정보를 저장하고 있다.
Line:27 은 실제 matrix 의 데이터가 저장되는 곳을 지시하는 포인터이다.

마지막으로 line:49~50 은 matrix 의 부가정보를 기록하는 곳이다. matrix 는 초기화시에 size, step 에 대해 정의를 한다. 예를들어, CV_8UC1 에 3 x 4 인 2D matrix + 1-channel 을 정의했을때, step 은 행row과 열col에서 사용하는 Byte 수를 저장하고, size 는 행과 열의 수를 저장한다. 자세한 예제는 다른글을 참고하자.

여기서 언급하고 싶은 부분은 negative index 를 가지는 포인터 사용에 대한 것이다. size 와 step 을 초기화하는 아래 코드를 보면

static inline void setSize( Mat& m, int _dims, const int* _sz,
                            const size_t* _steps, bool autoSteps=false )
{
    CV_Assert( 0 <= _dims && _dims <= CV_MAX_DIM );
    if( m.dims != _dims )
    {
        if( m.step.p != m.step.buf )
        {
            fastFree(m.step.p);
            m.step.p = m.step.buf;
            m.size.p = &m.rows;
        }
        if( _dims > 2 )
        {
            m.step.p = (size_t*)fastMalloc(_dims*sizeof(m.step.p[0]) + (_dims+1)*sizeof(m.size.p[0]));
            m.size.p = (int*)(m.step.p + _dims) + 1;
            m.size.p[-1] = _dims;
            m.rows = m.cols = -1;
        }
    }
    //...
}

line:17 에서 어마어마한 사용법을 볼 수 있다. 세상에나! 인덱스가 음수다. 이게 가능한가? 첨 보면 아주아주 당황스럽다. 기본적으로 배열의 인덱스는 0 부터 시작하고 음수가 될 수 없다라고 알고 있을 것이다. 그러나 실상은 그렇지 않다. 즉, 음수가 가능하다. 단, 미리 정의된 배열의 범위안에서 사용이 가능한 것이다.

That is correct. From C99 §6.5.2.1/2:

The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))).

There’s no magic. It’s a 1-1 equivalence. As always when dereferencing a pointer (*), you need to be sure it’s pointing to a valid address.

하나하나 정리를 하고 싶은데 시간이 없네. 위의 코드에서 step 과 size 의 핵심은 메모리 영역을 공유하면서 포인터로 지시하는 위치만 다르다는 것이다.. 할당된 메모리의 시작위치는 step.p 가 가르키고 있고, dim + 1 만큼 이동한 위치를 size.p 가 지시한다. 예를들어, 2D matrix 를 선언했다고 할 때, size 와 step 이 사용하는 메모리는 2 * sizeof(m.step.p[0]) + (2 + 1) * sizeof(m.size.p[0])만큼 할당이 된다. m.step.p[0] 과 m.size.p[0] 은 unsigned int 이므로 sizeof(m.step.p[0]) 은 4 가 된다. 그러므로 총 메모리 할당량은 20 Byte 가 된다.

아래 메모리 모형은 1 칸이 4 Byte 라고 볼 때,

| a | b | c | d | e | …

m.step.p = addr(a)
m.size.p = addr(d)
m.size.p – 1 = addr(c) —> m.size.p[-1] = value(c)

가 되므로 유효한 표현식이 되는 것이다. 이 내용을 확인하고 싶다면 여기 참조.

나머지 내용은 다음 기회에.

참고
Advertisements

Written by gomiski

2014/03/13 at 5:04 am

One Response

Subscribe to comments with RSS.

  1. […] 각 함수, 멤버변수가 의미하는 바를 쉽게 확인할 수 있을 것이다. 아래글에서 언급한 내용을 확인하는데 도움이 될 […]


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: