图片压缩 in Android

本文深入探讨了Android平台上的图片压缩技术,包括压缩算法、图片格式选择、质量压缩策略、图片缩放的细节,以及JPEG压缩算法的改进。特别讨论了如何在Android中实现双线性插值以减少图像失真,并介绍了图像质量评价标准如Butteraugli和SSIM。此外,还涉及Exif信息的读写和相关代码库的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

图片压缩

背景

成像系统两大核心: 镜头+感光元器件

底大一级压死人

image-20210330093604413

小米11发布会上:

image-20210330093733661

img

一般全画幅单反/微单相机,厂家设置的有效像素为2400万像素 cmos面积:864mm2 每平方毫米上2.78个像素

asp-c 有效像素为2400万像素 cmos面积 332.3mm2 每平方毫米上7.23个像素

华为mate40 pro 1/1.28英寸 5000万像素,常用1000w像素 cmos面积192.11mm2 每平方毫米26个/5.2个像素

如果按全画幅单反的解析度,华为mate40 pro 应该配500万像素,而不是5000万.

小米10 pro 1/1.33英寸 (15.28mmx11.46mm) 一亿像素 cmos面积175.09mm2 每平方毫米上57个像素

(1英寸约等于25.4mm)

从数值上看,手机1000w像素拍出来的放到最大,效果应该和asp-c 2400万像素放到最大的效果差不多

but: 细节见功夫

放大到100%, 手机拍照的效果比相机差太多了.不是一个档次

压缩算法/图片格式

gif:动图格式,兼容性好,画面质量较差.

png: 可带透明通道.文件偏大.无损压缩.

jpeg:无与伦比的兼容性,非常不错的压缩比. 有损压缩.

webp: 谷歌主推,没有流行起来

Guetzli: 谷歌出的玩具算法,以更高的压缩比压成jpg格式,但极度耗内存和cpu,基本不具有工业价值.

hevc/heif: h.265对应的图片压缩算法,有版权问题. 腹死胎中,只有苹果玩了几个版本.

avif: h.266对应的图片压缩算法,全开源. 压缩比超高,不惧反复压缩.各巨头火热开发中,很有前景.

质量压缩

bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);

quality: 取值0-100.

一般来说,手机/相机拍照的质量为96,98.

其实肉眼看,80和100并无明显差别.

用于存硬盘收藏的图片,推荐80-90

用于聊天,晒朋友圈之类的上传,推荐70

用于跑算法模型的上传,具体看算法的要求. 可以使用80-90的webp

image-20210322173812647

0-婺源2

image-20210322174122498

image-20210322174142524

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dnEqLofi-1625658189921)(/Users/hss/Library/Application Support/typora-user-images/image-20210322174334485.png)]

image-20210322174356037

image-20210322174413216

image-20210322174430404

image-20210322174543208

image-20210322181148076

图片的缩与放

向下采样 向上插值

相关算法:

  • 单线性/最近邻: 速度最快,但可能失真,有明显锯齿

  • 双线性

  • 双三次/双立方

  • Lanczos

其中,Android只实现了单线性和双线性.

且直接读流的向下采样时,只支持单线性采样.

缩小: 向下采样:

image-20210322192213318

放大: 向上插值

image-20210322192256909

image-20210322192314640

Android里的单线性/双线性操作:

从流中解码bitmap:

BitmapFactory.Options options = new BitmapFactory.Options();
//或者 inDensity 搭配 inTargetDensity 使用,算法和 inSampleSize 一样
options.inSampleSize = 2;//向下采样,仅支持单线性采样
Bitmap compress = BitmapFactory.decodeFile("/sdcard/test.png", options);

bitmap的scale操作:

Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png");
Bitmap compress = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true);//最后一个参数 true代表使用双线性,false:使用单线性

或者使用matrix操作:

Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png");
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
bm = Bitmap.createBitmap(bitmap, 0, 0, bit.getWidth(), bit.getHeight(), matrix, true);
//最后一个参数 true代表使用双线性,false:使用单线性

bitmap在view上的绘制:

当把一个bitmap对象绘制到比bitmap尺寸大的view上时,默认使用单线性插值.此时锯齿明显

如果要使用双线性,重写view的onPreDraw(canvas)方法,给canvas加上抗锯齿即可:

canvas.setDrawFilter(new PaintFlagsDrawFilter(0,Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG));

下面是一朵花放大四倍的对比:

