Eigen C++ 라이브러리 완벽 가이드: 기본부터 심화까지

Eigen은 C++에서 선형 대수 연산을 위한 강력한 라이브러리로, 복잡한 행렬 및 벡터 계산을 효율적으로 처리할 수 있도록 설계되었습니다. 특히 3D 재구성, 컴퓨터 비전, 로보틱스 등에서 필수적으로 사용됩니다. Eigen 라이브러리는 고도로 최적화된 내부 구현을 통해 성능을 극대화하며, 사용자는 복잡한 최적화 과정을 직접 신경 쓸 필요 없이 선형 대수 연산에 집중할 수 있습니다.

1. Matrix 객체 생성 및 초기화

Eigen의 모든 데이터 타입은 Matrix 템플릿 클래스를 기반으로 합니다. 크기와 데이터 타입은 템플릿 인자로 지정됩니다. 예를 들어, 2x2 정수 행렬은 Eigen::Matrix<int, 2, 2>로, 3x3 부동소수점 행렬은 Eigen::Matrix<float, 3, 3>으로 정의할 수 있습니다. Eigen은 이를 위한 편리한 타입 별칭을 제공합니다 (예: Matrix2i, Matrix3f).

고정 크기 행렬 및 벡터

고정 크기 객체는 컴파일 시점에 크기가 결정됩니다. 생성 시 직접 값을 대입하는 방식 (<< 연산자)을 사용하며, 벡터의 경우 생성자에 직접 값을 전달할 수도 있습니다. C++11 이상을 지원하는 경우, 초기화 리스트를 사용하여 객체를 생성할 수도 있습니다.


#include <Eigen/Dense>
#include <iostream>

int main() {
    // 고정 크기 정수 2x2 행렬
    Eigen::Matrix<int, 2, 2> fixedMat;
    Eigen::Matrix2i matAlias; // 타입 별칭 사용

    // 초기화 리스트 (C++11 이상)
    Eigen::MatrixXi dynamicMat = {{1, 2, 3}, {4, 5, 6}};

    // 2차원 부동소수점 벡터 초기화
    Eigen::Vector2f vec(99.9f, 88.8f);

    std::cout << "Fixed Matrix:\n" << fixedMat << std::endl;
    std::cout << "Alias Matrix:\n" << matAlias << std::endl;

    // << 연산자를 이용한 값 대입 (벡터도 가능)
    matAlias << 1, 2,
                3, 4;
    std::cout << "After Assignment (matAlias):\n" << matAlias << std::endl;
    std::cout << "Vector vec:\n" << vec << std::endl;
    std::cout << "Dynamic Matrix:\n" << dynamicMat << std::endl;

    return 0;
}

동적 크기 행렬

크기가 런타임에 결정되는 경우, Eigen::Matrix<T, Dynamic, Dynamic> 또는 Eigen::MatrixXd (double 타입)와 같은 타입을 사용합니다. resize() 함수를 통해 크기를 변경할 수 있습니다.


#include <Eigen/Dense>
#include <iostream>

int main() {
    Eigen::MatrixXi dymat; // 동적 크기 정수 행렬 선언
    dymat.resize(2, 3); // 크기 2x3으로 조정
    dymat << 1, 2, 3,
             4, 5, 6;
    std::cout << "Initial dynamic matrix:\n" << dymat << std::endl;
    std::cout << "Rows: " << dymat.rows() << ", Cols: " << dymat.cols() << std::endl;

    dymat.resize(3, 2); // 크기 3x2로 변경
    std::cout << "Resized dynamic matrix:\n" << dymat << std::endl;
    std::cout << "Rows: " << dymat.rows() << ", Cols: " << dymat.cols() << std::endl;

    return 0;
}

참고: Eigen은 기본적으로 열 우선(Column-Major) 저장 방식을 사용합니다. 동적 크기 행렬은 힙(heap)에 할당되며, 고정 크기 행렬은 스택(stack)에 할당되는 경향이 있습니다. 일반적으로 16x16 미만의 작은 고정 크기 행렬은 성능상 이점이 있습니다.

2. 행렬 요소 접근

행렬의 특정 요소에 접근하려면 () 연산자를 사용합니다. 인덱스는 0부터 시작합니다. 벡터 역시 동일한 방식으로 접근합니다.


#include <Eigen/Dense>
#include <iostream>

int main() {
    Eigen::MatrixXd m(2, 2); // 동적 크기 double 행렬
    m(0, 0) = 3.0;
    m(1, 0) = 2.5;
    m(0, 1) = -1.0;
    m(1, 1) = m(1, 0) + m(0, 1); // 요소 간 연산
    std::cout << "Matrix m:\n" << m << std::endl;

    Eigen::VectorXd v(2); // 동적 크기 double 벡터
    v(0) = 4.0;
    v(1) = v(0) - 1.0;
    std::cout << "Vector v:\n" << v << std::endl;

    return 0;
}

