728x90

앞서 구현한 벡터는 vector_get()을 통해 직접 인덱싱할 수 있었지만, C++의 for (auto x : vec) 같은 문법은 지원하지 않기 때문에 반복문에서 사용하기가 조금 불편합니다.

그래서 이번에는 C 스타일로 이터레이터(iterator)를 만들어서 while, for 루프에서 자연스럽게 벡터를 순회할 수 있도록 기능을 확장해 보겠습니다.


🔧 이터레이터 구조 정의

typedef struct {
    StaticVector* vector;
    size_t index;
} VectorIterator;
  • vector: 순회 대상이 되는 벡터
  • index: 현재 이터레이터가 가리키고 있는 인덱스

📜 이터레이터 함수들

// 시작 위치의 이터레이터 반환
VectorIterator vector_begin(StaticVector* vec) {
    VectorIterator it = {vec, 0};
    return it;
}

// 다음 요소가 존재하는지 확인
int vector_iterator_has_next(VectorIterator* it) {
    return it->index < it->vector->size;
}

// 다음 요소를 반환하고, 내부 인덱스를 증가
void* vector_iterator_next(VectorIterator* it) {
    if (!vector_iterator_has_next(it)) return NULL;
    return it->vector->buffer + (it->element_size * it->index++);
}

🧪 사용 예시

int main() {
    int buffer[MAX_VECTOR_CAPACITY];
    StaticVector vec;
    vector_init_static(&vec, buffer, sizeof(int), MAX_VECTOR_CAPACITY);

    for (int i = 1; i <= 5; ++i) {
        vector_push_back(&vec, &i);  // 1 ~ 5 저장
    }

    printf("== 벡터 요소 출력 ==\n");
    VectorIterator it = vector_begin(&vec);
    while (vector_iterator_has_next(&it)) {
        int* val = (int*)vector_iterator_next(&it);
        printf("%d ", *val);
    }
    printf("\n");

    return 0;
}

출력:

 
== 벡터 요소 출력 ==
1 2 3 4 5

✅ 이터레이터 도입의 장점

 

이점 설명
코드 가독성 향상 반복문에서 인덱싱 없이 순회 가능
범용성 모든 타입의 벡터에 공통으로 사용 가능
재사용성 이터레이터 구조를 통해 다른 컨테이너에도 확장 가능

🔚 마무리하며

이번 글에서는 정적 제네릭 벡터에 이터레이터 기능을 추가해 반복문에서 쉽게 사용할 수 있도록 확장해 보았습니다. 이를 통해 C에서도 마치 고급 언어처럼 벡터를 유연하게 사용할 수 있게 되었죠!

728x90
728x90

C++의 std::vector처럼 다양한 타입의 데이터를 담을 수 있는 벡터 자료구조는 매우 편리합니다. 하지만 임베디드 환경에서 동작하는 C 언어에서는 다음과 같은 제약이 존재하죠:

  • malloc, realloc, free와 같은 동적 메모리 할당이 금지
  • 표준 라이브러리 의존 최소화 필요
  • 컴파일 타임에 크기가 고정된 구조 선호

이 글에서는 이런 조건을 만족하는 정적(static) 제네릭 벡터를 직접 구현해 보겠습니다.
 


목표

  • 어떤 타입이든 담을 수 있는 제네릭 벡터
  • malloc 없이 작동 (정적 메모리)
  • 삽입, 삭제, 조회 기능 지원
  • C 언어로 작성

📦 구조 설계

먼저 벡터 구조체는 다음과 같은 정보를 담습니다:

typedef struct {
    uint8_t* buffer;       // 데이터를 저장하는 버퍼
    size_t element_size;   // 요소 하나의 크기
    size_t size;           // 현재 저장된 요소 개수
    size_t capacity;       // 최대 저장 가능한 요소 개수
} StaticVector;

🛠️ 구현 코드

#include <stdio.h>
#include <string.h>
#include <stdint.h>

#define MAX_VECTOR_CAPACITY 128  // 벡터 최대 용량 (임베디드 환경에서 제한)

// 제네릭 벡터 구조체
typedef struct {
    uint8_t* buffer;       // 외부에서 주입받은 버퍼
    size_t element_size;   // 요소 하나의 크기
    size_t size;           // 현재 저장된 요소 수
    size_t capacity;       // 전체 저장 가능한 요소 수
} StaticVector;