image-20210322193343801

image-20210322193358557

更多效果见:QQ音乐团队分享:Android中的图片压缩技术详解(下篇)

在Android上,图片缩放推荐使用双线性

jpeg压缩算法的改进

JPEG压缩算法的过程有:

色彩模型变换(bitmap的RGB转YUV),离散余弦变换、量化、Z字形编码、游程编码、Huffman编码

压缩过程详解

  • Android上开启霍夫曼压缩 : 7.0开始已经自动开启.

  • 自适应的量化表: 不使用jpeg算法内置的两张量化表,而针对特定类型图片使用调教好的量化表

    自适应量化表的 JPEG 压缩技术

    image-20210305101923363

图像质量评价

参考

无参考评价图像质量(主观评价客观化)

指标/维度:

img

此处不关注图片本身质量,只关注jpg压缩过程中量化表所得压缩质量

1 单图质量评价

量化因子相关的质量

可用于避免重复压缩

已知图像和量化表,反向估算量化因子,得到图片质量的估计值.

可使用库: https://github.com/sephiroth74/Android-Exif-Extended 里的

int jpeg_quality =  exif.getQualityGuess()

或者使用如何获取JPEG图片质量和预测压缩图片大小中单独的算法

用c语言从JPEG(文件后缀为.jpg)中提取jpeg压缩的亮度和色度量化矩阵

两个算法的测试结果如下:

实际压缩质量50以上,可准确判断质量,50以下,已无法准确判断.

企业微信截图_c710b92e-db33-4f7e-a485-fe57c4ddf50d

image-20210331111732037

但是,这个准确也是取决于jpg压缩的实际算法,如果使用的是libjpeg,反推出的量化因子就准确.

如果使用的是mozjpeg,那么反推出的质量也会不准确:

比如,使用squoosh里的mozjpeg压缩一张图到75的质量,计算出的质量因子为:

image-20210331111439045

image-20210331111317646

工业上可用的两个质量评价指标

  • 亮度: 计算平均亮度或者ROI的平均亮度, 绝对值可用.

  • 模糊程度(高斯模糊): opencv的拉普拉斯算子计算可得.可用于相对比较.

压缩前后质量比对评价

Butteraugli

Butteraugli 项目是一个图片差异比较库,用于测试图片的心理视觉误差阈值,即查看者开始注意到图片质量下降的点。换句话说,此项目试图量化您的压缩图片的失真程度。

SSIM (Structural SIMilarity) 结构相似性

PNSR一般用于视频质量快速评价 图像质量评价指标之 PSNR 和 SSIM

exif

https://zh.wikipedia.org/wiki/Exif

不仅仅是jpg图片可以有exif,webp也可以有

可交换图像文件格式(英语:Exchangeable image file format,官方简称Exif),是专门为数码相机的照片设定的文件格式,可以记录数码照片的属性信息和拍摄数据。

Exif最初由日本电子工业发展协会在1996年制定,版本为1.0。1998年,升级到2.1版,增加了对音频文件的支持。2002年3月,发表了2.2版。

Exif可以附加于JPEGTIFFRIFF等文件之中,为其增加有关数码相机拍摄信息的内容和索引图或图像处理软件的版本信息。

Windows 7操作系统具备对Exif的原生支持,通过鼠标右键点击图片打开菜单,点击属性并切换到详细信息标签下即可直接查看Exif信息。

Exif信息是可以被任意编辑的,因此只有参考的功能。

Exif信息以0xFFE1作为开头标记,后两个字节表示Exif信息的长度。所以Exif信息最大为64 kB,而内部采用TIFF格式。

读写工具

  • Android: androidx.exifinterface.media.ExifInterface 或者Android-Exif-Extended

  • win/Mac: 命令行工具-可跨平台,跨语言使用: exiftool

    题外话: 命令行工具的跨平台,跨语言使用

    ​ Mac/windows/linux java,js,Python,c,c++

    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec("cmd.exe /c dir d:\\");
    //exec(String command, String[] envp, File dir)
    InputStream inputStream = process.getInputStream();
    BufferedReader br = new BufferedReader(new InputStreamReader(inputStream,"gb2312"));
    String line = null;
    while((line = br.readLine()) != null) {
    	System.out.println(line);
    }
    
    //表现形式上,命令行工具接受的string[]参数,与java的main方法入口类似:
    在命令行: java -Xms128m -Xmx256m -jar xxx.jar 
      
    public static void main(string[] args){
      //args: 可以拿到执行java命令时传入的各种参数,对这些参数做出一些处理.
    }
    
    //用java包装命令行工具的方法: 将丰富的参数包装成builder模式
    

