# C++에서의 연속 웨이블릿 변환 예제

#### 연속 웨이블릿 변환 개요

연속 웨이블릿 변환(Continuous Wavelet Transform, CWT)은 입력 신호를 다양한 주파수와 시간 구간으로 분해하여 분석하는 방법이다. 이 변환은 다음과 같은 일반 수식을 통해 정의된다:

$$
W\_{\psi}(a, b) = \int\_{-\infty}^{\infty} f(t) \cdot \psi^{\*}\left( \frac{t - b}{a} \right) , dt
$$

여기서,

* $f(t)$는 시간 도메인에서의 원본 신호이다.
* $\psi(t)$는 모 웨이블릿(mother wavelet) 함수이다.
* $a$는 스케일(scale) 매개변수로, 신호의 주파수 정보에 영향을 미친다.
* $b$는 시간 이동(translation) 매개변수이다.
* $\psi^{\*}$는 모 웨이블릿의 복소 켤레이다.

#### C++에서의 기본 구현 구조

C++에서 CWT를 구현하려면 위의 수식을 기반으로 연속적인 스케일링과 이동 작업을 효율적으로 수행할 수 있도록 설계해야 한다. 이때 중요한 점은 연속 웨이블릿 변환이 연속적인 스케일 값과 이동 값에 대해 무한히 많은 웨이블릿 계수를 계산하게 되어 메모리와 연산량이 상당히 커진다는 점이다.

#### 주요 클래스와 함수 설계

1. **Wavelet 클래스**: 모 웨이블릿 함수를 정의하고, 필요한 연산을 지원하는 클래스이다.
2. **CWT 클래스**: 주어진 신호에 대해 특정 스케일과 이동 값을 기반으로 변환을 수행하는 클래스로, 내부적으로 Wavelet 클래스와 신호 데이터 처리를 담당한다.

**Wavelet 클래스의 기본 정의**

모 웨이블릿 함수로 주로 사용되는 **모르레(Morlet)** 웨이블릿을 예제로 들 수 있다. 모르레 웨이블릿은 복소수 기반의 함수로, 다음과 같은 식으로 정의된다:

$$
\psi(t) = \pi^{-\frac{1}{4}} e^{i \omega\_0 t} e^{-\frac{t^2}{2}}
$$

여기서, $\omega\_0$는 중심 주파수(center frequency)이다. 이 식을 기반으로 `Wavelet` 클래스의 코드를 작성할 수 있다.

```cpp
class Wavelet {
public:
    Wavelet(double omega0) : omega0(omega0) {}

    std::complex<double> morlet(double t) const {
        return std::pow(M_PI, -0.25) * std::exp(std::complex<double>(0, omega0 * t)) * std::exp(-0.5 * t * t);
    }

private:
    double omega0; // 중심 주파수
};
```

#### CWT 클래스에서의 변환 함수 구현

`CWT` 클래스는 `Wavelet` 클래스를 이용해 특정 스케일과 이동에 대해 웨이블릿 변환을 수행한다. 변환의 주요 수식은 스케일 $a$와 시간 이동 $b$에 대한 반복 처리를 포함해야 하며, 이때 입력 신호 $f(t)$와 웨이블릿 $\psi(t)$ 간의 컨볼루션을 수행한다.

다음은 `CWT` 클래스의 기본 구조이다.

```cpp
class CWT {
public:
    CWT(const std::vector<double>& signal, double omega0) 
        : signal(signal), wavelet(omega0) {}

    std::vector<std::complex<double>> transform(double scale, double translation) {
        std::vector<std::complex<double>> coefficients;
        for (size_t i = 0; i < signal.size(); ++i) {
            double t = static_cast<double>(i);
            std::complex<double> result = 0.0;
            for (size_t j = 0; j < signal.size(); ++j) {
                double tj = static_cast<double>(j);
                double scaledT = (tj - translation) / scale;
                result += signal[j] * wavelet.morlet(scaledT);
            }
            coefficients.push_back(result);
        }
        return coefficients;
    }

private:
    std::vector<double> signal;
    Wavelet wavelet;
};
```

위 코드에서 `transform` 함수는 주어진 스케일과 이동 값에 대해 변환을 수행하며, 결과는 웨이블릿 계수의 벡터로 반환된다.

#### 주요 함수 설명

* `transform(double scale, double translation)`: 입력된 스케일과 이동 값을 기반으로 웨이블릿 변환을 수행하여 신호의 특정 주파수와 시간 구간에 대한 정보를 계산한다.
* `morlet(double t)`: Wavelet 클래스에서 정의한 모르레 웨이블릿 함수로, CWT에서 주파수와 시간 정보를 얻기 위해 사용된다.

#### 스케일과 이동 매개변수의 활용

연속 웨이블릿 변환에서 주파수 정보를 다양한 해상도로 분석하기 위해 **스케일**과 **이동** 매개변수를 적절히 설정하는 것이 중요하다. CWT의 구현에서는 여러 스케일과 이동 값에 대한 반복 연산을 수행하므로, 스케일 $a$와 이동 $b$의 범위와 간격을 설정하는 방식에 따라 결과의 해상도와 연산 비용이 크게 달라질 수 있다.

