# DWT를 위한 C++ 구현 예제

이 장에서는 이산 웨이블릿 변환(DWT)을 C++로 구현하는 방법을 단계별로 설명한다. 이를 통해 독자는 웨이블릿 변환의 기초 원리를 이해할 뿐 아니라, 실제 코드로 이를 구현하는 능력을 갖추게 될 것이다. 구현 예제는 Haar 웨이블릿을 사용한 기본적인 1차원 DWT를 기반으로 하며, 필요한 경우 다차원 확장에 대한 지침도 제공한다.

#### DWT의 수학적 배경

이산 웨이블릿 변환은 주어진 신호 $\mathbf{x}$를 고주파 성분과 저주파 성분으로 분리하는 변환이다. 이를 위해 두 가지 필터가 사용된다: 저주파 통과 필터 $\mathbf{h}$와 고주파 통과 필터 $\mathbf{g}$. 신호 $\mathbf{x}$에 대한 DWT는 다음과 같이 정의된다:

$$
\mathbf{cA} = \sum\_{k} \mathbf{x}\[n] \cdot \mathbf{h}\[2k - n]
$$

$$
\mathbf{cD} = \sum\_{k} \mathbf{x}\[n] \cdot \mathbf{g}\[2k - n]
$$

여기서, $\mathbf{cA}$는 저주파 성분(approximation coefficients), $\mathbf{cD}$는 고주파 성분(detail coefficients)을 나타낸다. 이러한 변환은 반복적으로 적용되어 다중 해상도 분석(Multi-Resolution Analysis)을 수행할 수 있다.

#### C++ 구현을 위한 기본 구조

DWT를 구현하기 위해 다음과 같은 C++ 코드를 작성할 수 있다. 이 코드는 1차원 배열을 입력으로 받아 Haar 웨이블릿 필터를 적용하여 저주파와 고주파 성분을 계산한다.

**1. 필요한 헤더 파일 및 라이브러리 선언**

```cpp
#include <iostream>
#include <vector>
#include <cmath>
```

필요한 기본 헤더 파일을 포함한다. 이 예제에서는 `vector`를 사용하여 신호 데이터를 저장하며, `cmath`를 사용하여 수학적 계산을 수행한다.

**2. Haar 웨이블릿 필터 정의**

Haar 웨이블릿의 필터는 다음과 같은 단순한 형태를 가진다:

$$
\mathbf{h} = \left\[\frac{1}{\sqrt{2}}, \frac{1}{\sqrt{2}}\right], \quad \mathbf{g} = \left\[\frac{1}{\sqrt{2}}, -\frac{1}{\sqrt{2}}\right]
$$

이 필터들은 입력 신호의 저주파 성분과 고주파 성분을 계산하는 데 사용된다.

```cpp
std::vector<double> low_pass_filter = {1 / std::sqrt(2), 1 / std::sqrt(2)};
std::vector<double> high_pass_filter = {1 / std::sqrt(2), -1 / std::sqrt(2)};
```

**3. DWT 변환 함수 정의**

DWT를 적용하는 핵심 함수는 다음과 같다. 이 함수는 입력 신호와 필터를 받아서 필터링 결과를 반환한다.

```cpp
std::vector<double> apply_filter(const std::vector<double>& signal, const std::vector<double>& filter) {
    std::vector<double> result(signal.size() / 2);
    for (size_t i = 0; i < result.size(); ++i) {
        result[i] = signal[2 * i] * filter[0] + signal[2 * i + 1] * filter[1];
    }
    return result;
}
```

이 함수는 입력 신호의 짝수 인덱스와 홀수 인덱스를 필터링하여 저주파 및 고주파 성분을 계산한다.

**4. 전체 DWT 과정**

