Fast cosine transformを理解する

# はじめに
Fast cosine transformを説明します。B.G. Lee のPaperを参照します。
> B. G. Lee A new algorithm to compute the discrete cosine transform

# inverse DCT II
定義式は次の通りです。簡略化のため$C_{2N}^{(2n+1)k} = \cos \frac{\pi}{2N} (2n+1)k$と表記します。

```math
\begin{eqnarray}
x_n &=& \sum_{k=0}^{N-1} X_k C_{2N}^{(2n+1)k} \\
\end{eqnarray}
```

kについて偶数と奇数に分けます。

```math
\begin{eqnarray}
x_n &=&
\left\{
\begin{array}{ll}
 g_n &=& \sum_{k=0}^{N/2-1} X_{2k} C_{2N}^{(2n+1)2k} \\
 h'_n &=& \sum_{k=0}^{N/2-1} X_{2k+1} C_{2N}^{(2n+1)(2k+1)} \\
\end{array}
\right. \\
\end{eqnarray}
```

nについて前半 N/2 と 後半 N/2 に分けます。
次が成り立ちます。n=0 ... N/2-1 です。

```math
\begin{eqnarray}
x_n &=& g_n + h'_n \\
x_{N-1-n} &=& g_n - h'_n \\
\end{eqnarray}
```

上記を示します。前半は自明です。後半 N/2 が成り立つことを説明します。

```math
\begin{eqnarray}
x_{N-1-n} &=& \sum_{k=0}^{N/2-1} X_k C_{2N}^{(2(N-1-n)+1)k} \\
C_{2N}^{(2(N-1-n)+1)k} &=& C_{2N}^{2Nk-(2n+1)k} = \cos \left( k \pi - \frac{\pi (2n+1)k}{2N} \right) \\
\end{eqnarray}
```

次の関係式から後半が成り立つことが分かります。

```math
\begin{eqnarray}
\cos \left(n \pi - \theta \right) = 
\left\{
\begin{array}{ll}
 + \cos \theta & (n = even) \\
 - \cos \theta & (n = odd) \\
\end{array}
\right. \\
\end{eqnarray}
```

次に再帰的に処理を行うために $g_n,h'_n$ を $x_n$と同じ式の形にします。

$x_n$は次の形でした。

```math
\begin{eqnarray}
x_n &=& \sum_{k=0}^{N-1} X_{k} C_{2N}^{(2n+1)k} \\
\end{eqnarray}
```

$g_n$について考えます。

```math
\begin{eqnarray}
g_n &=& \sum_{k=0}^{N/2-1} X_{2k} C_{2N}^{(2n+1)2k} \\
    &=& \sum_{k=0}^{N/2-1} X_{2k} C_{2(N/2)}^{(2n+1)k} \\
G_k &=& X_{2k} \\
g_n &=& \sum_{k=0}^{N/2-1} G_k C_{2(N/2)}^{(2n+1)k} \\
\end{eqnarray}
```

$h'_n$について考えます。

```math
\begin{eqnarray}
h'_n &=& \sum_{k=0}^{N/2-1} X_{2k+1} C_{2N}^{(2n+1)(2k+1)} \\
\end{eqnarray}
```

次の公式を使うことで、

```math
\begin{eqnarray}
2\cos \alpha \cos \beta = \cos (\alpha + \beta) + \cos (\alpha - \beta)
\end{eqnarray}
```

次のように式変形できます。

```math
\begin{eqnarray}
2 C_{2N}^{(2n+1)} C_{2N}^{(2n+1)(2k+1)} &=& C_{2N}^{(2n+1)2k} + C_{2N}^{(2n+1)2(k+1)} \\
\end{eqnarray}
```

上記を$h'_n$に適用します。

```math
\begin{eqnarray}
2 C_{2N}^{(2n+1)} h'_n &=& \sum_{k=0}^{N/2-1} X_{2k+1} \left( C_{2N}^{(2n+1)2k} + C_{2N}^{(2n+1)2(k+1)} \right) \\
\end{eqnarray}
```

上記右辺2項を次のように変形します。
右肩の$2(k+1)$ を $2k$ にするために、2つ前の$X$を使います。

```math
\begin{eqnarray}
\sum_{k=0}^{N/2-1} X_{2k+1} C_{2N}^{(2n+1)2(k+1)} &=& \sum_{k=0}^{N/2-1} X_{2k-1} C_{2N}^{(2n+1)2k} \\
\end{eqnarray}
```

```math
\begin{eqnarray}
X_{-1} &=& 0 \\
C_{2N}^{(2n+1)2(N/2)} &=& C_{2}^{(2n+1)} = 0 \\
\end{eqnarray}
```

