[OpenCV] 영상 데이터 구조와 표현 방법

zzwon1212·2023년 10월 21일
0

OpenCV

목록 보기
1/16
post-thumbnail

1. 좌표계와 행렬

  • w×hw \times h 영상에서 사용되는 좌표계

    (0, 0)(0, 1)\cdots(0, w-1)
    (1, 0)(1, 1)\cdots(1, w-1)
    \vdots\vdots\ddots\vdots
    (h-1, 0)(h-1, 1)\cdots(h-1, w-1)
  • M×NM \times N 행렬로도 표현 가능하다. 위 좌표계를 Transpose한 형태이므로, 혼동을 막기 위해 작업 전에 정의를 명확히 하자.

    [a1,1a1,2a1,Na2,1a2,2a2,NaM,1aM,2aM,N]\begin{bmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,N} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,N} \\ \vdots & \vdots & \ddots & \vdots \\ a_{M,1} & a_{M,2} & \cdots & a_{M,N} \\ \end{bmatrix}

2. C++에서 픽셀 표현

2.1. Grayscale Image

typedef unsigned char BYTE;		// Windows
typedef unsigned char uint_8t;	// Linux
typedef unsigned char uchar;	// OpenCV
  • 한 픽셀의 범위 [0, 255]
  • unsigned char 데이터 타입으로 표현(1Byte)

2.2. Color Image

class RGB
{
    unsigned char R;
    unsigned char G;
    unsigned char B;
};
  • unsigned char 자료형 3개를 멤버 변수로 가지는 배열/구조체(3Byte)

3. C++에서 영상 표현

3.1. 정적 2차원 배열의 생성

unsigned char a[480][640] {};
  • {}: 초기값을 default값으로 설정. 배열의 모든 원소값이 0으로 초기화
  • 단점
    • 배열의 크기를 미리 알고 있어야 하기 때문에 다양한 크기의 영상을 표현하기에 부적절
    • 대략 1MB까지 가능한 Stack 영역에 메모리 할당하기 때문에 대략 2MB인 FHD는 저장을 할 수 없음

3.2. 동적 2차원 배열

[출처: 영상 데이터 구조와 표현 방법 - 황선규 강사]

3.2.1. 생성

int w = 640;
int h = 480;
    
unsigned char** p;
p = new unsigned char*[h];
for (int i = 0; i < h; i++) {
    p[i] = new unsigned char[w] {};
}
  • new 연산자는 동적 메모리 할당을 수행하며, 이를 통해 Heap 영역에 메모리를 할당한다. x86는 2GB, x64는 8TB까지 할당 가능하다. (?)메모리 사용이 끝난 후에는 해제한다.
  • 변수 w에 640, h에 480이 할당된다. p는 2차원 포인터이며, unsigned char 유형을 가리키는 포인터 배열을 나타낸다. p 내부에는 h개의 unsigned char* 포인터가 있다. 각 포인터는 w개의 unsigned char를 가리키는 배열을 가리킨다. p는 h행, w열을 갖는 2차원 배열의 구조를 나타낸다. 반복문을 사용하여 p 내부의 각 포인터를 동적으로 할당하고, w 크기의 배열로 초기화하였으므로, p 내부의 각 unsigned char 배열은 w개의 요소를 가지며, 이 요소는 초기화되어 0 또는 기본값으로 설정된다.

3.2.2. 원소에 접근

for (int y = 0; y < h; y++) {
    for (int x = 0; x < w; x++) {
        p[y][x] = p[y][x] + 10;
    }
}

→→→→→→
→→→→→→
→→→→→→
→→→→→→

  • 위 화살표 방향대로 2차원 배열 p의 모든 원소 값을 10씩 증가시킨다. p가 만들어진 과정을 생각해보면 이 방향이 ↓ 방향보다 효율적이다.

3.2.3. 메모리 해제

for (int y = 0; y < h; y++) {
    delete[] p[i];
}
delete[] p;
  • 동적 2차원 배열 생성의 역순으로 해제
  • delete 구문에서 괄호 연산자[]를 반드시 사용

3.3. 대용량 1차원 메모리

3.3.1. 메모리 할당 후 영상 데이터 저장하기

int w = 10, h = 10;
unsigned char* data = new unsigned char[w * h] {};
...

delete[] data;	// 메모리 공간 해제

3.3.2. 특정 좌표 (x, y) 위치의 픽셀 값 참조하기

unsigned char& p1 = *(data + y*w + x);
  • p1은 참조변수이다.
  • data + y*w + x은 data의 y*w + x번째 요소의 주소이다.
  • *(data + y*w + x)은 data의 y*w + x번째 요소의 값이다.

3.4. 영상 데이터 저장 클래스(간단한)

class MyImage
{
public:
    MyImage() : w(0), h(0), data(0) {}

    MyImage(int _w, int _h) : w(_w), h(_h) {
        data = new unsigned char[w*h] {};
    }

    ~MyImage() {
        if (data) delete[] data;
    }

    unsigned char& at(int x, int y) {
        return *(data + y * w + x);
    }

public:
    int w, h;
    unsigned char* data;
};
  • 동적 할당과 픽셀 값 참조를 이용해서 영상 데이터를 저장할 수 있는 MyImage 클래스
  • 편의를 위해 모든 데이터 멤버를 public 지시자로 선언
  • data: 픽셀 데이터를 저장하기 위해서 동적 할당한 메모리 공간의 시작 주소를 가리킬 포인터 변수
  • MyImage 클래스의 디폴트 생성자를 보면 w, h, data를 0으로 초기화한다.
  • 2개의 정수값을 생성자 인자로 받는 형태의 MyImage 생성자에서는, w와 h를 초기화하고, data 포인터 변수는 new 연산자를 이용해서 데이터 공간을 할당하고 그 시작 주소를 가리키도록 설정한다.
  • 소멸자에서는 만약 메모리 할당이 있었다면, 해당 메모리를 해제한다.
  • at 함수를 이용해서 (x, y) 좌표에 있는 픽셀 값을 참조로 반환해서, 해당 픽셀 값을 읽어올 뿐만 아니라 픽셀 값을 설정할 수도 있도록 한다.

📙강의 - 강사 황선규

profile
JUST DO IT.

0개의 댓글