本文目录一览:
- 1、java 实现人脸的动漫化 照片用数组存还是image对象?提取人脸的什么特征?人脸处理常用哪些特征?
- 2、java里面获得图像像素后怎么做dct变换
- 3、JAVA与图像压缩
- 4、什么是dct 变换?它有什么特点?它被广泛应用在什么编码标准中
- 5、关于离散余弦变换(DCT)
java 实现人脸的动漫化 照片用数组存还是image对象?提取人脸的什么特征?人脸处理常用哪些特征?
YOGUAI为保险柜,自动门,考勤等行业提供人脸识别方案。
随着计算机网络和通信技术的发展,信息安全、知识产权保护和身份认证等问题成了一个重要而紧迫的研究课题。身份认证是保证系统安全的必要前提,在多种不同的安全领域都需要准确的身份认证。传统的身份证、智能卡、密码等身份认证方法存在携带不便、容易遗失、不可读或密码易被破解等诸多问题。基于人脸识别技术的身份认证方法与传统的方法相比,具有更好的安全性、可靠性和有效性,因此正越来越受到人们的重视,并逐渐进入社会生活的各个领域。
人脸识别技术具有广泛的应用前景,可以应用到多种不同的安全领域,因其识别特征的独特性、惟一性和相对稳定性,逐渐成为一非常热门的研究课题。许多典型的人脸识别算法和应用系统都是针对标准或特定的人脸数据库,利用库内人脸进行训练,并在相同的库中实现人脸识别。但在软件保护、计算机安全等特殊应用中,身份认证仅针对单个对象进行人脸识别,现有的人脸识别方法并不能胜任这样的识别任务。为此,本文针对单对象人脸识别的特点,讨论了单对象人脸检测和识别的关键技术,在此基础上提出了一种单对象人脸识别算法,实验结果证明了该方法的有效性。
2单对象人脸识别的特点
与典型的人脸识别相比,单对象人脸识别有以下4个方面的特点:
应用领域人脸识别的应用领域很广,如刑侦破案、证件核对、保安监控等,而单对象人脸识别主要应用在软件保护、计算机安全锁、特定对象追踪等领域。
识别系统的目标单对象人脸识别的最终目标是系统必须具有高度的安全性和可靠性,即识别错误率趋于0。虽然降低识别错误率的同时识别率也会降低,但可以通过提示用户调整姿态(如注视摄像头等)加以改善。
肤色模型由于单对象人脸识别仅针对特定的对象,所以人脸检测的肤色模型可采用自适应的方法调整肤色范围。
分类方法单对象人脸识别不存在人脸数据库,常用的最小距离分类法不能够正确识别特定的对象,只能用阈值作为判据。因此,阈值的选取十分重要,阈值过大则容易出现错判,存在安全隐患;而阈值过小又会影响识别效率。
3人脸的检测和归一化
人脸检测是人脸识别的前提。对于给定的图像,人脸检测的目的在于判断图像中是否存在人脸,如果存在,则返回其位置和空间分布。利用人脸肤色和面部特征,将人脸检测分为两个阶段:外脸检测和内脸定位。外脸检测主要利用人脸肤色进行初步的脸区检测,分割出肤色区域;内脸检测是在外脸区域中利用面部几何特征进行验证和定位。
3.1外脸检测
外脸检测的任务是将待检图像中可能的人脸区域找出来并加以标记,其步骤如下:
(1)根据人类肤色在色彩空间中存在区域性的特点,将可能为人脸的像素检测出来。为更好地利用肤色特征,同时选用HSI和YcbCr两种色彩空间对图像进行二值化处理,肤色范围限定在H∈[0,46],S∈[0.10,0.72],Cb∈[98,130],Cr∈[128,170]内。将满足条件的像素标记为肤色像素,其余的均为非肤色像素。
(2)去噪处理。在以每一个肤色点为中心的5×5邻域内统计肤色像素的个数,超过半数时中心点保留为肤色,否则认为是非肤色。
(3)将二值图像中的肤色块作区域归并,并对目标区域进行比例、结构分析,过滤掉不可能的人脸区域。目标区域的高度/宽度比例限定在0.8~2.0。
3.2内脸检测和定位
将包含眼、眉、鼻和嘴的区域称为内脸区域。内脸区域能够很好地表达人脸特征,且不易受背景、头发等因素的干扰,因此内脸区域的检测和定位对后续的特征提取和识别至关重要。
在外脸区域的上半部,对二值图像进行水平方向和垂直方向的投影,确定两个包含黑点的矩形区域作为双眼的大致区域。在确定的两个区域中,对黑点进行区域膨胀,可以得到眼睛的基本轮廓和左石眼角,黑点坐标的平均值作为瞳孔的位置。
设左右瞳孔的坐标分别为(Lx,Ly)和(Rx,Ry),两个瞳孔之间的距离为d,根据人脸的几何特征,我们将内脸区域定义为:宽度=-d×1.6,高度=-d×1.8,左上角坐标为(Lx-d×0.3,(Ly Ry)/2-(-d)×0.3)。实验表明,该区域能够很好地表达人脸特征。
3.3内脸区域的归一化
由于各待测图像中的人脸大小具有很大的随机性,因此,有必要对内脸区域进行归一化操作。人脸归一化是指对内脸区域的图像进行缩放变换,得到统一大小的标准图像,实验中,我们规定标准图像的大小为128×128。归一化处理,保证了人脸大小的一致性,体现了人脸在图像平面内的尺寸不变性。
对归一化的人脸图像,采用小波变换与DCT相结合的方法提取人脸特征。首先对人脸图像进行3层小波分解,取低频子图像LL3作为人脸特征提取的对象,从而获得每幅训练样本或测试样本的低频子图像;然后对低频子图像进行离散余弦变换(DCT),DCT系数个数与子图像的大小相等(即256),由于图像DCT变换,能量集中在低频部分,因此只取其中的136个低频系数作为特征向量。
5人脸的识别
完成训练过程并获得待测样本的特征后,即可进行人脸识别,本文采用欧氏距离进行分类。
5.1计算样本与平均脸的欧氏距离
用m和x表示平均脸和样本的特征向量,则样本与平均脸的欧氏距离为:
其中mk表示平均脸的第k个特征向量,xk表示待测样本的第k个特征向量。身份认证时,计算待测样本与平均脸的欧氏距离,并与特定对象的自适应阈值进行比较,将小于阈值的样本判为该对象的人脸,即认证通过。
5.2自适应阈值的选取
与典型的人脸识别方法不同,单对象人脸认识没有人脸数据库,不能用距离最小作为判据,只能用阈值作为判别依据。阈值的选取应兼顾识别率和识别的准确性,实验中我们取训练样本与平均脸的欧氏距离平均值作为分类阈值,即:
其中,N为训练样本数,此值不宜太小;di为第i个样本与平均脸之间的欧氏距离。
莫士特科技有限公司提供模式识别主板及解决方案。
希望采纳
java里面获得图像像素后怎么做dct变换
private void setAlpha(ByteArrayOutputStream os) {
try {
ImageIcon imageIcon = new ImageIcon(os.toByteArray());
BufferedImage bufferedImage = new BufferedImage(imageIcon.getIconWidth(),imageIcon.getIconHeight()
, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2D = (Graphics2D) bufferedImage.getGraphics();
g2D.drawImage(imageIcon.getImage(), 0, 0,
imageIcon.getImageObserver());
//循环每一个像素点,改变像素点的Alpha值
int alpha = 100;
for (int j1 = bufferedImage.getMinY(); j1 bufferedImage.getHeight(); j1++) {
for (int j2 = bufferedImage.getMinX(); j2 bufferedImage.getWidth(); j2++) {
int rgb = bufferedImage.getRGB(j2, j1);
rgb = ( (alpha + 1) 24) | (rgb 0x00ffffff);
bufferedImage.setRGB(j2, j1, rgb);
}
}
g2D.drawImage(bufferedImage, 0, 0, imageIcon.getImageObserver());
//生成图片为PNG
ImageIO.write(bufferedImage, "png", new File(图片路径));
}
catch (Exception e) {
e.printStackTrace();
}
}
使用JAVA对图片进行效果变换(第二幅图覆盖第一幅图的像素并显示)
一提到JAVA,谈论最多的就是JSP/SERVLET/J2EE之类的,但对于用JAVA对图片进行效果变换,到论坛里看了看,关于这方面的话题并不多,网上关于图像效果处理的文章也并不丰富,于是在自己摸索了几天,并且参考了AnfyJava(专业级的JAVA效果生成器)后,我用轻量级控件写了一个(AnfyJava继承的是Applet,Applet是java.awt包中的,属于重量级控件,SUN现在推荐使用swing来编写图形程序,因此,我用的是JApplet)。
其实,用JAVA做图像效果和其它语言在本质上并没有什么区别,只不过在实现起来有所不同罢了,下面我就把我在项目中处理的经验与大家分享一下吧。
图像的变换,实际上就是把两幅图片的内容进行某些运算,生成新的图像,然后显示出来,最终实现从一幅图片到另一幅图片的过度效果。变换的具体过程如下:
在上面的过程中,图片A和B的尺寸最好保持一致,如果不一致的话,可能要做一些额外的处理,在此,我选用的图片A和B的尺寸是一致的。
首先,我们将其当作一个Applet来写,由于Applet的局限性,不可以直接使用File类来读取图像文件,因此,我们只能通过如下方法来获取图像文件。
URLClassLoader urlLoader = (URLClassLoader)this.getClass().getClassLoader();
URL url = urlLoader.findResource("imagea.gif");
Image image = Toolkit.getDefaultToolkit().getImage(url);
当我们获得了图像后,可以通过java.awt.image.PixelGrabber包中的PixelGrabber方法来将图像中的像素信息完全读取出来,其用法如下:
PixelGrabber(Image img, int x, int y, int w, int h, int[] pix, int off, int scansize)
其中img是要读取的图像,x/y是要读取图像中的左上角坐标,w/h分别是从x/y开始起的距离,其实x,y,w,h就是一个矩形,pix是保存像素的数组,off是读取图像时开始的位置,scansize是指扫描的宽度,一般来说和w相等。
int width = image.getWidth();
int height = image.getHeight();
int size = width * height;
int[] pixels = new int[size];
pixelgrabber = new PixelGrabber(image, 0, 0, width, height, pixels, 0, width);
try {
pixelgrabber.grabPixels(); //读取像素入数组
}
catch (InterruptedException _ex) {}
由于像素信息是由alpha,red,green,blue组成的,其格式为
因此,我们可以将颜色分解成单独的RGB信息
int alpha = (pixel 24) 0xff;
int red = (pixel 16) 0xff;
int green = (pixel 8) 0xff;
int blue = (pixel) 0xff;
假如要实现显示图片A后,图片B由上至下展开,则可以每次将图片A中的一行像素替换为B中的相应行,然后生成新的像素信息:
图像A的像素数组 图像B的像素数组
old = pixelA; //保存图片A的像素信息
oldR = redA; //保存图片A的R信息
oldG = greenA; //保存图片A的G信息
oldB = blueA; //保存图片A的B信息
for (int i = 0; i width; i++) {//line为行数
oldR[line * width + i] = redA [line * width + i];
oldG[line * width + i] = greenA [line * width + i];
oldB[line * width + i] = blueA [line * width + i];
old[line * width + i] = oldR[line * width + i] 16 + oldG[line * width + i] 8 + oldB[line * width + i];
}
当生成新的像素信息后,可以通过java.awt.image.MemoryImageSource包中的MemoryImageSource(int w, int h, ColorModel cm, int[] pix, int off, int scan)方法将像素数组对应到图像,并且可以用newPixels()方法来生成新的图像(具体用法可以参考JAVA API DOC)。
memoryimagesource = new MemoryImageSource(imageWidth, imageHeight,
new DirectColorModel(24, 0xff0000, 0x00ff00, 0x0000ff), blocks, 0, imageWidth);
//检查java版本
String javaVersion;
try {
javaVersion = System.getProperty("java.version");
}
catch (SecurityException _ex) {
javaVersion = "unk";
}
if (!javaVersion.startsWith("1.0")) { //jdk1.1以上的版本才支持此方法
try {
memoryimagesource.setAnimated(true);
memoryimagesource.setFullBufferUpdates(true);
imageBuf = createImage(memoryimagesource);
memoryimagesource.newPixels();//生成新的图像
}
catch (NoSuchMethodError _ex) {
System.out.println("unknow java version!");
}
}
到此,新的图像已经产生,只需要输出到屏幕即可。
在此,需要注意以下几个问题:
1、由于Applet读取的图像文件可以比较大,对于速度较慢的网络,可能会造成图像未读取完全就开始进行变换,因此,建议使用MediaTracker方法来保证图像能被顺利载入。
2、 在显示的时候,为了避免闪烁,可以采用双缓冲的方法。
3、 由于在某此高速计算机上,生成新的像素可以非常快,为了避免速度过快而造成效果并不明显,可以加以适当的延时处理。
4、 为了保证效果的平滑,我们特地开辟了非常大的数组来保存像素/RGB信息,这是以空间换时间的做法。
完整的源程序附下(在jdk1.4/2k Server/RedHat9下运行通过,所用机器为P4 2.4G/512M)
package pic;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
public class effect
extends JApplet
implements Runnable {
//定义变量
Toolkit toolkit;
int totalBlocks = 0; //图像被分解成的块数,默认为 宽X高
int[] blocks; //保存块数的信息
Image[] bufferImage = new Image[2]; //屏幕上的图形缓冲
VolatileImage offScreenImage;
Image imageBuf; //保存图片缓冲区内容
Graphics2D offScreenGraphics;
Thread thread;
MediaTracker mediaTracker;
boolean[] isImageReady; //图片是否已经装载
MemoryImageSource memoryimagesource;
int imageWidth, imageHeight; //图像的宽及高
int[] pixelA, pixelB;
int[] redA, greenA, blueA, redB, greenB, blueB;
public effect() throws HeadlessException {
bufferImage[0] = getImage("a.jpg");
bufferImage[1] = getImage("b.jpg");
if ( (bufferImage[0].getWidth(this) != bufferImage[1].getWidth(this)) ||
(bufferImage[0].getHeight(this) != bufferImage[1].getHeight(this))) {
System.out.println("图像尺寸不一致!");
return;
}
toolkit = getToolkit();
imageWidth = bufferImage[0].getWidth(this);
imageHeight = bufferImage[0].getHeight(this);
totalBlocks = imageWidth * imageHeight; //计算分解的块数
blocks = new int[totalBlocks];
pixelA = new int[totalBlocks];
pixelB = new int[totalBlocks];
redA = new int[totalBlocks];
greenA = new int[totalBlocks];
blueA = new int[totalBlocks];
redB = new int[totalBlocks];
greenB = new int[totalBlocks];
blueB = new int[totalBlocks];
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gd.getDefaultConfiguration();
offScreenImage = gc.createCompatibleVolatileImage(imageWidth, imageHeight); //创建图像缓冲
offScreenGraphics = offScreenImage.createGraphics(); //取得缓冲的graphics对象
}
public void init() {
getImagePixels(bufferImage[0], pixelA);
getImagePixels(bufferImage[1], pixelB);
for (int i = 0; i totalBlocks; i++) {
blocks[i] = pixelA[i]; //保存图像A的像素信息
redA[i] = pixelA[i] 0xff0000; //保存图像B的red值
greenA[i] = pixelA[i] 0x00ff00; //保存图像B的green值
blueA[i] = pixelA[i] 0x0000ff; //保存图像B的blue值
redB[i] = pixelB[i] 0xff0000; //保存图像B的red值
greenB[i] = pixelB[i] 0x00ff00; //保存图像B的green值
blueB[i] = pixelB[i] 0x0000ff; //保存图像B的blue值
}
prepareImagePixels(); //将像素信息还原为图像
}
public void run() {
//检查java版本
String javaVersion;
try {
javaVersion = System.getProperty("java.version");
}
catch (SecurityException _ex) {
javaVersion = "unk";
}
if (javaVersion.startsWith("1.0")) {
System.out.println("require java 1.1 or later version!");
return;
}
try { //暂停3秒钟后等待效果开始
thread.sleep(3000l);
}
catch (InterruptedException ex1) {
}
int line = 0;
Thread currentThread = Thread.currentThread();
while (line imageHeight thread == currentThread) {
for (int i = 0; i imageWidth; i++) {
int offset = line * imageWidth + i;
blocks[offset] = pixelB[offset]; //与下一被注释的语句作用相同
//blocks[offset] = redB[offset] | greenB[offset] | blueB[offset];
}
memoryimagesource.newPixels(); //生成新的图像
line++;
repaint();
//适当延时
try {
thread.sleep(20l);
}
catch (InterruptedException ex) {
}
}
}
public void paint(Graphics g) {
if (offScreenGraphics != null) { //保证在destory()时不引发异常
offScreenGraphics.drawImage(imageBuf, 0, 0, this);
g.drawImage(offScreenImage, 0, 0, this);
}
}
public void start() {
if (thread == null) {
thread = new Thread(this);
thread.start();
}
}
public void stop() {
thread = null;
}
public final void update(Graphics g) {
paint(g);
}
public void destroy() {
if (offScreenImage != null) {
offScreenImage.flush();
}
offScreenImage = null;
if (offScreenGraphics != null) {
offScreenGraphics.dispose();
}
offScreenGraphics = null;
System.gc();
}
Image getImage(String filename) {
URLClassLoader urlLoader = (URLClassLoader)this.getClass().getClassLoader();
URL url = null;
Image image = null;
url = urlLoader.findResource(filename);
image = Toolkit.getDefaultToolkit().getImage(url);
MediaTracker mediatracker = new MediaTracker(this);
try {
mediatracker.addImage(image, 0);
mediatracker.waitForID(0);
}
catch (InterruptedException _ex) {
image = null;
}
if (mediatracker.isErrorID(0)) {
image = null;
}
return image;
}
private boolean getImagePixels(Image image, int pixels[]) {
PixelGrabber pixelgrabber = new PixelGrabber(image, 0, 0, imageWidth,
imageHeight, pixels,
0, imageWidth);
try {
pixelgrabber.grabPixels();
}
catch (InterruptedException _ex) {
return false;
}
return true;
}
void prepareImagePixels() {
memoryimagesource = new MemoryImageSource(imageWidth, imageHeight,
new DirectColorModel(24, 0xff0000,
0x00ff00, 0x0000ff), blocks, 0, imageWidth);
try {
memoryimagesource.setAnimated(true);
memoryimagesource.setFullBufferUpdates(true);
imageBuf = createImage(memoryimagesource);
memoryimagesource.newPixels(); //生成新的图像
}
catch (NoSuchMethodError _ex) {
}
}
public int getWidth() {
return imageWidth;
}
public int getHeight() {
return imageHeight;
}
public static void main(String args[]) {
JFrame frame = new JFrame("Demo");
effect e = new effect();
e.init();
e.start();
frame.getContentPane().setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.getContentPane().add(e);
frame.setSize(new Dimension(e.getWidth() + 6, e.getHeight() + 20));
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = frame.getSize();
frame.setLocation( (screenSize.width - frameSize.width) / 2,
(screenSize.height - frameSize.height) / 2);
frame.show();
}
}
新建一个HTML文件,加入以下语句可作为Applet(其中width/height分别是图片的高和宽):
作为应用程序运行的时候,在命令行输入:
java pic.effect
JAVA与图像压缩
此篇转载。
----------------------------------------------------------------------
下面是一段给图片加上网站logo的代码,注意第12,13,14行设置了图片的压缩比。本例为不压缩原图片。
java 代码
BufferedImage image = ImageIO.read(new FileInputStream("c:\\base.jpg"));
//读取图标
BufferedImage image_biao = ImageIO.read(new FileInputStream(
"c:\\logo.gif"));
Graphics2D g = image.createGraphics();
g.drawImage(image_biao, 10, 10, image_biao.getWidth(null),
image_biao.getHeight(null), null);
g.dispose();
FileOutputStream out = new FileOutputStream("c:\\out.jpg");
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(image);
param.setQuality(1f, false);
encoder.setJPEGEncodeParam(param);
encoder.encode(image);
out.close();
setQuality
public void setQuality(float quality, boolean forceBaseline)
quality取值在 1.0 到 0.0 之间
Some guidelines: 0.75 high quality 0.5 medium quality 0.25 low quality
另外要得到一张指定高度和宽度的图片的话可以用以下代码:(接上面代码)
java 代码
BufferedImage tag = new BufferedImage(500, 300,image.getType());
tag.getGraphics().drawImage(image, 0, 0, 500, 300, null); //绘制缩小后的图
FileOutputStream out2 = new FileOutputStream("c:\\out2.jpg");
JPEGImageEncoder encoder2 = JPEGCodec.createJPEGEncoder(out2);
JPEGEncodeParam param2 = encoder.getDefaultJPEGEncodeParam(tag);
param2.setQuality(1f, false);
encoder2.setJPEGEncodeParam(param2);
encoder2.encode(tag);
out2.close();
注意第一行新建 BufferedImage 的时候要使用原图片的type,这样可以保证输出与原图片相同质量的图片。
什么是dct 变换?它有什么特点?它被广泛应用在什么编码标准中
DCT变换,全称是离散余弦变换(Discrete Cosine Transform),主要用于将数据或图像的压缩,能够将空域的信号转换到频域上,具有良好的去相关性的性能。DCT变换本身是无损的,但是在图像编码等领域给接下来的量化、哈弗曼编码等创造了良好的条件。同时由于DCT变换是对称的,所以,我们可以在量化编码后利用DCT反变换,在接收端恢复原始图像,DCT变换在当前的图像分析和图像压缩领域有着极广的用途,我们常见的JPEG静态图像编码以及MJPEG、MPEG动态编码等标准中都使用了DCT变换。
特点:除实数变换、确定的变换矩阵、准最佳变换性能外,二维DCT还是一种可分离的变换,可以用两次一维变换得到二维变换结果。
应用编码标准:常见的JPEG静态图像编码以及MJPEG、MPEG动态编码等标准。
关于离散余弦变换(DCT)
1.转自: 离散余弦变换(DCT)的定义_小火车_新浪博客
已知离散傅里叶变换( DFT)为:
由于许多要处理的信号都是实信号,在使用DFT时由于傅里叶变换时由于实信号傅立叶变换的共轭对称性导致DFT后在频域中有一半的数据冗余。
离散余弦变换(DCT)是对实信号定义的一种变换,变换后在频域中得到的也是一个实信号,相比DFT而言,DCT可以减少一半以上的计算。DCT还有一个很重要的性质(能量集中特性):大多书自然信号(声音、图像)的能量都集中在离散余弦变换后的低频部分,因而DCT在(声音、图像)数据压缩中得到了广泛的使用。由于DCT是从DFT推导出来的另一种变换,因此许多DFT的属性在DCT中仍然是保留下来的。
推导N点长实序列的DCT,首先来定义一个新的长度为2N的序列:
可看作是将周期为N的序列x[m]做一个周期延拓成一个周期为2N的序列。如图1中第一张图。
再来看图1中第一张图是关于x = -1/2对称的,要让他关于x = 0对称需要将其向右平移1/2个单位,得到x’[m] = x’[m – 1/2]就是关于x = 0对称的周期序列了(如图1中第二张图)。
然后求这个2N序列的DFT:
就是DCT-2型离散余弦变换.从上面的过程也可以直接看出,离散余弦变换相当于一个长度大概是它两倍的离散傅里叶变换.
变换后的x[n]是以2N为周期,偶对称的序列: X[N+n] = X[N+n-2N] = X[n-N] = x[N-n]
定义变换矩阵C[n,m]:
用计算机计算DCT-2 (用的是O(n^2)朴素算法,用于验证正交特性以及观察其频域数据):
DCT的结果:
对相同序列FFT的结果:
比较DFT和FFT的结果可以观察出DCT变换只有实部,而DFT变换后有虚部。在这个例子中DCT在频域中只用3个点就可以表示这个信号,而DFT变换后在频域中需要5个点来表示信号。
参考:
2.转自: 二维DCT变换 - Wuyuan's Blog
写这篇文章的目的主要是为了给x264打好基础,x264用的是整数DCT变换,所以就先来说说DCT变换吧。
DCT(Discrete Cosine Transform),又叫离散余弦变换,它的第二种类型,经常用于信号和图像数据的压缩。经过DCT变换后的数据能量非常集中,一般只有左上角的数值是非零的,也就是能量都集中在离散余弦变换后的直流和低频部分,下面我会用matlab来演示整个过程。
1.一维DCT变换
我们首先来看看一维的DCT变换,这是二维的基础。一维的DCT变换共有8种,其中最实用的是第二种形式,公式如下:
其中c(u)是加上去一个系数,为了能使DCT变换矩阵成为正交矩阵,在后面二维变换将看到他的作用。N是f(x)的总数。相比其他几种形式,他的运算还是比较简单的,因此也用的比较广。
2.二维DCT变换
二维DCT变换是在一维的基础上再进行一次DCT变换,这个比较好理解,直接看公式:
这里我只讨论两个N相等的情况,也就是数据是方阵的形式,在实际应用中对不是方阵的数据都是先补齐再进行变换的。为了matlab仿真方便点,写成矩阵形式:
下面就用matlab来模拟一下,使用随机生成的4x4矩阵作为输入,程序如下:
Y是使用上面的公式进行变换,YY是用matlab自带的dct2函数变换,结果是是:
可以看出Y和YY的结果是一样的,这也进一步验证了上面的公式是正确的。由于X是我随机生成的,相关性很小,变换后的结果比较乱;如果是信号或图像这样相关性比较大的数据的话,数值会集中在左上角,右下角一般都是零,再使用“之”字型扫描得到数据流会包含很多连续的零,编码后数据量会非常小,这就是DCT变换带来的好处。
3.二维DCT反变换
DCT逆变换的公式如下:
矩阵形式可以由正变换的公式直接推出来,因为在A中加了c(i)这个系数,使得A成为了正交矩阵,所以我们就可以这样做:
在用matlab来验证是否能反变换出原来的数据:
X使用的是上面正变换用的数据,运行后得到的X1为:
X1=
61.000019.000050.000020.0000
82.000026.000061.000045.0000
89.000090.000082.000043.0000
93.000059.000053.000097.0000
和X完全相等。在实际进行编码的时候,比如JPEG压缩的时候,只会对Y左上角的数据进行传输,所以解码出来的内容不会完全和原来的相同。
4.整数DCT变换
说道DCT就顺便提一下x264中的整数DCT变换,整数DCT变换是以DCT变换为基础的,为了减少计算量做的一些调整,下面我写一下整数DCT变换公式的大致推导过程:
然后根据A是正交矩阵,把c=bd带入A中,使行向量为单位向量可以得到d=0.4142。令d=0.5,得到b*b=0.4,代入上面的式子中,把0.5提取出来放到右边的点乘中就得到了:
这样在对大括号部分进行计算时就都是加法和减法了,而且在精度上没有太大降低。在x264实际编码中,变换和量化是一起进行的,使得编码速度有了很大的提高。