// 초기화 (버퍼는 외부에서 미리 선언하여 전달)
void vector_init_static(StaticVector* vec, void* buffer, size_t element_size, size_t capacity) {
    vec->buffer = (uint8_t*)buffer;
    vec->element_size = element_size;
    vec->size = 0;
    vec->capacity = capacity;
}

int vector_push_back(StaticVector* vec, void* element) {
    if (vec->size >= vec->capacity) return -1;  // overflow
    memcpy(vec->buffer + vec->size * vec->element_size, element, vec->element_size);
    vec->size++;
    return 0;
}

int vector_insert(StaticVector* vec, size_t index, void* element) {
    if (vec->size >= vec->capacity || index > vec->size) return -1;

    uint8_t* insert_pos = vec->buffer + index * vec->element_size;
    memmove(insert_pos + vec->element_size, insert_pos, (vec->size - index) * vec->element_size);
    memcpy(insert_pos, element, vec->element_size);
    vec->size++;
    return 0;
}

int vector_erase(StaticVector* vec, size_t index) {
    if (index >= vec->size) return -1;
    uint8_t* erase_pos = vec->buffer + index * vec->element_size;
    memmove(erase_pos, erase_pos + vec->element_size, (vec->size - index - 1) * vec->element_size);
    vec->size--;
    return 0;
}

void* vector_get(StaticVector* vec, size_t index) {
    if (index >= vec->size) return NULL;
    return vec->buffer + index * vec->element_size;
}

void vector_clear(StaticVector* vec) {
    vec->size = 0;
}

✨ 사용 예시

int main() {
    int buffer[MAX_VECTOR_CAPACITY];  // 정적으로 할당된 버퍼
    StaticVector vec;

    vector_init_static(&vec, buffer, sizeof(int), MAX_VECTOR_CAPACITY);

    for (int i = 0; i < 5; ++i) {
        vector_push_back(&vec, &i);
    }

    int value = 999;
    vector_insert(&vec, 2, &value);
    vector_erase(&vec, 0);

    for (size_t i = 0; i < vec.size; ++i) {
        int* p = (int*)vector_get(&vec, i);
        printf("%d ", *p);
    }

    return 0;
}

출력 예:

 
1 999 2 3 4

 


✅ 정리

 

항목 설명
제네릭 지원 void*와 element_size를 통해 어떤 타입도 저장 가능
정적 메모리 외부에서 버퍼를 받아와 동적 할당 없이 작동
기본 연산 지원 push_back, insert, erase, get, clear 등
임베디드 최적화 malloc 없이도 안전하게 작동

 
 
 
 

// static_vector.h
#ifndef STATIC_VECTOR_H
#define STATIC_VECTOR_H

#include <stddef.h>
#include <stdint.h>

// 정적 제네릭 벡터 구조체
typedef struct {
    uint8_t* buffer;
    size_t element_size;
    size_t size;
    size_t capacity;
} StaticVector;

// 벡터 초기화 (외부 버퍼 필요)
void vector_init_static(StaticVector* vec, void* buffer, size_t element_size, size_t capacity);

// 요소 추가
int vector_push_back(StaticVector* vec, void* element);

// 요소 삽입
int vector_insert(StaticVector* vec, size_t index, void* element);

// 요소 삭제
int vector_erase(StaticVector* vec, size_t index);

// 요소 접근
void* vector_get(StaticVector* vec, size_t index);

// 벡터 비우기
void vector_clear(StaticVector* vec);

// ===== 이터레이터 =====
typedef struct {
    StaticVector* vector;
    size_t index;
} VectorIterator;

VectorIterator vector_begin(StaticVector* vec);
int vector_iterator_has_next(VectorIterator* it);
void* vector_iterator_next(VectorIterator* it);

#endif // STATIC_VECTOR_H
// static_vector.c
#include "static_vector.h"
#include <string.h>

void vector_init_static(StaticVector* vec, void* buffer, size_t element_size, size_t capacity) {
    vec->buffer = (uint8_t*)buffer;
    vec->element_size = element_size;
    vec->size = 0;
    vec->capacity = capacity;
}