이를 효율적으로 처리하기 위해서:

* 스케일 값 $a$는 로그 스케일에서 일정한 간격으로 설정하는 것이 일반적이다. 작은 스케일에서는 고주파 성분이, 큰 스케일에서는 저주파 성분이 더 잘 나타나기 때문이다.
* 이동 값 $b$는 시간 축에 따라 일정한 간격으로 설정하여 변환이 전체 신호에 걸쳐 균등하게 수행되도록 한다.

**스케일과 이동을 위한 코드 추가**

스케일과 이동을 자동으로 설정하기 위해 `CWT` 클래스에 스케일과 이동 매개변수를 정의하고, 이를 반복하여 CWT를 수행하는 함수를 추가할 수 있다.

```cpp
class CWT {
public:
    CWT(const std::vector<double>& signal, double omega0) 
        : signal(signal), wavelet(omega0) {}

    std::vector<std::vector<std::complex<double>>> fullTransform(double minScale, double maxScale, double scaleStep, double translationStep) {
        std::vector<std::vector<std::complex<double>>> allCoefficients;
        
        for (double scale = minScale; scale <= maxScale; scale += scaleStep) {
            std::vector<std::complex<double>> scaleCoefficients;
            for (double translation = 0; translation < signal.size(); translation += translationStep) {
                scaleCoefficients.push_back(transformSingleScale(scale, translation));
            }
            allCoefficients.push_back(scaleCoefficients);
        }
        return allCoefficients;
    }

private:
    std::vector<double> signal;
    Wavelet wavelet;

    std::complex<double> transformSingleScale(double scale, double translation) {
        std::complex<double> result = 0.0;
        for (size_t j = 0; j < signal.size(); ++j) {
            double tj = static_cast<double>(j);
            double scaledT = (tj - translation) / scale;
            result += signal[j] * wavelet.morlet(scaledT);
        }
        return result;
    }
};
```

위 코드에서는 다음과 같은 함수를 정의하였다:

* `fullTransform(double minScale, double maxScale, double scaleStep, double translationStep)`: 사용자에게 스케일과 이동 범위 및 간격을 설정할 수 있도록 하여, 연속 웨이블릿 변환을 모든 스케일과 이동 값에 대해 자동으로 수행한다.
* `transformSingleScale(double scale, double translation)`: 주어진 스케일과 이동 값에 대해 웨이블릿 변환을 수행하는 보조 함수로, `fullTransform` 내에서 반복 호출된다.

#### 결과 시각화를 위한 데이터 구조

CWT의 결과는 스케일과 시간 축에 따른 웨이블릿 계수로 구성되며, 이를 행렬 형식으로 표현할 수 있다. 각 행은 특정 스케일에 대한 계수의 집합이며, 열은 시간 축을 따라 이동된 변환 결과이다. 이 데이터는 이미지로 시각화하거나 주파수 스펙트럼 형태로 변환하여 분석할 수 있다.

**결과 시각화를 위한 다이어그램 예시**

다이어그램을 사용하여 결과 데이터의 흐름과 구조를 시각적으로 나타낼 수 있다. 다음은 웨이블릿 변환 과정의 흐름을 보여주는 다이어그램이다.

{% @mermaid/diagram content="flowchart TD
A\[입력 신호] --> B\[스케일 반복]
B --> C\[각 스케일에 대해 이동 반복]
C --> D\[Wavelet 계수 계산]
D --> E\[계수 저장 및 출력]
E --> F\[웨이블릿 계수 행렬]" %}

#### CWT 결과를 이용한 시간-주파수 분석

CWT의 결과는 시간과 주파수 정보를 동시에 포함하므로, 이를 통해 신호의 특정 시간 구간에서의 주파수 성분을 쉽게 파악할 수 있다. 특히 비정상 신호(non-stationary signals)에서 CWT는 시간과 주파수 해상도를 모두 제공하므로, 신호의 변화 패턴이나 특정 주파수 대역의 강도 변화를 탐지하는 데 유용하다.

**시간-주파수 분석을 위한 추가 함수**

신호의 특정 시간 구간에서 특정 주파수 대역의 변화를 분석하기 위해, `CWT` 클래스에 특정 스케일 대역의 평균값이나 최대값을 계산하는 함수를 추가할 수 있다.

```cpp
std::vector<double> analyzeFrequencyBand(double minScale, double maxScale) {
    std::vector<double> frequencyAnalysis(signal.size(), 0.0);
    for (const auto& scaleCoefficients : fullTransform(minScale, maxScale, 1.0, 1.0)) {
        for (size_t i = 0; i < scaleCoefficients.size(); ++i) {
            frequencyAnalysis[i] += std::abs(scaleCoefficients[i]);
        }
    }
    return frequencyAnalysis;
}
```

이 함수는 특정 스케일 범위 내에서의 주파수 성분 변화를 분석하여, 각 시간 구간의 주파수 대역 강도를 계산한다. 이를 통해 특정 시간 구간에서의 에너지 변화나 특정 주파수 대역의 활성도를 파악할 수 있다.