python里读写exif而不改变图片数据区如何实现?

思路: 先把整个文件拷过去,然后把exif读到内存,内存修改exif,再把修改后的exif写入到拷贝的文件中,这样就不会改变图片质量了.

不要用PIL,有坑,会改变图片质量

import piexif
from shutil import copyfile
import exifread

original = "IMG_20200628_111551.jpg"
target = "IMG_20200628_111551-3.jpg"
copyfile(original, target)

exif_dict = piexif.load(original)
exif_dict["0th"][piexif.ImageIFD.Copyright] = "lalalla----"

piexif.insert(piexif.dump(exif_dict), target)

# 打印
f = open(target, 'rb')
tags = exifread.process_file(f)
print(tags.__len__())
for tag in tags.keys():
    print(" {}:  {}".format(tag, tags[tag]))

代码库

luban2

thumbnailator

一些应用

app开发中,上传图片

typora写blog时,上传博客图片

https://github.com/JuZiSang/picgo-plugin-compress

扩展阅读

QQ音乐团队分享:Android中的图片压缩技术详解(上篇)

QQ音乐团队分享:Android中的图片压缩技术详解(下篇)

gif图像的压缩

本篇文章由一文多发平台ArtiPub自动发布

Luban(鲁班)——Android图片压缩工具,仿微信朋友圈压缩策略。项目描述目前做app开发总绕不开图片这个元素。但是随着手机拍照分辨率的提升,图片压缩成为一个很重要的问题。单纯对图片进行裁切,压缩已经有很多文章介绍。但是裁切成多少,压缩成多少却很难控制好,裁切过头图片太小,质量压缩过头则显示效果太差。于是自然想到app巨头“微信”会是怎么处理,Luban(鲁班)就是通过在微信朋友圈发送近100张不同分辨率图片,对比原图与微信压缩后的图片逆向推算出来的压缩算法。因为有其他语言也想要实现 Luban,所以描述了一遍算法步骤 因为是逆向推算,效果还没法跟微信一模一样,但是已经很接近微信朋友圈压缩后的效果,具体看以下对比!效果与对比内容原图LubanWechat截屏 720P720*1280,390k720*1280,87k720*1280,56k截屏 1080P1080*1920,2.21M1080*1920,104k1080*1920,112k拍照 13M(4:3)3096*4128,3.12M1548*2064,141k1548*2064,147k拍照 9.6M(16:9)4128*2322,4.64M1032*581,97k1032*581,74k滚动截屏1080*6433,1.56M1080*6433,351k1080*6433,482k导入compile 'io.reactivex:rxandroid:1.2.1' compile 'io.reactivex:rxjava:1.1.6' compile 'top.zibin:Luban:1.0.5'使用Listener方式Luban内部采用io线程进行图片压缩,外部调用只需设置好结果监听即可Luban.get(this)     .load(File)                     //传人要压缩图片     .putGear(Luban.THIRD_GEAR)      //设定压缩档次,默认三挡     .setCompressListener(new OnCompressListener() { //设置回调         @Override         public void onStart() {             //TODO 压缩开始前调用,可以在方法内启动 loading UI         }         @Override         public void onSuccess(File file) {             //TODO 压缩成功后调用,返回压缩后的图片文件         }         @Override         public void onError(Throwable e) {             //TODO 当压缩过去出现问题时调用         }     }).launch();    //启动压缩RxJava方式RxJava 调用方式请自行随意控制线程Luban.get(this)         .load(file)         .putGear(Luban.THIRD_GEAR)         .asObservable()         .subscribeOn(Schedulers.io())         .observeOn(AndroidSchedulers.mainThread())         .doOnError(new Action1() {             @Override             public void call(Throwable throwable) {                 throwable.printStackTrace();             }         })         .onErrorResumeNext(new Func1>() {             @Override             public Observable<? extends File> call(Throwable throwable) {                 return Observable.empty();             }         })         .subscribe(new Action1() {             @Override             public void call(File file) {                 //TODO 压缩成功后调用,返回压缩后的图片文件             }         }); 标签:Luban(鲁班)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值