int vector_push_back(StaticVector* vec, void* element) {
    if (vec->size >= vec->capacity) return -1;
    memcpy(vec->buffer + vec->size * vec->element_size, element, vec->element_size);
    vec->size++;
    return 0;
}

int vector_insert(StaticVector* vec, size_t index, void* element) {
    if (vec->size >= vec->capacity || index > vec->size) return -1;
    uint8_t* insert_pos = vec->buffer + index * vec->element_size;
    memmove(insert_pos + vec->element_size, insert_pos, (vec->size - index) * vec->element_size);
    memcpy(insert_pos, element, vec->element_size);
    vec->size++;
    return 0;
}

int vector_erase(StaticVector* vec, size_t index) {
    if (index >= vec->size) return -1;
    uint8_t* erase_pos = vec->buffer + index * vec->element_size;
    memmove(erase_pos, erase_pos + vec->element_size, (vec->size - index - 1) * vec->element_size);
    vec->size--;
    return 0;
}

void* vector_get(StaticVector* vec, size_t index) {
    if (index >= vec->size) return NULL;
    return vec->buffer + index * vec->element_size;
}

void vector_clear(StaticVector* vec) {
    vec->size = 0;
}

// ===== 이터레이터 구현 =====

VectorIterator vector_begin(StaticVector* vec) {
    VectorIterator it = {vec, 0};
    return it;
}

int vector_iterator_has_next(VectorIterator* it) {
    return it->index < it->vector->size;
}

void* vector_iterator_next(VectorIterator* it) {
    if (!vector_iterator_has_next(it)) return NULL;
    return it->vector->buffer + (it->vector->element_size * it->index++);
}
728x90
728x90

컴퓨터 비전, 로봇공학, 증강현실 등 다양한 분야에서 3D 세계를 2D 이미지로 변환하는 과정은 필수적입니다. 이 변환 과정에서 핵심적인 역할을 하는 것이 바로 카메라 내부 파라미터(Intrinsic Parameters)입니다. 이 글에서는 카메라 내부 파라미터의 개념, 구성 요소, 수학적 표현, 그리고 응용 분야에 대해 자세히 알아보겠습니다.

 

1. 카메라 내부 파라미터란?

카메라 내부 파라미터는 카메라의 광학적 특성과 이미지 센서의 특성을 설명하는 요소들로, 카메라 자체의 고유한 특성을 나타냅니다. 이 파라미터들은 카메라의 제조 과정에서 결정되며, 카메라가 3D 세계를 2D 이미지로 투영하는 방식을 수학적으로 표현합니다.

 

카메라 내부 파라미터는 일반적으로 핀홀 카메라 모델(Pinhole Camera Model)을 기반으로 합니다. 이 모델은 카메라가 하나의 점(핀홀)을 통해 외부의 상이 이미지로 투영된다는 개념에 기초합니다. 실제 카메라는 렌즈를 사용하지만, 수학적 모델링을 위해 핀홀 모델을 사용하는 것이 일반적입니다.

 

2. 내부 파라미터의 구성 요소

카메라 내부 파라미터는 다음과 같은 주요 요소로 구성됩니다:

2.1 초점 거리(Focal Length)

초점 거리는 렌즈 중심에서 이미지 센서까지의 거리를 의미합니다. 위 그림에서 볼 수 있듯이, 렌즈를 통과한 빛이 이미지 센서에 초점을 맺는 지점까지의 거리가 바로 초점 거리입니다. 카메라 모델에서 초점 거리는 픽셀 단위로 표현되며, 일반적으로 가로(fx)와 세로(fy) 방향으로 구분됩니다.

왜 fx와 fy 두 개의 값이 있을까?

일반적인 카메라 모델에서는 초점 거리를 두 개의 값으로 표현합니다:

  1. fx: x축(가로) 방향의 초점 거리
  2. fy: y축(세로) 방향의 초점 거리

이것은 이미지 센서의 픽셀이 가로와 세로 방향으로 다른 크기를 가질 수 있기 때문입니다. 현대 카메라에서는 대부분 픽셀이 정사각형이므로 fx와 fy의 값이 거의 같지만, 컴퓨터 비전 알고리즘에서는 이 두 값을 별도로 처리합니다.

 