上記を$h'_n$に適用します。

```math
\begin{eqnarray}
2 C_{2N}^{(2n+1)} h'_n &=& \sum_{k=0}^{N/2-1} \left( X_{2k+1} + X_{2k-1} \right) C_{2N}^{(2n+1)2k} \\
h'_n &=& \frac{1}{2 C_{2N}^{(2n+1)}} \sum_{k=0}^{N/2-1} H_k C_{2(N/2)}^{(2n+1)k} = \frac{1}{2 C_{2N}^{(2n+1)}} h_n \\
h_n &=& \sum_{k=0}^{N/2-1} H_k C_{2(N/2)}^{(2n+1)k} \\
H_n &=& X_{2k+1} + X_{2k-1} \\
\end{eqnarray}
```

式をまとめます。$x_n$は次の通りです。

```math
\begin{eqnarray}
x_n &=& \sum_{k=0}^{N-1} X_k C_{2N}^{(2n+1)k} \\
n &=& 0,..., N-1 \\
\end{eqnarray}
```

$g_n,h_n$は次の通りです。

```math
\begin{eqnarray}
g_n &=& \sum_{k=0}^{N/2-1} G_k C_{2(N/2)}^{(2n+1)k} \\
h_n &=& \sum_{k=0}^{N/2-1} H_k C_{2(N/2)}^{(2n+1)k} \\
n &=& 0,..., N/2-1 \\
\end{eqnarray}
```

$G_k,H_k$を$X_k$で表します。

```math
\begin{eqnarray}
G_k &=& X_{2k} \\
H_k &=& X_{2k+1} + X_{2k-1} \\
\end{eqnarray}
```

$x_k$を$g_k,h_k$で表します。

```math
\begin{eqnarray}
x_n &=& g_n + \frac{1}{2 C_{2N}^{(2n+1)}} h_n \\
x_{N-1-n} &=& g_n - \frac{1}{2 C_{2N}^{(2n+1)}} h_n \\
\end{eqnarray}
```

次にフロー図を示します。N=8の場合です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84205/e5a55842-ed21-2307-e24a-0c51dc34ed9b.png)

# forward DCT II

定義式は次の通りです。

```math
\begin{eqnarray}
X_k &=& \sum_{n=0}^{N-1} x_n C_{2N}^{(2n+1)k} \\
\end{eqnarray}
```

nについて前半 N/2 と 後半 N/2 に分けます。
次が成り立ちます。n=0 ... N/2-1 です。

```math
\begin{eqnarray}
X_k &=& \sum_{n=0}^{N/2-1} x_n C_{2N}^{(2n+1)k} + \sum_{n=0}^{N/2-1} x_{N-1-n} C_{2N}^{(2(N-1-n)+1)k} \\
\end{eqnarray}
```

kについて偶数と奇数に分けます。

```math
\begin{eqnarray}
X_{2k} 
&=& \sum_{n=0}^{N/2-1} x_n C_{2N}^{(2n+1)2k} + \sum_{n=0}^{N/2-1} x_{N-1-n} C_{2N}^{(2(N-1-n)+1)2k} \\
&=& \sum_{n=0}^{N/2-1} x_n C_{2N/2}^{(2n+1)k} + \sum_{n=0}^{N/2-1} x_{N-1-n} C_{2N/2}^{(2n+1)k} \\
&=& \sum_{n=0}^{N/2-1} (x_n - x_{N-1-n}) C_{2N/2}^{(2n+1)k} = \sum_{n=0}^{N/2-1} g_n C_{2N/2}^{(2n+1)k} = G_k \\
X_{2k+1} 
&=& \sum_{n=0}^{N/2-1} x_n C_{2N}^{(2n+1)(2k+1)} + \sum_{n=0}^{N/2-1} x_{N-1-n} C_{2N}^{(2(N-1-n)+1)(2k+1)} \\
&=& \sum_{n=0}^{N/2-1} x_n C_{2N}^{(2n+1)(2k+1)} - \sum_{n=0}^{N/2-1} x_{N-1-n} C_{2N}^{(2n+1)(2k+1)} \\
&=& \sum_{n=0}^{N/2-1} (x_n - x_{N-1-n}) C_{2N}^{(2n+1)(2k+1)} \\
&=& \sum_{n=0}^{N/2-1} \frac{(x_n - x_{N-1-n})}{2 C_{2N}^{(2n+1)}} \left (C_{2N/2}^{(2n+1)(k+1)} + C_{2N/2}^{(2n+1)k} \right) \\
&=& \sum_{n=0}^{N/2-1} h_n \left (C_{2N/2}^{(2n+1)(k+1)} + C_{2N/2}^{(2n+1)k} \right) = H_{k+1} + H_k \\
\end{eqnarray}
```

