вівторок, 25 грудня 2018 р.

Хвильки (вейвлети) Хаара в 1D

Хвильки (вейвлети) - це математичне приладдя для ієрархіного розкладення зображень. У цій статті я розповім про найпростішу форму хвильок - базис Хаара в одновимірному просторі.

Щоб отримати базове розуміння як працюють хвильки розглянемо простий приклад. Припустимо, що ми маємо одновимірне зображення з такими значеннями:

$$[9\ 7\ 3\ 5]$$

Ми можемо зберегти це зображення залишивши лише середні значення для кожної двійки пікселів:

$$[8\ 4]$$

Очевидно, що частина інформації загубилась. Щоб отримати початкові чотири пікселі нам також треба зберегати коефіцієнти деталізації. Тут такими коефіцієнтами є $1$ для першої пари і $-1$ для другої пари значень.

РоздільністьСереднє значенняКоеф. деталізації
$4$$[\ 9\ 7\ 3\ 5\ ]$
$2$$[\ 8\ 4\ ]$$[\ 1\ {-1}\ ]$
$1$$[\ 6\ ]$$[\ 2\ ]$
Отже, для одновимірного базиса Хаара хвильковие перетворення нашого зображення виглядає так: $$[\ 6\ 2\ 1\ -1\ ]$$ Зауважте, що інформація не була ані втрачена, ані набута. Початкове зображення мало 4 коефіцієнти і перетворене також. Початкове зображення можна легко відновити з його перетворення.

Базис хвилькового перетворення

Розглянемо дві ортогональні функції:
  1. Маштабувальна - $\psi(x)$.
  2. Хвилькова (материнська) - $\phi(x)$.
Які задаються так: \begin{equation} \phi(s) := \begin{cases} 1 & 0 \le x < 1\\ 0 & \mbox{інакше.} \end{cases} \end{equation} \begin{equation} \psi(s) := \begin{cases} 1 & 0 \le x < \frac{1}{2}\\ -1 & \frac{1}{2} \le x < 1\\ 0 & \mbox{інакше.} \end{cases} \end{equation} Фактично, $[\ 8\ 4\ 1\ -1\ ]$ кодує $[\ 9\ 7\ 3\ 5\ ]$ у такому вигляді: \begin{equation} 8\phi(x) + \psi(x) + 4\phi(x-1) - \psi(x-1).\label{dec9735pre} \end{equation} УВАГА! Зараз ми зробимо невеличкий виверт. Ми розглядатимемо зображення як кусково-неперервні функції на проміжку $[0, 1)$. Наприклад, одновимірне зображення $[\ 9\ 7\ 3\ 5\ ]$ виглядатиме так:
Отже, тепер ми вже не можемо сказати, що \eqref{dec9735pre} предсталяє $[\ 9\ 7\ 3\ 5\ ]$. Тепер нам потрібно відмаштабувати наші функції. У загальному випдаку для зображення розміру $2^n$ на першому кроці треба використовувати $\phi_n^i(x) = \phi(2^nx-i)$ і $\psi_n^i(x) = \psi(2^nx-i)$.

Базис Хаара має чудову властивість ортогональності, тобто всі базисні функції - $\phi_0^0, \psi_0^0, \phi_1^1, \psi_1^1, \dots$ ортогональні. Нам також хотілось би нормалізувати базис.

Означення
Базисна функція $u(x)$ нормалізована, якщо $$ \langle u|u\rangle = \int_0^1 u(x)u(x)dx = 1.$$
Базис Хаара можна нормалізувати підправивши наявне визначення базисних функцій так: \begin{align} \phi_i^j(x):=2^{j/2}\phi(2^jx-i),\\ \psi_i^j(x):=2^{j/2}\psi(2^jx-i). \end{align} В такому базисі ненормалізовані коефіцієнти $[\ 6\ 2\ 1\ -1\ ]$ стають нормалізованими $$[\quad 6\quad 2\quad \frac{1}{\sqrt{2}}\quad -\frac{1}{\sqrt{2}}\quad ].$$ Ніж очислювати ненормалізовані коейіцієнти і потім нормалізовувати їх, можна включити нормалізацію в алгоритм.

