离散余弦变换原理及实现【转载】

1.预备知识
1.1可分离变换
二维傅立叶变换可用通用的关系式来表示:
在这里插入图片描述
式中:x, u=0, 1, 2, …, M-1;y, v=0, 1, 2, …, N-1;g(x,y,u,v)和h(x,y,u,v)分别称为正向变换核和反向变换核
如果满足 :
在这里插入图片描述
则称正、反变换核是可分离的。进一步,如果g1和g2,h1和h2在函数形式上一样,则称该变换核是对称的。
2.图像变换的矩阵表示
数字图像都是实数矩阵, 设f(x, y)为M×N的图像灰度矩阵, 通常为了分析、推导方便,可将可分离变换写成矩阵的形式:
在这里插入图片描述
其中,F、f是二维M×N的矩阵;P是M×M矩阵;Q是N×N矩阵。
在这里插入图片描述
式中,u=0, 1, 2, …, M-1,v=0, 1, 2, …, N-1。
对二维离散傅立叶变换,则有 :
P ( x , u ) = g 1 ( x , u ) = e j 2 π u x / M D ( y , v ) = g 2 ( y , v ) = e j 2 π v y / N P(x,u)=g_1(x,u)=e^{-j2\pi ux/M}\\ D(y,v)=g_2(y,v)=e^{-j2\pi vy/N} 实践中,除了DFT变换之外,还采用许多其他的可分离的正交变换。例如:离散余弦变换、沃尔什-哈达玛变换、K-L变换等。

2.离散余弦变换数学原理
离散余弦变换(Discrete Cosine Transform,DCT)是可分离的变换,其变换核为余弦函数。DCT除了具有一般的正交变换性质外, 它的变换阵的基向量能很好地描述人类语音信号和图像信号的相关特征。因此,在对语音信号、图像信号的变换中,DCT变换被认为是一种准最佳变换。