式をまとめます。$X_n$は次の通りです。

```math
\begin{eqnarray}
X_k &=& \sum_{n=0}^{N-1} x_n C_{2N}^{(2n+1)k} \\
k &=& 0, ..., N-1 \\
\end{eqnarray}
```

$G_n, H_n$は次の通りです。

```math
\begin{eqnarray}
G_k &=& \sum_{n=0}^{N/2-1} g_n C_{2N/2}^{(2n+1)k} \\
H_k &=& \sum_{n=0}^{N/2-1} h_n C_{2N/2}^{(2n+1)k} \\
k &=& 0, ..., N/2-1 \\
\end{eqnarray}
```

$g_n, h_n$を$x_n$で表します。

```math
\begin{eqnarray}
g_n &=& x_n + x_{N-1-n} \\
h_n &=& \frac{x_n - x_{N-1-n}}{2 C^{2n+1}_{2N}} \\
\end{eqnarray}
```

$X_k$を$G_k,H_k$で表します。

```math
\begin{eqnarray}
X_{2k} &=& G_k \\
X_{2k+1} &=& H_k + H_{k+1} \\
\end{eqnarray}
```

次にフロー図を示します。N=8の場合です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84205/76b0f9a1-3d07-3631-49e1-8ae70aae38e0.png)


# DCT IV
DCT IVはDCT IIに変換できます。次のようにして変換します。

```math
\begin{eqnarray}
x_n
&=& \sum_{k=0}^{N-1}{X_k C_{4N}^{(2n+1)(2k+1)}} \\
&=& \frac{1}{2 C_{4N}^{2n+1}} \sum_{k=0}^{N-1}{X_k \left( C_{2N}^{(2n+1)k} + C_{2N}^{(2n+1)(k+1)} \right) } \\
&=& \frac{1}{2 C_{4N}^{2n+1}} \sum_{k=0}^{N-1}{(X_{k-1} + X_k) C_{2N}^{(2n+1)k}} \\
&=& \frac{1}{2 C_{4N}^{2n+1}} y_n \\
y_n &=& \sum_{k=0}^{N-1}{Y_k C_{2N}^{(2n+1)k}} \\
Y_k &=& X_{k-1} + X_k \\
\end{eqnarray}
```

# code example

```:makefile
CFLAGS=-I. -Wall -Werror -O2 -march=native
INCS=
OBJS=test.o
LIBS=
TARGET=test

%.o: %.c $(INCS)
	$(CC) $(CFLAGS) -c -o $@ $<

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^ $(LIBS)

clean:
	rm -rf $(TARGET) *.o
```