현대 카메라에서는 일반적으로 fx와 fy의 값이 같거나 매우 유사합니다. 초점 거리가 길수록(값이 클수록) 시야가 좁아지고 확대 효과가 커지며, 초점 거리가 짧을수록 넓은 시야를 얻을 수 있습니다.

 

2.2 주점(Principal Point)

주점은 카메라 렌즈의 광학 축이 이미지 센서와 만나는 지점을 의미합니다. 이상적인 카메라에서는 이미지의 중심과 일치하지만, 실제로는 제조 오차 등으로 인해 약간의 차이가 있을 수 있습니다.

  • cx: 주점의 x좌표
  • cy: 주점의 y좌표

주점은 이미지 좌표계에서 표현되며, 픽셀 단위로 나타냅니다.

 

2.3 비대칭 계수(Skew Coefficient)

비대칭 계수는 이미지 센서의 픽셀이 완벽한 직사각형이 아닐 때 발생하는 왜곡을 나타냅니다. 이는 이미지 센서의 x축과 y축이 완벽하게 직교하지 않을 때 생기는 왜곡을 보정하기 위한 파라미터입니다.

  • s: 비대칭 계수(skew coefficient)

현대의 대부분 카메라에서는 제조 기술의 발전으로 비대칭 계수가 매우 작거나 무시할 수 있는 수준이므로, 일반적으로 s=0으로 간주합니다.

 

일반적인 픽셀과 비대칭 픽셀의 차이를 비교하면:

  • 일반 픽셀: 직사각형 구조, x축과 y축이 직교(90°)
  • 비대칭 픽셀: 평행사변형 구조, x축과 y축이 θ 각도로 만남

 

3. 카메라 내부 파라미터 행렬(Camera Intrinsic Matrix)

카메라 내부 파라미터는 일반적으로 3×3 행렬 형태로 표현됩니다. 이 행렬을 카메라 내부 행렬(Camera Intrinsic Matrix) 또는 카메라 캘리브레이션 행렬(Camera Calibration Matrix)이라고 합니다.

내부 파라미터 행렬 K는 다음과 같이 표현됩니다:

K = [fx  s  cx]
    [0  fy  cy]
    [0   0   1]

여기서:

  • fx, fy: x축과 y축 방향의 초점 거리(픽셀 단위)
  • cx, cy: 주점의 좌표(픽셀 단위)
  • s: 비대칭 계수(일반적으로 0)

이 행렬은 3D 카메라 좌표계의 점을 2D 이미지 좌표계로 투영하는 데 사용됩니다.

4. 내부 파라미터와 핀홀 카메라 모델

핀홀 카메라 모델에서 3D 공간상의 점 P = (X, Y, Z)가 2D 이미지 상의 점 p = (u, v)로 투영되는 과정은 다음과 같이 표현할 수 있습니다:

[u]   [fx  s  cx][X/Z]
[v] = [0  fy  cy][Y/Z]
[1]   [0   0   1][1  ]

여기서 (X, Y, Z)는 카메라 좌표계에서의 3D 점 좌표이고, (u, v)는 이미지 좌표계에서의 2D 점 좌표입니다.

이 과정에서 눈여겨볼 점은 3D 점의 Z 좌표로 X와 Y를 나누는 것입니다. 이는 원근 투영(perspective projection)의 핵심 특성으로, 멀리 있는 물체가 더 작게 보이는 현상을 수학적으로 표현한 것입니다.

5. 카메라 캘리브레이션

카메라 내부 파라미터를 결정하는 과정을 카메라 캘리브레이션(Camera Calibration)이라고 합니다. 캘리브레이션은 보통 특정 패턴(예: 체스보드)을 다양한 각도와 거리에서 촬영한 이미지를 사용하여 수행합니다.

캘리브레이션 과정은 대략적으로 다음과 같습니다:

  1. 체스보드와 같은 알려진 패턴을 다양한 위치와 방향에서 촬영합니다.
  2. 이미지에서 패턴의 특징점(예: 체스보드의 코너)을 찾습니다.
  3. 실제 3D 공간에서의 특징점 위치와 이미지에서 검출된 특징점 위치 사이의 관계를 이용하여 내부 파라미터를 계산합니다.

주요 캘리브레이션 도구로는 OpenCV의 calibrateCamera() 함수, MATLAB의 Camera Calibration Toolbox, GML Camera Calibration Toolbox 등이 있습니다.

요약 및 결론