2.1一维离散余弦变换定义
在这里插入图片描述
一维DCT定义如下: 设{f(x)|x=0, 1, …, N-1}为离散的信号列
在这里插入图片描述
看看,这里我们就用到了特定核函数的可分离性!将变换式展开整理后, 可以写成矩阵的形式, 即 :
F = G f F=Gf
在这里插入图片描述
(注意:G矩阵的第三行第一列应该cos(2π/2N),第三行最后一列应该是cos(2(2N-1)π/2N)

2.2二维离散余弦变换
二维DCT正变换核为:
在这里插入图片描述
式中,x, u=0, 1, 2, …, M-1; y, v=0, 1, 2, …, N-1。
二维DCT定义如下:
设f(x, y)为M×N的数字图像矩阵,则
在这里插入图片描述
式中: x, u=0, 1, 2, …, M-1; y, v=0, 1, 2, …, N-1。

通常根据可分离性, 二维DCT可用两次一维DCT来完成, 其算法流程与DFT类似, 即
在这里插入图片描述

3.二维DFT与二维DCT的频谱特征分析
3.1 细节(高频分量)较少的图像实验
在这里插入图片描述
Conclusion:
对于比较平滑的图像/数据,DFT变换数据集中在中间(低频信号区),DCT变换数据集中在左上角,几乎无法看出DCT的优势在哪里。

3.2 细节丰富的图像实验
在这里插入图片描述
Conclusion:
DCT变化后的数据很发散,DCT变化后的数据仍然比较集中。如果同样从频率谱恢复原始图像,那么选用DCT更合理,因为DCT只需要存储更少的数据点。正是这个原因,是的DCT广泛地应用于图像压缩。

4.DCT应用于图像压缩
16*16 进行分区做DCT变换,然后按照不同的模板进行数据存留与重建。我们会发现,如果保存的数据过少,会有块效应现象发生。
在这里插入图片描述
64*64的分区设置,块效应更明显。此时就要在每个分区内多采集点数据啦。
在这里插入图片描述
6.简介DCT在JPEG压缩编码中的应用
JPEG(Joint Photographic Experts Group) 专家组开发了两种基本的压缩算法,一种是采用以离散余弦变换(DCT)为基础的有损压缩算法,另一种是采用以预测技术为基础的无损压缩算法。使用有损压缩算法时,在压缩比为25:1的情况下,压缩后还原得到的图像与原始图像相比较,非图像专家难于找出它们之间的区别,因此得到了广泛的应用。
在这里插入图片描述
JPEG算法的主要计算步骤

  • 正向离散余弦变换(FDCT)
  • 量化(quantization)
  • Z字形编码(zigzag scan)
  • 使用差分脉冲编码调制(differential pulse code modulation,DPCM)对直流系数(DC)进行编码
  • 使用行程长度编码(run-length encoding,RLE)对交流系数(AC)进行编码
  • 熵编码(entropy coding)

7.DCT在数字水印(digital watermarking)技术中的应用
数字水印技术是将特定的信息嵌入到数字信息的内容中,要求嵌入的信息不能被轻易的去除,在一定的条件下可以被提取出来,以确认作者的版权。
水印嵌入框图:
在这里插入图片描述
水印检测框图:
在这里插入图片描述
9.离散余弦变换编程实现(C/C++)
核心原理:频率不同的正弦或余弦波相乘,其黎曼积分总是为零。
dct.h

//==============================================================================
//  Copyright (C) 2019 王小康. All rights reserved.
//
//  作者: 王小康
//  描述: 一维二维DCT变换
//  日期: 2019-05-09
//
//==============================================================================

#ifndef _DCT_H_
#define _DCT_H_

#define  DCT_LEN  8    //一维或二维矩阵边长

void dct_init(void);
void dct_1Ddct(float *out, float *in);
void dct_1Didct(float *out, float *in);
void dct_1Dspectrum(float *out, float *in);
void dct_2Ddct(float out[][DCT_LEN], float in[][DCT_LEN]);
void dct_2Didct(float out[][DCT_LEN], float in[][DCT_LEN]);

#endif

dct.cpp

//==============================================================================
//  Copyright (C) 2019 王小康. All rights reserved.
//
//  作者: 王小康
//  描述: 一维二维DCT变换
//  日期: 2019-05-09
//
//==============================================================================

#include <math.h>
#include "dct.h"

static float  dctTableA[DCT_LEN][DCT_LEN];
static float  sqrt_1_N;
static float  sqrt_2_N;

////////////////////////////////////////////////////////////////////////////////

// 离散余弦变换初始化,生成变换核。一维二维同核。
void dct_init(void){
	sqrt_1_N = sqrt(1.0 / DCT_LEN);
	sqrt_2_N = sqrt(2.0 / DCT_LEN);
	int i, j;
	for (i = 0; i < DCT_LEN; i++){
		dctTableA[0][i] = sqrt_1_N;
	}
	for (i = 1; i < DCT_LEN; i++){
		for (j = 0; j < DCT_LEN; j++){
			dctTableA[i][j] = sqrt_2_N * cos((3.14159265359*i*(2 * j + 1)) / (2 * DCT_LEN));
		}
	}
}

// 一维离散余弦变换
void dct_1Ddct(float *out, float *in){
	unsigned i, j;
	for (i = 0; i < DCT_LEN; i++){
		out[i] = 0;
		for (j = 0; j < DCT_LEN; j++) out[i] += dctTableA[i][j] * in[j];
	}
}

// 一维离散余弦变换,返回频谱图
void dct_1Dspectrum(float *out, float *in){
	unsigned i, j;
	out[0] = 0;
	for (i = 0; i < DCT_LEN; i++) out[0] += in[i];
	out[0] /= DCT_LEN;
	if (out[0] < 0) out[0] = -out[0];
	for (i = 1; i < DCT_LEN; i++){
		out[i] = 0;
		for (j = 0; j < DCT_LEN; j++) out[i] += dctTableA[i][j] * in[j];
		out[i] *= sqrt_2_N;
		if (out[i] < 0) out[i] = -out[i];
	}
}

// 一维离散余弦逆变换
void dct_1Didct(float *out, float *in){
	unsigned i, j;
	for (i = 0; i < DCT_LEN; i++){
		out[i] = 0;
		for (j = 0; j < DCT_LEN; j++) out[i] += dctTableA[j][i] * in[j];
	}
}

// 二维离散余弦变换
void dct_2Ddct(float out[][DCT_LEN], float in[][DCT_LEN]){
	float tmp[DCT_LEN][DCT_LEN];
	float sum;
	int i, j, k;
	for (i = 0; i < DCT_LEN; i++){
		for (j = 0; j < DCT_LEN; j++){
			sum = 0;
			for (k = 0; k < DCT_LEN; k++) sum += dctTableA[i][k] * in[k][j];
			tmp[i][j] = sum;
		}
	}
	for (i = 0; i < DCT_LEN; i++){
		for (j = 0; j < DCT_LEN; j++){
			sum = 0;
			for (k = 0; k < DCT_LEN; k++) sum += dctTableA[j][k] * tmp[i][k];
			out[i][j] = sum;
		}
	}
}

// 二维离散余弦逆变换
void dct_2Didct(float out[][DCT_LEN], float in[][DCT_LEN]){
	float tmp[DCT_LEN][DCT_LEN];
	float sum;
	int i, j, k;
	for (i = 0; i < DCT_LEN; i++){
		for (j = 0; j < DCT_LEN; j++){
			sum = 0;
			for (k = 0; k < DCT_LEN; k++) sum += dctTableA[k][i] * in[k][j];
			tmp[i][j] = sum;
		}
	}
	for (i = 0; i < DCT_LEN; i++){
		for (j = 0; j < DCT_LEN; j++){
			sum = 0;
			for (k = 0; k < DCT_LEN; k++) sum += dctTableA[k][j] * tmp[i][k];
			out[i][j] = sum;
		}
	}
}

main.cpp

扫描二维码关注公众号,回复: 10639978 查看本文章
//==============================================================================
//
//  作者: 王小康
//  描述: 一维二维DCT变换测试程序
//  日期: 2019-05-09
//
//==============================================================================

#include <stdio.h>
#include <stdlib.h>
#include "dct.h"

static void show1D(float *x){
	int i;
	for (i = 0; i < DCT_LEN; i++){
		printf("%15f", x[i]);
	}
	putchar('\n');
}

static void show2D(float x[][DCT_LEN]){
	int i, j;
	for (i = 0; i < DCT_LEN; i++){
		for (j = 0; j < DCT_LEN; j++){
			printf("%15f", x[i][j]);
		}
		putchar('\n');
	}
	putchar('\n');
}

int main(int argc, char *argv[]){

	float data1[DCT_LEN][DCT_LEN] = {
		{ 89, 101, 114, 125, 126, 115, 105, 96 },
		{ 97, 115, 131, 147, 149, 135, 123, 113 },
		{ 114, 134, 159, 178, 175, 164, 149, 137 },
		{ 121, 143, 177, 196, 201, 189, 165, 150 },
		{ 119, 141, 175, 201, 207, 186, 162, 144 },
		{ 107, 130, 165, 189, 192, 171, 144, 125 },
		{ 97, 119, 149, 171, 172, 145, 117, 96 },
		{ 88, 107, 136, 156, 155, 129, 97, 75 }

		// {200, 200, 200, 200, 200, 200, 200, 200},
		// {240, 220, 190,  20,  20,  60,  30, 160},
		// {200, 160, 180,  40,  40,  50,  50, 120},
		// {220, 120, 190,  60,  60,  80,  70, 100},
		// {190, 100, 100,  80,  80,  90,  99, 111},
		// {100, 150, 120, 100,  70, 100, 120, 130},
		// {111, 111, 121, 123, 110, 111, 134, 167},
		// {120, 220, 222, 222, 210, 109, 190, 100}
	};
	float data2[DCT_LEN][DCT_LEN];
	float data3[DCT_LEN][DCT_LEN];

	dct_init();

	printf("一维原始数据    :");
	show1D((float*)data1);
	dct_1Dspectrum((float*)data2, (float*)data1);
	printf("一维原始数据频谱:");
	show1D((float*)data2);
	dct_1Ddct((float*)data2, (float*)data1);
	printf("一维DCT变换后   :");
	show1D((float*)data2);
	dct_1Didct((float*)data3, (float*)data2);
	printf("一维DCT逆变换后 :");
	show1D((float*)data3);
	putchar('\n');

	printf("二维原始数据:\n");
	show2D(data1);
	putchar('\n');

	dct_2Ddct(data2, data1);
	printf("二维DCT变换后:\n");
	show2D(data2);
	putchar('\n');

	dct_2Didct(data3, data2);
	printf("二维DCT逆变换后:\n");
	show2D(data3);
	putchar('\n');

	system("pause");
	return 0;
}

实验结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


以上博文整理自:

  1. 原理:https://blog.csdn.net/shenziheng1/article/details/52965104
  2. 实现:https://www.jianshu.com/p/fe3709c0a765
发布了96 篇原创文章 · 获赞 294 · 访问量 84万+

猜你喜欢

转载自blog.csdn.net/baidu_38172402/article/details/105272324