```cpp
void perform_dwt(const std::vector<double>& signal) {
    std::vector<double> approximation = apply_filter(signal, low_pass_filter);
    std::vector<double> detail = apply_filter(signal, high_pass_filter);

    std::cout << "Approximation Coefficients: ";
    for (const auto& value : approximation) {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    std::cout << "Detail Coefficients: ";
    for (const auto& value : detail) {
        std::cout << value << " ";
    }
    std::cout << std::endl;
}
```

이 코드는 입력된 신호를 저주파 필터와 고주파 필터에 각각 적용하여 두 가지 결과를 출력한다.

#### 5. 사용 예제

이제 구현한 DWT 함수가 실제로 어떻게 작동하는지 예제를 통해 확인해 보자. 예를 들어, 다음과 같은 신호가 있다고 가정하자:

$$
\mathbf{x} = \[4, 6, 10, 12, 14, 8, 6, 4]
$$

C++ 코드에서 이 신호를 입력으로 하여 DWT를 수행하는 방법은 다음과 같다:

```cpp
int main() {
    std::vector<double> signal = {4, 6, 10, 12, 14, 8, 6, 4};
    
    std::cout << "Original Signal: ";
    for (const auto& value : signal) {
        std::cout << value << " ";
    }
    std::cout << std::endl;
    
    perform_dwt(signal);

    return 0;
}
```

이 프로그램을 실행하면 입력 신호의 저주파 및 고주파 성분이 다음과 같이 출력된다:

```
Original Signal: 4 6 10 12 14 8 6 4
Approximation Coefficients: 7.07107 15.5563 15.5563 7.07107
Detail Coefficients: -1.41421 -1.41421 4.24264 1.41421
```

#### 6. 단계별 설명

이 코드는 다음과 같은 절차로 작동한다:

1. **신호 입력**: `signal` 벡터를 통해 신호 데이터를 입력받는다.
2. **저주파 및 고주파 필터링**: 각각의 샘플 쌍에 대해 저주파 필터와 고주파 필터를 적용하여 `apply_filter` 함수를 사용해 필터링을 수행한다.
3. **결과 출력**: 필터링된 결과를 `approximation`과 `detail` 벡터에 저장하고, 이를 화면에 출력한다.

이러한 방식으로, DWT를 사용하면 신호의 저주파 및 고주파 성분을 쉽게 분리할 수 있다. 이 과정은 다중 해상도 분석(Multi-Resolution Analysis, MRA)의 기본 단위가 된다.

#### 7. 확장: 다중 레벨 DWT

위의 예제는 신호의 1단계(레벨 1) 변환만을 수행하였다. 하지만 다중 해상도 분석에서는 반복적으로 저주파 성분에 대해 변환을 적용하여 다중 레벨 분석을 수행할 수 있다. 이를 위해 저주파 성분에 대해 추가적으로 DWT를 수행하는 재귀 함수 형태로 확장할 수 있다.

```cpp
void multi_level_dwt(std::vector<double> signal, int levels) {
    for (int i = 0; i < levels; ++i) {
        std::cout << "Level " << i + 1 << ":" << std::endl;
        perform_dwt(signal);
        signal = apply_filter(signal, low_pass_filter);  // 다음 레벨은 저주파 성분으로 다시 변환
    }
}
```

이 함수는 입력 신호에 대해 지정된 레벨 수만큼 반복적으로 DWT를 수행하여, 각 단계에서 저주파 성분을 다시 변환한다. 예를 들어, 3단계 변환을 수행하려면 다음과 같이 코드를 수정할 수 있다:

```cpp
int main() {
    std::vector<double> signal = {4, 6, 10, 12, 14, 8, 6, 4};
    int levels = 3;
    multi_level_dwt(signal, levels);
    return 0;
}
```

이제 프로그램은 3단계의 DWT 결과를 연속적으로 출력하게 되며, 각 단계에서 점차적으로 신호의 세부 정보를 추출하고 분석할 수 있다.

#### 8. 메모리 효율적인 구현