3. 행렬 사칙연산

Eigen은 +, -, *, / 등의 연산자를 오버로드하여 직관적인 연산을 지원합니다. 연산을 수행하는 행렬들은 반드시 동일한 데이터 타입이어야 합니다. 타입 변환이 필요한 경우 cast() 멤버 함수를 사용합니다.

덧셈과 뺄셈

행렬 덧셈, 뺄셈 및 복합 할당 연산 (+=, -=)을 지원합니다.


#include <Eigen/Dense>
#include <iostream>

int main() {
    Eigen::Matrix2d matA;
    matA << 1.0, 2.0,
            3.0, 4.0;
    Eigen::MatrixXd matB(2, 2);
    matB << 2.0, 3.0,
            1.0, 4.0;

    std::cout << "matA + matB =\n" << matA + matB << std::endl;
    std::cout << "matA - matB =\n" << matA - matB << std::endl;

    matA += matB; // 복합 할당 연산
    std::cout << "After matA += matB, matA =\n" << matA << std::endl;

    Eigen::Vector3d vecV(1.0, 2.0, 3.0);
    Eigen::Vector3d vecW(1.0, 0.0, 0.0);
    std::cout << "-vecV + vecW - vecV =\n" << -vecV + vecW - vecV << std::endl;

    return 0;
}

스칼라 곱셈 및 나눗셈

행렬과 스칼라 값 간의 곱셈 및 나눗셈을 수행합니다.


#include <Eigen/Dense>
#include <iostream>

int main() {
    Eigen::Matrix2d mat = {{1.0, 2.0}, {3.0, 4.0}};
    std::cout << "Original matrix:\n" << mat << std::endl;

    auto scaledMat = mat * 2.0; // 스칼라 곱셈
    auto dividedMat = mat / 2.0; // 스칼라 나눗셈

    std::cout << "Matrix * 2.0:\n" << scaledMat << std::endl;
    std::cout << "Matrix / 2.0:\n" << dividedMat << std::endl;

    return 0;
}

행렬 곱셈

행렬 곱셈은 * 연산자를 사용합니다. 곱셈이 가능하려면 첫 번째 행렬의 열 수와 두 번째 행렬의 행 수가 같아야 합니다.


#include <Eigen/Dense>
#include <iostream>

int main() {
    Eigen::Matrix2d matA = {{1.0, 2.0}, {3.0, 4.0}};
    Eigen::Matrix2d matB = matA.transpose(); // 전치 행렬

    std::cout << "Matrix A:\n" << matA << std::endl;
    std::cout << "Matrix B (A transpose):\n" << matB << std::endl;
    std::cout << "A * B:\n" << matA * matB << std::endl;

    Eigen::Vector2d vecC(1.0, 1.0);
    std::cout << "A * B * C:\n" << matA * matB * vecC << std::endl;

    return 0;
}

내적과 외적

벡터의 내적(dot product)은 dot() 함수 또는 transpose() * vector 형태로 계산할 수 있습니다. 외적(cross product)은 3차원 벡터에 대해 cross() 함수를 사용합니다.


#include <Eigen/Dense>
#include <iostream>

int main() {
    Eigen::Vector3d vecA(1.0, 1.0, 1.0);
    Eigen::Vector3d vecB(3.0, 4.0, 5.0);

    // 내적
    double dotProduct = vecA.dot(vecB);
    std::cout << "Dot product (vecA.dot(vecB)): " << dotProduct << std::endl;
    std::cout << "Dot product (vecA.transpose() * vecB): " << (vecA.transpose() * vecB)(0,0) << std::endl; // 결과는 1x1 행렬

    // 외적 (3D 벡터만 가능)
    Eigen::Vector3d crossProduct = vecA.cross(vecB);
    std::cout << "Cross product (vecA.cross(vecB)):\n" << crossProduct << std::endl;

    return 0;
}

4. Array 클래스: 요소별 연산

Array 클래스는 Matrix와 유사하지만, 모든 연산이 요소별(element-wise)로 수행됩니다. 이는 NumPy의 브로드캐스팅(broadcasting)과 유사한 동작을 제공합니다. +, * 연산이 각각 요소별 덧셈, 곱셈을 의미합니다.


#include <Eigen/Dense>
#include <iostream>

