研究了那么久的图片压缩原理之后
虽然没能带回一个自己用JAVA实现的图片压缩软件
但是总算是自己终于对图片压缩有了个清晰的了解
好了,废话不多说
继续上次关于远程监控系统中用UDP广播图片遇到的图片压缩大小瓶颈问题
首先再次讨论上次给出的那组数据(关于对比ImageIO默认参数下写出GIF/JPEG,以及自己设置参数的JPEG)的讨论
GIF采用的是字典LZW算法,该算法是无损压缩,能提供近乎完美的无损压缩比,我们记得压缩后的图片大小大约为90KB
而JPEG默认情况下为有损压缩,压缩后大小大约200KB
在ImageIO中通过自己设置压缩质量来压缩,我们发现当压缩质量小于0.5以后,图片大小的变化是很缓慢的
用import com.sun.image.codec.jpeg.JPEGCodec提供的编码解码类来设置压缩质量,在同等质量的情况下,虽然比ImageIO中图片大小变小了,其实也是很有限的。通过前文,我们了解了这样设置图片质量其实知识改变量化表,在图片质量已经不高的情况下,其改变对图片大小的影响其实是很小的
测试代码
package cn.mzd.newIM.test;
import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
public class ImageSizeTest {
/**
* @param args
* @throws AWTException
*/
public void getImageSize() throws AWTException {
java.awt.Robot rb = new java.awt.Robot();
Dimension d = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
Rectangle rt = new Rectangle(0, 0, (int) d.getWidth(), (int) d
.getHeight());
for (int i = 0; i < 1000; i++) {
BufferedImage image = rb.createScreenCapture(rt);
bufferedImageTobytes(image, "gif");
bufferedImageTobytes(image, "jpeg");
bufferedImageTobytes(image, 0.9f);
newCompressImage(image, 0.9f);
}
}
/**
* 用Format对应格式中ImageIO默认参数把IMAGE打包成BYTE[]
* @param image
* @return
*/
private byte[] bufferedImageTobytes(BufferedImage image, String format) {
System.out.println(format + "格式开始打包" + getCurrentTime());
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
ImageIO.write(image, format, out);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(format + "格式完成打包-----" + getCurrentTime()
+ "----lenth------" + out.toByteArray().length);
return out.toByteArray();
}
/**
*
* 自己设置压缩质量来把图片压缩成byte[]
*
* @param image
* 压缩源图片
* @param quality
* 压缩质量,在0-1之间,
* @return 返回的字节数组
*/
private byte[] bufferedImageTobytes(BufferedImage image, float quality) {
System.out.println("jpeg" + quality + "质量开始打包" + getCurrentTime());
// 如果图片空,返回空
if (image == null) {
return null;
}
// 得到指定Format图片的writer
Iterator<ImageWriter> iter = ImageIO
.getImageWritersByFormatName("jpeg");// 得到迭代器
ImageWriter writer = (ImageWriter) iter.next(); // 得到writer
// 得到指定writer的输出参数设置(ImageWriteParam )
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 设置可否压缩
iwp.setCompressionQuality(quality); // 设置压缩质量参数
iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
ColorModel colorModel = ColorModel.getRGBdefault();
// 指定压缩时使用的色彩模式
iwp.setDestinationType(new javax.imageio.ImageTypeSpecifier(colorModel,
colorModel.createCompatibleSampleModel(16, 16)));
// 开始打包图片,写入byte[]
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流
IIOImage iIamge = new IIOImage(image, null, null);
try {
// 此处因为ImageWriter中用来接收write信息的output要求必须是ImageOutput
// 通过ImageIo中的静态方法,得到byteArrayOutputStream的ImageOutput
writer.setOutput(ImageIO
.createImageOutputStream(byteArrayOutputStream));
writer.write(null, iIamge, iwp);
} catch (IOException e) {
System.out.println("write errro");
e.printStackTrace();
}
System.out.println("jpeg" + quality + "质量完成打包-----" + getCurrentTime()
+ "----lenth----" + byteArrayOutputStream.toByteArray().length);
return byteArrayOutputStream.toByteArray();
}
/**
* 自己定义格式,得到当前系统时间
*
* @return
*/
private String getCurrentTime() {
Calendar c = new GregorianCalendar();
int hour = c.get(Calendar.HOUR_OF_DAY);
int min = c.get(Calendar.MINUTE);
int second = c.get(Calendar.SECOND);
int millsecond = c.get(Calendar.MILLISECOND);
String time = hour + "点" + min + "分" + second + "秒" + millsecond;
return time;
}
/**
* 通过 com.sun.image.codec.jpeg.JPEGCodec提供的编码器来实现图像压缩
* @param image
* @param quality
* @return
*/
private byte[] newCompressImage(BufferedImage image, float quality) {
// 如果图片空,返回空
if (image == null) {
return null;
}
// 开始开始,写入byte[]
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流
// 设置压缩参数
JPEGEncodeParam param = JPEGCodec.getDefaultJPEGEncodeParam(image);
param.setQuality(quality, false);
// 设置编码器
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(
byteArrayOutputStream, param);
System.out.println("newCompressive" + quality + "质量开始打包"
+ getCurrentTime());
try {
encoder.encode(image);
} catch (Exception ef){
ef.printStackTrace();
}
System.out.println("newCompressive" + quality + "质量打包完成"
+ getCurrentTime() + "----lenth----"
+ byteArrayOutputStream.toByteArray().length);
return byteArrayOutputStream.toByteArray();
}
public static void main(String args[]) throws Exception {
ImageSizeTest test = new ImageSizeTest();
test.getImageSize();
}
}
测试结果依然不理想
突发奇想
GIF采用的是LZW编码进行压缩
JPEG后期的熵编码用的是Huffman,那如果先进行JPEG算法,再进行LZW算法,会有什么样的效果呢?
想干就干
咱写代码来测试一下
测试代码如下
package cn.mzd.newIM.test;
import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import sun.awt.image.JPEGImageDecoder;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
public class ImageSizeTest {
/**
* @param args
* @throws AWTException
*/
public void getImageSize() throws AWTException {
java.awt.Robot rb = new java.awt.Robot();
Dimension d = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
Rectangle rt = new Rectangle(0, 0, (int) d.getWidth(), (int) d
.getHeight());
for (int i = 0; i < 1000; i++) {
BufferedImage image = rb.createScreenCapture(rt);
// bufferedImageTobytes(image, "gif");
giftest(bufferedImageTobytes(image, "jpeg"));
giftest(bufferedImageTobytes(image, 0.2f));
giftest(newCompressImage(image, 0.2f));
}
}
/**
* 用Format对应格式中ImageIO默认参数把IMAGE打包成BYTE[]
*
* @param image
* @return
*/
private byte[] bufferedImageTobytes(BufferedImage image, String format) {
System.out.println(format + "格式开始打包" + getCurrentTime());
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
ImageIO.write(image, format, out);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(format + "格式完成打包-----" + getCurrentTime()
+ "----lenth------" + out.toByteArray().length);
return out.toByteArray();
}
/**
*
* 自己设置压缩质量来把图片压缩成byte[]
*
* @param image
* 压缩源图片
* @param quality
* 压缩质量,在0-1之间,
* @return 返回的字节数组
*/
private byte[] bufferedImageTobytes(BufferedImage image, float quality) {
System.out.println("jpeg" + quality + "质量开始打包" + getCurrentTime());
// 如果图片空,返回空
if (image == null) {
return null;
}
// 得到指定Format图片的writer
Iterator<ImageWriter> iter = ImageIO
.getImageWritersByFormatName("jpeg");// 得到迭代器
ImageWriter writer = (ImageWriter) iter.next(); // 得到writer
// 得到指定writer的输出参数设置(ImageWriteParam )
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 设置可否压缩
iwp.setCompressionQuality(quality); // 设置压缩质量参数
iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
ColorModel colorModel = ColorModel.getRGBdefault();
// 指定压缩时使用的色彩模式
iwp.setDestinationType(new javax.imageio.ImageTypeSpecifier(colorModel,
colorModel.createCompatibleSampleModel(16, 16)));
// 开始打包图片,写入byte[]
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流
IIOImage iIamge = new IIOImage(image, null, null);
try {
// 此处因为ImageWriter中用来接收write信息的output要求必须是ImageOutput
// 通过ImageIo中的静态方法,得到byteArrayOutputStream的ImageOutput
writer.setOutput(ImageIO
.createImageOutputStream(byteArrayOutputStream));
writer.write(null, iIamge, iwp);
} catch (IOException e) {
System.out.println("write errro");
e.printStackTrace();
}
System.out.println("jpeg" + quality + "质量完成打包-----" + getCurrentTime()
+ "----lenth----" + byteArrayOutputStream.toByteArray().length);
return byteArrayOutputStream.toByteArray();
}
/**
* 自己定义格式,得到当前系统时间
*
* @return
*/
private String getCurrentTime() {
Calendar c = new GregorianCalendar();
int hour = c.get(Calendar.HOUR_OF_DAY);
int min = c.get(Calendar.MINUTE);
int second = c.get(Calendar.SECOND);
int millsecond = c.get(Calendar.MILLISECOND);
String time = hour + "点" + min + "分" + second + "秒" + millsecond;
return time;
}
/**
* 通过 com.sun.image.codec.jpeg.JPEGCodec提供的编码器来实现图像压缩
*
* @param image
* @param quality
* @return
*/
private byte[] newCompressImage(BufferedImage image, float quality) {
// 如果图片空,返回空
if (image == null) {
return null;
}
// 开始开始,写入byte[]
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流
// 设置压缩参数
JPEGEncodeParam param = JPEGCodec.getDefaultJPEGEncodeParam(image);
param.setQuality(quality, false);
// 设置编码器
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(
byteArrayOutputStream, param);
System.out.println("newCompressive" + quality + "质量开始打包"
+ getCurrentTime());
try {
encoder.encode(image);
} catch (Exception ef) {
ef.printStackTrace();
}
System.out.println("newCompressive" + quality + "质量打包完成"
+ getCurrentTime() + "----lenth----"
+ byteArrayOutputStream.toByteArray().length);
return byteArrayOutputStream.toByteArray();
}
/**
* 测试把图片先压缩成JPEG,再用JPEG压缩成GIF
*/
public byte[] giftest(byte[] imagedata) {
System.out.println("giftest开始打包" + getCurrentTime());
BufferedImage image = null;
ByteArrayInputStream input = new ByteArrayInputStream(imagedata);
// 得到解码器
JPEGImageDecoder decoder = (JPEGImageDecoder) JPEGCodec
.createJPEGDecoder(input);
// 把JPEG 数据流解压缩
try {
image = ((com.sun.image.codec.jpeg.JPEGImageDecoder) decoder)
.decodeAsBufferedImage();
} catch (Exception ef) {
ef.printStackTrace();
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
ImageIO.write(image, "gif", out);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("giftest开始打包" + getCurrentTime() + "----lenth----"
+ out.toByteArray().length);
return out.toByteArray();
}
public static void main(String args[]) throws Exception {
ImageSizeTest test = new ImageSizeTest();
test.getImageSize();
}
}
测试结果就补贴了
发现,对于默认的JPEG参数压缩,GIF能二次压缩到90K左右(类似之间GIF压缩)
而对于自己设定参数的压缩,当质量很高时(高于0.5),GIF效果还是有的
当质量很低时(低于0.1)再进行GIF压缩,大小反而变大-------------------分布均匀
此次试验再次宣告失败
难道我们的监控系统就不能用UDP来实现了吗?
虽然通过压缩图片直接打到保证图片质量和要求大小小于64KB的试验失败了,但是我们还有其他的办法
我们要始终相信灰太狼的那句“我还会再回来的”
具体怎么实现呢?
我想,思路如下----------把图片数据分包,用UDP广播分包后的消息,每个数据包内容里有属于第几个包的第几块
UDP的不可靠性,以及根据概率学知识,大家都知道分的包越多,越危险,于是,我们还是得先对图像进行压缩,再分包,再传送,尽量少分包
在接收方,用一个缓冲区来接收数据包。根本数据包内容中的标识来组装包,根据实时性要求,当接收消息超过3秒还未收到兄弟包,在丢弃。
别以为在这里我要用UDP来实现类型TCP的差错重传和确认机制,事情还没糟糕到需要做那一步。
分享到:
相关推荐
**2.1.3 小结** - 数据结构的选择对程序的效率至关重要。 ##### 2.2 算法及性能分析 **2.2.1 算法** - **定义**:算法是解决问题的一系列步骤。 - **特性**:包括输入、输出、确定性、有限性、可行性。 **2.2.2...
- Spill前进行二次排序,先按Partition排序,再按Key排序。 - 运行Combiner(如果配置),对写入磁盘的数据进行预处理,以减少磁盘I/O。 - 最终将多个Spill文件通过多路归并算法合并成一个文件。 - **Reduce端的...
- **小结** - 数据结构的选择对算法效率有很大影响。合理选择数据结构可以简化问题的解决过程。 - **算法及性能分析** - **算法** - 算法是一系列解决问题的清晰指令集。一个好的算法应该具有正确性、可读性、...
**2.1.3 小结** - 数据结构是算法的基础,合理选择数据结构可以显著提高算法的效率。 ##### 2.2 算法及性能分析 **2.2.1 算法** - **算法**:是解决问题的一系列步骤或指令的有序集合。 **2.2.2 时间复杂性** ...
- **小结**:总结数据结构学习的重点,为后续章节做铺垫。 - **算法及性能分析** - **算法**:阐述算法的概念,包括算法的五大特性(可行性、确定性、有穷性、输入、输出)。 - **时间复杂性**:讲解时间复杂性...
- **递归的实现与堆栈**:递归调用会在堆栈中保存每一次调用的信息,直至达到递归基为止。 **5.2 基于归纳的递归** - **基于归纳的递归**:通过归纳法来确定递归函数的形式。 **5.3 递推关系求解** - **求解递推...
- **小结** - 数据结构的选择直接影响到算法的效率。 **2.2 算法及性能分析** - **算法** - 算法是对特定问题求解步骤的一种描述。 - 设计原则包括正确性、可读性、健壮性和效率。 - **时间复杂性** - 大O...
Karen Morton及其团队在本书中提供了专业的方案:先掌握语言特性,再学习Oracle为提升语言效率而加入的支持特性,进而将两者综合考虑并在工作中加以应用。作者通过总结各自多年的软件开发和教学培训经验,与大家...
一次翻新,改版后采用了六宫格风格,每个重要的功能一目了然、触手可及。在六个功能按钮的上方照例显示着输入框,用户可以手动输入中文或英文。就在这个输入框的一旁,是新增的语音识别功能。 [8] 在默认情况下,...