- 카메라 내부 파라미터는 컴퓨터 비전 분야에서 3D 세계를 2D 이미지로 변환하는 핵심적인 요소입니다. 초점 거리, 주점, 비대칭 계수 등으로 구성되며, 3×3 행렬 형태로 표현됩니다.

- 정확한 내부 파라미터를 결정하기 위해서는 카메라 캘리브레이션 과정이 필요하며, 다양한 도구와 라이브러리를 활용할 수 있습니다.

- 내부 파라미터는 3D 재구성, 증강현실, 로봇 비전 등 다양한 응용 분야에서 활용되며, 컴퓨터 비전 알고리즘의 정확도와 성능에 직접적인 영향을 미칩니다.

728x90
728x90

🔧 캘리브레이션의 원리

카메라는 3차원 공간상의 점 2차원 이미지 좌표로 변환합니다. 이 과정은 기본적으로 **원근 투영(Perspective Projection)**에 의해 이루어집니다. 즉, 멀리 있는 물체는 더 작게 보이고, 가까이 있는 물체는 더 크게 보이는 현상을 수학적으로 모델링하는 것입니다.

이 변환 과정은 일반적으로 **핀홀 카메라 모델(Pinhole Camera Model)**을 기반으로 설명됩니다.

 

📌 핀홀 카메라 모델

핀홀 카메라 모델은 이상적인 카메라의 구조를 가정하는 모델입니다. 이 모델에서는 렌즈 없이 하나의 작은 구멍(핀홀)을 통해 빛이 들어와 이미지 평면에 투영됩니다. 즉, 렌즈의 굴절 효과나 센서의 비선형 왜곡을 고려하지 않은 이상적인 모델입니다.

 

 

 

🔹 카메라 투영 모델

3D 공간상의 점 가 카메라를 통해 2D 이미지 좌표 로 변환되는 과정은 다음과 같이 표현됩니다:

여기서,

  • A (Intrinsic Camera Matrix): 카메라 내부 파라미터 행렬 (초점 거리, 광학 중심 등 포함)
  • [R | t] (Extrinsic Parameters): 카메라의 위치 및 방향을 나타내는 외부 파라미터 행렬
  • (X, Y, Z): 월드 좌표계에서의 3D 점
  • (x, y): 이미지 좌표계에서의 2D 점

이 관계식을 통해 카메라가 어떻게 3D 세계를 2D 이미지로 변환하는지를 설명할 수 있으며, 이를 활용하여 캘리브레이션을 수행합니다.

 

🎯 카메라 좌표계와 투영 과정

카메라 좌표계에서 3D 점이 2D 이미지 평면으로 변환되는 과정은 다음 단계로 정리할 수 있습니다.

  1. 월드 좌표계 → 카메라 좌표계 변환
    • 3D 공간상의 점 (X, Y, Z) 를 카메라 위치에 맞게 변환
    • 외부 파라미터  [R | t] 를 이용하여 변환 수행
  2. 카메라 좌표계 → 정규화 이미지 평면
    • **초점 거리 f**을 고려하여 (Xc,Yc,Zc) (x', y')로 변환
    • x′ = Xc /Zc,  y′ = Yc /Zcx'
  3. 정규화 이미지 평면 → 실제 이미지 평면
    • 내부 파라미터 행렬 A를 이용하여 픽셀 좌표로 변환
    • x=fx⋅x′+cx,  y=fy⋅y′+cy

이 과정을 통해 실제로 촬영된 이미지에서 3D 점이 어떻게 2D 좌표로 변환되는지 설명할 수 있습니다.

 

 

✅ 정리

카메라 투영 모델은 3D 공간에서 2D 이미지로 변환하는 과정을 설명하는 핵심 개념입니다. 특히 핀홀 카메라 모델을 기반으로 한 투영 수식은 컴퓨터 비전 및 영상 처리 분야에서 필수적으로 사용됩니다.

📌 핵심 포인트

  • 카메라는 3D 공간상의 점을 2D 이미지로 변환하는 역할을 함
  • 투영 과정은 월드 좌표 → 카메라 좌표 → 이미지 평면 순으로 진행됨
  • 내부/외부 파라미터를 이용해 카메라의 위치 및 렌즈 특성을 반영할 수 있음

 

728x90

+ Recent posts