int main() {
    Eigen::Array<int, 2, 3> arrA = {{-1, -2, -1}, {6, 6, 6}};
    Eigen::Array<int, 2, 3> arrB = {{2, 2, 2}, {2, 3, 2}};

    std::cout << "Array A:\n" << arrA << std::endl;
    std::cout << "Array B:\n" << arrB << std::endl;

    // 요소별 덧셈 (+1)
    std::cout << "arrA + 1 (element-wise):\n" << arrA + 1 << std::endl;
    // 요소별 덧셈
    std::cout << "arrA + arrB (element-wise):\n" << arrA + arrB << std::endl;
    // 요소별 곱셈
    std::cout << "arrA * arrB (element-wise):\n" << arrA * arrB << std::endl;

    // 기타 요소별 함수
    std::cout << "Absolute value of arrA:\n" << arrA.abs() << std::endl;
    std::cout << "Square root of arrB:\n" << arrB.sqrt() << std::endl;
    std::cout << "Max element-wise (arrA.max(arrB)):\n" << arrA.max(arrB) << std::endl;

    return 0;
}

5. 블록(Block) 연산

행렬의 특정 부분 블록을 추출하거나 수정하는 기능을 제공합니다. block() 함수를 사용하며, 시작 행/열 인덱스와 블록의 크기를 지정합니다. 블록 연산은 좌측값(l-value)으로 사용될 수 있어, 블록에 직접 값을 할당할 수 있습니다.


#include <Eigen/Dense>
#include <iostream>

int main() {
    Eigen::MatrixXf mat(4, 4);
    mat << 1, 2, 3, 4,
           5, 6, 7, 8,
           9, 10, 11, 12,
           13, 14, 15, 16;

    std::cout << "Original Matrix:\n" << mat << std::endl;

    // (1,1) 위치에서 시작하는 2x2 블록 추출 (우측값)
    auto block_rvalue = mat.block<2, 2>(1, 1);
    std::cout << "Extracted 2x2 block at (1,1):\n" << block_rvalue << std::endl;

    // (1,1) 위치에서 시작하는 2x2 블록에 값 할당 (좌측값)
    mat.block(1, 1, 2, 2) = Eigen::Array22f::Constant(0.5f);
    std::cout << "Matrix after assigning to block:\n" << mat << std::endl;

    // 행 접근
    std::cout << "Second row (index 1):\n" << mat.row(1) << std::endl;

    // 왼쪽 상단 2x3 블록에 1을 더함
    mat.topLeftCorner(2, 3) = mat.topLeftCorner(2, 3).array() + 1.0f;
    std::cout << "Matrix after adding 1 to top-left corner:\n" << mat << std::endl;

    return 0;
}

6. 슬라이싱(Slicing)

슬라이싱은 블록 연산과 유사하지만, 더 유연한 인덱싱을 지원합니다. seq(), seqN(), lastN(), all 등의 인덱싱 객체를 사용하여 특정 행 또는 열의 시퀀스를 선택할 수 있습니다. 슬라이싱 결과 역시 좌측값 또는 우측값으로 사용 가능합니다.


#include <Eigen/Dense>
#include <iostream>
#include <vector>

int main() {
    Eigen::MatrixXf mat(4, 4);
    mat << 1, 2, 3, 4,
           5, 6, 7, 8,
           9, 10, 11, 12,
           13, 14, 15, 16;

    std::cout << "Original Matrix:\n" << mat << std::endl;

    // seq()를 사용한 행/열 선택
    std::cout << "Rows 1-2, Cols 0-1:\n" << mat(Eigen::seq(1, 2), Eigen::seq(0, 1)) << std::endl;

    // seqN() (시작, 개수, 간격) 및 last 사용
    std::cout << "Rows (0, 2, 4), Last Column:\n" << mat(Eigen::seqN(0, 4, 2), Eigen::last) << std::endl;

    // std::vector를 사용한 인덱싱
    std::vector<int> rowIndices = {1, 2, 3};
    int colIndices[] = {0, 1};
    std::cout << "Rows {1,2,3}, Cols {0,1}:\n" << mat(rowIndices, Eigen::Map<Eigen::VectorXi>(colIndices, 2))) << std::endl;

    // lastN() 및 all 사용
    std::cout << "Last 2 rows, all columns:\n" << mat(Eigen::lastN(2), Eigen::all) << std::endl;

    // 역순 슬라이싱
    std::cout << "Rows (last to 1), Cols {1,2}:\n" << mat(Eigen::seq(Eigen::last, 1, -1), Eigen::VectorXi({1, 2})) << std::endl;

    // 임의의 행/열 인덱스 배열 사용
    Eigen::Array<int, 3, 1> rowPermutation = {1, 3, 2};
    Eigen::Array<int, 1, 2> colPermutation = {2, 0};
    std::cout << "Permuted rows/cols:\n" << mat(rowPermutation, colPermutation) << std::endl;

    return 0;
}