```c:test.c
#include <math.h>
#include <stdio.h>
#include <string.h>

const double C4[] = {
  7.07106781186548E-01,
};

const double C8[] = {
  5.41196100146197E-01, // 1/(2*cos(pi/8*1))
  1.30656296487638E+00, // 1/(2*cos(pi/8*3))
};

const double C16[] = {
  5.09795579104159E-01, // 1/(2*cos(pi/16*1))
  6.01344886935045E-01, // 1/(2*cos(pi/16*3))
  8.99976223136416E-01, // 1/(2*cos(pi/16*5))
  2.56291544774151E+00, // 1/(2*cos(pi/16*7))
};

const double C32[] = {
  5.02419286188156E-01, // 1/(2*cos(pi/32*1))
  5.22498614939689E-01, // 1/(2*cos(pi/32*3))
  5.66944034816358E-01, // 1/(2*cos(pi/32*5))
  6.46821783359990E-01, // 1/(2*cos(pi/32*7))
  7.88154623451250E-01, // 1/(2*cos(pi/32*9))
  1.06067768599035E+00, // 1/(2*cos(pi/32*11))
  1.72244709823833E+00, // 1/(2*cos(pi/32*13))
  5.10114861868916E+00, // 1/(2*cos(pi/32*15))
};

static void idctII2(double* input, double* output)
{
  double* x = output;
  double* X = input;
  double tmp = C4[0] * X[1];
  x[0] = X[0] + tmp;
  x[1] = X[0] - tmp;
}

static void idctII4(double* input, double* output)
{
  double* x = output;
  static double in[2], g[2], h[2], tmp[2];
  
  in[0] = input[0];
  in[1] = input[2];
  idctII2(in, g);
  
  in[0] = input[1];
  in[1] = input[3] + input[1];
  idctII2(in, h);
  
  tmp[0] = C8[0] * h[0];
  tmp[1] = C8[1] * h[1];
  x[0] = g[0] + tmp[0];
  x[1] = g[1] + tmp[1];
  x[2] = g[1] - tmp[1];
  x[3] = g[0] - tmp[0];
}

static void idctII8(double* input, double* output)
{
  double* x = output;
  static double in[4], g[4], h[4], tmp[4];
  
  in[0] = input[0];
  in[1] = input[2];
  in[2] = input[4];
  in[3] = input[6];
  idctII4(in, g);
  
  in[0] = input[1];
  in[1] = input[3] + input[1];
  in[2] = input[5] + input[3];
  in[3] = input[7] + input[5];
  idctII4(in, h);
  
  tmp[0] = C16[0] * h[0];
  tmp[1] = C16[1] * h[1];
  tmp[2] = C16[2] * h[2];
  tmp[3] = C16[3] * h[3];
  x[0] = g[0] + tmp[0];
  x[1] = g[1] + tmp[1];
  x[2] = g[2] + tmp[2];
  x[3] = g[3] + tmp[3];
  x[4] = g[3] - tmp[3];
  x[5] = g[2] - tmp[2];
  x[6] = g[1] - tmp[1];
  x[7] = g[0] - tmp[0];
}

static void fdctII2(double* input, double* output)
{
  double* X = output;
  double* x = input;
  
  X[0] =  x[0] + x[1];
  X[1] = (x[0] - x[1]) * C4[0];
}

static void fdctII4(double* input, double* output)
{
  double* X = output;
  static double in[2], G[2], H[2];
  
  in[0] = input[0] + input[3];
  in[1] = input[1] + input[2];
  fdctII2(in, G);
  
  in[0] = C8[0] * (input[0] - input[3]);
  in[1] = C8[1] * (input[1] - input[2]);
  fdctII2(in, H);
  
  X[0] = G[0];
  X[1] = H[0] + H[1];
  X[2] = G[1];
  X[3] = H[1];
}

static void fdctII8(double* input, double* output)
{
  double* X = output;
  static double in[4], G[4], H[4];
  
  in[0] = input[0] + input[7];
  in[1] = input[1] + input[6];
  in[2] = input[2] + input[5];
  in[3] = input[3] + input[4];
  fdctII4(in, G);
  
  in[0] = C16[0] * (input[0] - input[7]);
  in[1] = C16[1] * (input[1] - input[6]);
  in[2] = C16[2] * (input[2] - input[5]);
  in[3] = C16[3] * (input[3] - input[4]);
  fdctII4(in, H);
  
  X[0] = G[0];
  X[1] = H[0] + H[1];
  X[2] = G[1];
  X[3] = H[1] + H[2];
  X[4] = G[2];
  X[5] = H[2] + H[3];
  X[6] = G[3];
  X[7] = H[3];
}

static void dctIV8(double* input, double* output)
{
  int i;
  static double in[8];
  
  in[0] = input[0];
  for (i=1; i<8; i++) {
    in[i] = input[i-1] + input[i];
  }
  
  idctII8(in, output);
  for (i=0; i<8; i++) {
    output[i] *= C32[i];
  }
}

static void print_data(const char* str, const double *data, int N)
{
  int i;
  printf("%s", str);
  for (i=0; i<N; i++) {
    printf("%+1.3e ", data[i]);
  }
  printf("\n");
}

int main(int argc, char* argv[])
{
  double input[] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
  double output[8];
  
  print_data("input\n", input, 8);
  
  memset(output, 0, sizeof(output));
  fdctII8(input, output);
  print_data("fdctII8\n", output, 8);
  
  memset(output, 0, sizeof(output));
  idctII8(input, output);
  print_data("idctII8\n", output, 8);
  
  memset(output, 0, sizeof(output));
  dctIV8(input, output);
  print_data("dctIV8\n", output, 8);
  
  return 0;
}
```

```bash:console
$ make;./test.exe
cc -I. -Wall -Werror -O2 -march=native -c -o test.o test.c
cc -I. -Wall -Werror -O2 -march=native -o test test.o
input
+0.000e+000 +1.000e+000 +2.000e+000 +3.000e+000 +4.000e+000 +5.000e+000 +6.000e+000 +7.000e+000
fdctII8
+2.800e+001 -1.288e+001 +0.000e+000 -1.347e+000 +0.000e+000 -4.018e-001 +0.000e+000 -1.014e-001
idctII8
+1.459e+001 -1.615e+001 +6.358e+000 -5.495e+000 +2.864e+000 -2.459e+000 +9.404e-001 -6.464e-001
dctIV8
+1.236e+001 -1.576e+001 +6.963e+000 -6.391e+000 +4.586e+000 -4.404e+000 +3.839e+000 -3.793e+000
```