图片处理——关于图片压缩的调研(1)


背景

近期在图片转写过程中,引擎为了保证资源使用,对识别的图片进行了大小限制,因此部分超限的图片将不能进行识别,所以为解决此问题

尝试寻找一种可控制的不改变图片大小(长度、宽度)的压缩方案。

难点

1、压缩大小必须是可控的,压缩后尽量接近在限制大小,保证引擎识别的准确度

2、图片存在远程服务器,图片下载到jvm内存中后,不在进行转存,直接进行压缩后输出给引擎,保证效率

调研

javax.imageio 和 thumbnailator

imageio是JDK自带的图片处理工具
thumbnailator 是Google提供的一款开源图片处理Java类库,目前已停止维护

这里验证的图片:1.jpg,存储大小为5.78MB

thumbnailator获取图片并压缩

public  byte[] compressImgByThumbnails(String imgUrl ,float quality){
System.out.println("compressImgByThumbnails----begin--------------quality:"+quality);
byte[] afterBuff = null;
try{
BufferedImage compressBuff = Thumbnails.of(new URL(imgUrl)).scale(1f).outputQuality(quality).asBufferedImage();
byte[] beforeBuff = getByteArrayFromBufferedImage(compressBuff);
System.out.println("beforeBuff:"+(beforeBuff.length/1024f/1024f));
if(beforeBuff.length <0){//判断是否超限,这里为验证压缩设置为0
return beforeBuff;
}
//压缩图片
ByteArrayOutputStream os=new ByteArrayOutputStream();
Thumbnails.of(new URL(imgUrl)).scale(1f).outputQuality(quality).toOutputStream(os);
afterBuff = os.toByteArray();
os.close();
System.out.println("afterBuff:"+(afterBuff.length/1024f/1024f));
}catch (Exception e){
log.error("图片下载异常{}",e);
}
System.out.println("compressImgByThumbnails----end--------------quality:"+quality);
return afterBuff;
}

 获取输出

beforeBuff 为2.59MB ,多次压缩之后(第一行为quality值,第二行为压缩后大小,单位是MB),十次压缩耗时为28.266秒

ImageIo获取图片并压缩

private byte[] compressImgByImageIO(String imgUrl ,float quality){
System.out.println("compressImgByImageIO----begen--------------quality:"+quality);
byte[] afterBuff = null;
try{
BufferedImage buff = ImageIO.read(new URL(imgUrl));
byte[] beforeBuff = getByteArrayFromBufferedImage(buff);
System.out.println("beforeBuff:"+(beforeBuff.length/1024f/1024f));
// 指定写图片的方式为 jpg
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 先指定Output,才能调用writer.write方法
ImageOutputStream ios = ImageIO.createImageOutputStream(out);
writer.setOutput(ios);

ImageWriteParam param = writer.getDefaultWriteParam();
if (param.canWriteCompressed()){
// 指定压缩方式为MODE_EXPLICIT
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
// 压缩程度,参数qality是取值0~1范围内
param.setCompressionQuality(quality);
}
// 调用write方法,向输入流写图片
writer.write(null, new IIOImage(buff, null, null), param);
afterBuff = out.toByteArray();
out.close();
ios.close();
writer.dispose();
System.out.println("afterBuff:"+(afterBuff.length/1024f/1024f));
}catch (Exception e){
log.error("图片下载异常{}",e);
}
System.out.println("compressImgByImageIO----end--------------quality:"+quality);
return afterBuff;
}

获取输出

beforeBuff 为2.59MB ,多次压缩之后(第一行为quality值,第二行为压缩后大小,单位是MB),十次压缩耗时为16.601秒

分析

从二者对比上看,读取图片时,都对图片进行了压缩(原文件5.78MB,读取后2.59MB),且压缩比率未知;

quality对文件大小的影响是逐渐变小的,如下图:

针对以上点,读取文件来判断是否压缩,不能使用上面方法,需进行改造这里使用的是HttpClient

public static byte[] doGet(String url) {
try {
HttpClient client = HttpClients.createDefault();
//发送get请求
HttpGet request = new HttpGet(url);
HttpResponse response = client.execute(request);

/**请求发送成功,并得到响应**/
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
/**读取服务器返回过来的json字符串数据**/
HttpEntity entity = response.getEntity();
byte[] imgBytes = EntityUtils.toByteArray(entity);
return imgBytes;
}
} catch (IOException e) {
e.printStackTrace();
}

return null;
}

获取到的字节数组如果大小超限需要进行压缩,这里选用ImageIO来处理压缩,因为速度更快,改造如下
遍历循环,直到大小符合要求位置

public byte[] compressImg(String imgUrl,int limitSize){
byte[] beforeBuff = HttpUtil.doGet(imgUrl);
if(beforeBuff.length return beforeBuff;
}else{
float quality = 1f;
byte[] result = null;
while (quality>0.09f){
result = compressImgByImageIO(imgUrl ,quality);
if(result.length quality = 0f;
}else {
quality-=0.1f;
}
}
return result;
}
}


private byte[] compressImgByImageIO(String imgUrl ,float quality){
System.out.println("compressImgByImageIO------------------quality:"+quality);
byte[] afterBuff = null;
try{
// 指定写图片的方式为 jpg
BufferedImage buff = ImageIO.read(new URL(imgUrl));
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 先指定Output,才能调用writer.write方法
ImageOutputStream ios = ImageIO.createImageOutputStream(out);
writer.setOutput(ios);
ImageWriteParam param = writer.getDefaultWriteParam();
if (param.canWriteCompressed()){
// 指定压缩方式为MODE_EXPLICIT
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
// 压缩程度,参数qality是取值0~1范围内
param.setCompressionQuality(quality);
}
// 调用write方法,向输入流写图片
writer.write(null, new IIOImage(buff, null, null), param);
afterBuff = out.toByteArray();
out.close();
ios.close();
writer.dispose();
System.out.println("afterBuff:"+(afterBuff.length/1024f/1024f));
}catch (Exception e){
log.error("图片下载异常{}",e);
}

return afterBuff;
}

功能达成,但是效率不高,还需优化