元理系院生の新入社員がPythonとJavaで色々頑張るブログ

プログラミングや機械学習について調べた事を書いていきます

【Java】Javaで離散cos変換

音声信号処理や画像圧縮技術に使用されている離散cos変換(DCT)をJavaで実装します。
DCTについてはこちらのウェブページを参考にさせて頂きました。

離散コサイン変換

DCTはDFTと似ていますが、虚数計算が発生しないためコーディングは比較的容易でした。

離散COS変換の式

入力信号を X = [x_{0}, \dots, x_{N-1} ]^{T}、N行N列の変換行列 Tとした時、離散COS変換後の係数 C = [c_{0}, \dots, c_{N-1} ]^{T}は次のようになります。
 T_{i,j} = \sqrt{\frac{2}{N}}k_{i}cos(\frac{(i-1)(j-\frac{1}{2})}{N}\pi)
 C = DX

逆離散COS変換の式

離散COS変換後の係数を C = [c_{0}, \dots, c_{N-1} ]^{T}、N行N列の変換行列 Tとした時、逆変換後の信号X X = [x_{0}, \dots, x_{N-1} ]^{T}は次のようになります。
 X = D^{T}C

以下にDCT,iDCTのコードを示します。Matrix2dクラスは次の記事で作成したものを使用しています。
emoson.hateblo.jp

public class DCT {
	private int N;			//データ長
	private Matrix2d D;		//cosテーブル

	public DCT(int N){
		this.N = N;
		double[][] d = new double[N][N];
		for(int i=0; i<N; i++){
			double k = i==0 ? 1.0/Math.sqrt(2.0) : 1.0;
			for(int j=0; j<N; j++){
				d[i][j] = Math.sqrt(2.0/N) * k * Math.cos(i*(j+0.5)*Math.PI / (double)N);
			}
		}
		this.D = new Matrix2d(d);
	}

	//離散cos変換
	public double[] dctArray(double[] x){
		Matrix2d X = new Matrix2d(x);
		return Matrix2d.dot(D, X).T().getArrays()[0];
	}

	public double[] dctArray(Matrix2d X){
		return Matrix2d.dot(D, X).T().getArrays()[0];
	}

	public Matrix2d dctMatrix(double[] x){
		Matrix2d X = new Matrix2d(x);
		return Matrix2d.dot(D, X);
	}

	public Matrix2d dctMatrix(Matrix2d X){
		return Matrix2d.dot(D, X);
	}

	//逆離散cos変換
	public double[] idctArray(double[] x){
		Matrix2d X = new Matrix2d(x);
		return Matrix2d.dot(D.T(), X).T().getArrays()[0];
	}

	public double[] idctArray(Matrix2d X){
		return Matrix2d.dot(D.T(), X).T().getArrays()[0];
	}

	public Matrix2d idctMatrix(double[] x){
		Matrix2d X = new Matrix2d(x);
		return Matrix2d.dot(D.T(), X);
	}

	public Matrix2d idctMatrix(Matrix2d X){
		return Matrix2d.dot(D.T(), X);
	}


	//その場限り用の離散cos変換
	public static double[] dctOne(double[] x){
		int N = x.length;
		double[][] d = new double[N][N];
		for(int i=0; i<N; i++){
			double k = i==0 ? 1.0/Math.sqrt(2.0) : 1.0;
			for(int j=0; j<N; j++){
				d[i][j] = Math.sqrt(2.0/N) * k * Math.cos(i*(j+0.5)*Math.PI / (double)N);
			}
		}
		Matrix2d D = new Matrix2d(d);
		Matrix2d X = new Matrix2d(x);
		return Matrix2d.dot(D, X).T().getArrays()[0];
	}
	//その場限り用の逆離散cos変換
	public static double[] idctOne(double[] c){
		int N = c.length;
		double[][] d = new double[N][N];
		for(int i=0; i<N; i++){
			double k = i==0 ? 1.0/Math.sqrt(2.0) : 1.0;
			for(int j=0; j<N; j++){
				d[i][j] = Math.sqrt(2.0/N) * k * Math.cos(i*(j+0.5)*Math.PI / (double)N);
			}
		}
		Matrix2d D = new Matrix2d(d);
		Matrix2d C = new Matrix2d(c);
		return Matrix2d.dot(D.T(), C).T().getArrays()[0];
	}
	public static void main(String[] args){
		double[] X = {1, 1, 1, 1, 2, 2, 2, 2, 2};
		double[] C = DCT.dctOne(X);
		double[] _X = DCT.idctOne(C);
		System.out.println("入力信号\n"+new Matrix2d(X).T());
		System.out.println("変換後の係数\n"+new Matrix2d(C).T());
		System.out.println("逆変換後の信号\n"+new Matrix2d(_X).T());
		
	}
}

実行すると次のような結果が出力されます。

入力信号
| 1.00000  1.00000  1.00000  1.00000  2.00000  2.00000  2.00000  2.00000  2.00000 |

変換後の係数
| 4.66667 -1.33673 -0.23570  0.40825  0.23570 -0.19778 -0.23570  0.08579  0.23570 |

逆変換後の信号
| 1.00000  1.00000  1.00000  1.00000  2.00000  2.00000  2.00000  2.00000  2.00000 |

ちゃんと信号が復元されていますね。