위에서 설명한 DWT 구현은 직관적이지만, 신호 길이가 커질수록 메모리 사용량이 증가할 수 있다. 이를 방지하기 위해, 원본 신호의 일부만을 사용하여 계산을 수행하고 나머지 데이터를 덮어쓰는 방식으로 메모리 효율적인 구현이 가능하다.

**메모리 효율적 DWT 함수**

다음 코드는 원본 신호의 메모리를 재활용하여, 추가적인 메모리 할당 없이 DWT를 수행하는 방법을 보여준다:

```cpp
void in_place_dwt(std::vector<double>& signal) {
    int n = signal.size();
    std::vector<double> temp(n);

    while (n > 1) {
        n /= 2;
        for (int i = 0; i < n; ++i) {
            temp[i] = (signal[2 * i] * low_pass_filter[0] + signal[2 * i + 1] * low_pass_filter[1]);
            temp[i + n] = (signal[2 * i] * high_pass_filter[0] + signal[2 * i + 1] * high_pass_filter[1]);
        }

        for (int i = 0; i < 2 * n; ++i) {
            signal[i] = temp[i];
        }
    }
}
```

**메모리 재사용 방식의 작동 원리**

이 함수는 다음과 같은 방법으로 작동한다:

1. **임시 벡터 생성**: 신호 길이와 동일한 크기의 임시 벡터 `temp`를 만든다.
2. **신호의 절반 크기만 변환**: 저주파 및 고주파 성분을 각각 신호의 절반에 저장하여, 전체 신호 크기를 절반으로 줄인다.
3. **신호 덮어쓰기**: 계산된 결과를 원본 신호에 다시 저장하여 메모리를 절약한다.
4. **반복 감소**: 신호의 크기를 줄여가며 재귀적으로 변환을 수행한다.

이 구현은 기존 방식에 비해 메모리 사용량이 절반 수준으로 줄어들며, 대용량 신호 처리에 효과적이다.

#### 9. 역변환(Inverse DWT) 구현

DWT의 또 다른 중요한 기능은 역변환(Inverse Discrete Wavelet Transform, IDWT)이다. 신호를 원래 상태로 복원하기 위해 IDWT는 필터 계수를 사용하여 역변환을 수행한다.

$$
\mathbf{x}\[n] = \sum\_{k} \mathbf{cA}\[k] \cdot \mathbf{h}\[n - 2k] + \mathbf{cD}\[k] \cdot \mathbf{g}\[n - 2k]
$$

위 식은 저주파 성분과 고주파 성분을 결합하여 원래 신호를 복원하는 과정이다.

**C++ 역변환 함수**

```cpp
std::vector<double> inverse_dwt(const std::vector<double>& approximation, const std::vector<double>& detail) {
    size_t n = approximation.size();
    std::vector<double> signal(2 * n);

    for (size_t i = 0; i < n; ++i) {
        signal[2 * i] = (approximation[i] * low_pass_filter[0] + detail[i] * high_pass_filter[0]);
        signal[2 * i + 1] = (approximation[i] * low_pass_filter[1] + detail[i] * high_pass_filter[1]);
    }

    return signal;
}
```

#### 10. 역변환 사용 예제

```cpp
int main() {
    std::vector<double> approximation = {7.07107, 15.5563, 15.5563, 7.07107};
    std::vector<double> detail = {-1.41421, -1.41421, 4.24264, 1.41421};

    std::vector<double> reconstructed_signal = inverse_dwt(approximation, detail);
    
    std::cout << "Reconstructed Signal: ";
    for (const auto& value : reconstructed_signal) {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    return 0;
}
```

이 코드는 DWT로부터 얻은 저주파 및 고주파 성분을 사용하여 원래의 신호를 복원하며, 예상 결과는 다음과 같다:

```
Reconstructed Signal: 4 6 10 12 14 8 6 4
```

이로써, DWT 및 역변환을 통해 신호를 분해하고 복원하는 전체 과정이 구현되었다.