Реалізація

Наступний код створює масив 512 символів завдовжки і заповнює його значеннями функції Веєрштраса на проміжку $[-1, 1]$:
#include <algorithm>
#include <cassert>
#include <vector>
#include <fstream>

double Weierstrass(double x) {
    const int summandsCount = 100;
    const double a = 0.7;
    const double b = 11;
    double aRaised = 1;
    double bRaised = 1;
    double res = 0;
    for (int i = 0; i < summandsCount; ++i) {
        aRaised *= a;
        bRaised *= b;
        res += aRaised * cos(bRaised * atan(1) * 4 * x);
    }
    return res;
}

const auto kLevelsCount = 9;
const auto kN = 1 << 9;
const auto kSqrt2 = sqrt(2);

void save(const std::string& name, const std::vector<double>& data) {
    std::ofstream out(name);
    for (int i = 0; i < (int)data.size(); ++i) out << data[i] << std::endl;
}

void DecompositionStep(std::vector<double>& data, std::vector<double> storage, int size) {
    for (int i = 0; i < size; i = i + 2) {
        storage[i / 2] = (data[i] + data[i + 1]) / kSqrt2;
        storage[size / 2 + i / 2] = (data[i] - data[i + 1]) / kSqrt2;
    }
    std::copy_n(storage.cbegin(), size, data.begin());
}

void Decomposition(std::vector<double>& data, std::vector<double>& storage) {
    assert(data.size() == storage.size() && "At the end lod levels take 1 position and differences (data.size() - 1) positions");
    std::transform(data.cbegin(), data.cend(), data.begin(), [s = data.size()](double y) { return y / sqrt(s); });
    auto currSize = (int)data.size();
    while (currSize > 1) {
        DecompositionStep(data, storage, currSize);
        currSize /= 2;
    }
}

int main()
{
    std::vector<double> data(kN);
    const auto interval = std::make_pair(-0.5, 0.5);
    const auto length = interval.second - interval.first;
    for (int i = 0; i < kN; ++i) {
        double x = interval.first + length * i / (kN - 1);
        data[i] = Weierstrass(x);
    }
    
    std::vector<double> storage(data.size());
    Decomposition(data, storage);
    save("compressed.dat", data);
    return 0;
}
А цей код MATLAB'у зчитує утворений файл і відновлює початкові дані починаючи з найгрубішого рівня. По мірі відновлення він записує зображення кожного рівня в файл анімації.
h = figure;
axis tight manual
filename = 'haarWeierstrass.gif';
hold on

xStart = -1;
xEnd = 1;

load 'C:\Yola\VC\Test17\Test17\compressed.dat' compressed -ascii
L = length(compressed);
lodLevelsCount = log(L)/log(2) + 1;
storage = zeros(size(compressed));
for i = 1:lodLevelsCount
    lodStart = 1;
    detailStart = 2^(i-1) + 1;
    localY = compressed(1:detailStart - 1);
    x = linspace(xStart, xEnd, length(localY));
    plot(x, localY);
    title(['Рівень деталізації ' num2str(i-1) ', ' num2str(2^(i-1)) ' точок'])
    drawnow
    frame = getframe(h);
    im = frame2im(frame); 
    [imind,cm] = rgb2ind(im,256); 
    if i == 1 
        imwrite(imind,cm,filename,'gif', 'Loopcount',inf); 
    else 
        imwrite(imind,cm,filename,'gif','WriteMode','append'); 
    end 
    
    if (i < lodLevelsCount)
        % відновлення наступного рівня
        m = 2^((i-1)/2);
        for j = 1:detailStart - 1
            storage(2*j - 1) = compressed(j) + m * compressed(detailStart - 1 + j);
            storage(2*j) = compressed(j) - m * compressed(detailStart - 1 + j);
        end
        compressed(1:2*(detailStart-1)) = storage(1:2*(detailStart-1));
        pause(1);
        clf();
    end
end

Немає коментарів:

Дописати коментар