7. 행렬 초기화: 스태킹(Stacking)

기존 행렬들을 조합하여 새로운 행렬을 생성할 수 있습니다. hjoin() (수평 결합) 및 vjoin() (수직 결합) 또는 << 연산자를 사용하여 여러 행렬을 쌓을 수 있습니다.


#include <Eigen/Dense>
#include <iostream>

int main() {
    Eigen::MatrixXi mat1(2, 2);
    mat1 << 1, 2,
            3, 4;
    Eigen::MatrixXi mat2(2, 3);
    mat2 << 1, 1, 1,
            2, 2, 2;

    std::cout << "Matrix 1:\n" << mat1 << std::endl;
    std::cout << "Matrix 2:\n" << mat2 << std::endl;

    // 수평 결합
    Eigen::MatrixXi mat3(2, 5);
    mat3 << mat1, mat2;
    std::cout << "Horizontally stacked (mat1, mat2):\n" << mat3 << std::endl;

    // 복잡한 블록 할당 및 결합
    Eigen::MatrixXi mat4(5, 5);
    mat4.row(0) << mat3.row(1); // mat3의 첫 번째 행을 mat4의 첫 번째 행에 복사
    mat4.row(1).head(4) << 4, 4, 4, 4; // mat4의 두 번째 행의 앞 4개 요소 설정
    mat4.row(1).tail(1) << 100; // mat4의 두 번째 행의 마지막 요소 설정
    mat4.block(2, 0, 3, 5) << mat3, mat3.row(0); // 블록에 mat3와 mat3의 첫 번째 행 결합하여 할당
    std::cout << "Complexly initialized mat4:\n" << mat4 << std::endl;

    return 0;
}

8. 기본 행렬 생성

Eigen은 자주 사용되는 행렬들을 쉽게 생성하는 멤버 함수들을 제공합니다.

Zero, Ones, Identity, Constant, Random

  • Zero(rows, cols): 모든 요소가 0인 행렬 생성
  • Ones(rows, cols): 모든 요소가 1인 행렬 생성
  • Identity(size): 단위 행렬 생성
  • Constant(rows, cols, value): 모든 요소가 지정된 값인 행렬 생성
  • Random(rows, cols): 요소들이 무작위 값 (일반적으로 -1에서 1 사이)으로 채워진 행렬 생성

#include <Eigen/Dense>
#include <iostream>

int main() {
    // Zero matrix
    Eigen::MatrixXi zeroMat = Eigen::MatrixXi::Zero(2, 3);
    std::cout << "Zero Matrix (2x3):\n" << zeroMat << std::endl;
    Eigen::Matrix3i zeroSquare = Eigen::Matrix3i::Zero(); // 3x3 제로 행렬
    std::cout << "Zero Matrix (3x3):\n" << zeroSquare << std::endl;

    // Constant matrix
    Eigen::MatrixXf constMat = Eigen::MatrixXf::Constant(2, 3, 6.6f);
    std::cout << "Constant Matrix (2x3, value 6.6):\n" << constMat << std::endl;

    // Random matrix (vector)
    Eigen::RowVectorXf randVec = Eigen::RowVectorXf::Random(3); // 1x3 랜덤 벡터
    std::cout << "Random Row Vector (1x3):\n" << randVec << std::endl;

    // Identity matrix
    Eigen::Matrix3i identityMat = Eigen::Matrix3i::Identity();
    std::cout << "Identity Matrix (3x3):\n" << identityMat << std::endl;

    // 블록별 상수 행렬 할당
    const int size = 6;
    Eigen::MatrixXd matBlock(size, size);

    // 방법 1: 블록 멤버 함수 사용
    matBlock.topLeftCorner(size / 2, size / 2).setZero();
    matBlock.topRightCorner(size / 2, size / 2).setIdentity();
    matBlock.bottomLeftCorner(size / 2, size / 2).setIdentity();
    matBlock.bottomRightCorner(size / 2, size / 2).setZero();
    std::cout << "Matrix initialized with setZero/setIdentity:\n" << matBlock << std::endl << std::endl;

    // 방법 2: << 연산자와 블록 결합 사용
    Eigen::MatrixXd matBlock2(size, size);
    matBlock2 << Eigen::MatrixXd::Zero(size / 2, size / 2), Eigen::MatrixXd::Identity(size / 2, size / 2),
                   Eigen::MatrixXd::Identity(size / 2, size / 2), Eigen::MatrixXd::Zero(size / 2, size / 2);
    std::cout << "Matrix initialized with << and blocks:\n" << matBlock2 << std::endl;

    return 0;
}

태그: Eigen C++ 선형대수 행렬 벡터

7월 24일 04:42에 게시됨