专栏名称: 骑着jm的hi
软件开发工程师
今天看啥  ›  专栏  ›  骑着jm的hi

质量监控-图片减包

骑着jm的hi  · 掘金  · 后端  · 2018-12-11 14:00
阅读 43

质量监控-图片减包

文章链接

经过多个版本迭代,项目在release配置下的打包体积依旧轻松破百,应用体积过大导致的问题包括:

  • 更长的构建时间,换个词就是加班
  • TEXT段体积过大会导致审核失败
  • 用户不愿意下载应用

通常来说,资源文件能在应用体积包中占据1/3或者更多的体积,相比起代码(5kb/千行)的平均占用来说,对图片进行减包是最直接高效的手段,对图片资源的处理方式包括四种:

  1. 通过请求下载大图
  2. 使用工具压缩图片
  3. 查找删除重复图片
  4. 查找复用相似图片

考虑到由于项目开发分工的问题,方式1需要推动落地,所以本文不讨论这种处理方式。其他三种都能通过编写脚本实现自动化处理

图片压缩

图片压缩分为有损压缩无损压缩两类,有损压缩放弃了一部分图片的质量换取更高的压缩比。网上主流的压缩工具有tinypngpngquantImageAlphaImageOptim等,分别采用了一种或者多种压缩技术完成图片压缩

为什么png能够无损压缩

由于png格式的灵活性,同一张图片可以使用多种方式进行表示,不同方式占用的大小不一样。一般的软件会采用效率更高的方式来表示图片,所以这种情况下png图片存在巨大的优化空间。通常来说,从png文件中能去除的数据包括:

  • iTXttEXtzTXt这些可以存储任意文本的数据区段
  • iCCP数据区段存储的profile等等
  • photoshop导出的png图片存在大量的额外信息

png图片有两种类型的数据块,一种是必不可缺的数据块称为关键数据块。另一种叫做辅助数据块png文件格式规范指定的辅助数据块包括:

  • 背景颜色数据块bKGD
  • 基色和白色数据块cHRM
  • 图像γ数据块gAMA
  • 图像直方图数据块hIST
  • 物理像素尺寸数据块pHYs
  • 样本有效位数据块sBIT
  • 文本信息数据块tEXt
  • 图像最后修改时间数据块tIME
  • 图像透明数据块tRNS
  • 压缩文本数据块zTXt

其中tEXtzTXt数据段中存在的数据包括:

关键字
Title 图像名称
Author 图像作者
Description 图像说明
Copyright 版权声明
CreationTime 原图创作时间
Software 创作图像使用的软件
Disclaimer 弃权
Warning 图像内容警告
Source 创作图像使用的设备
Comment 注释信息

由上可见,辅助数据块在png文件中可能占据了极大的篇幅,正是这些数据块构成了png的无损压缩条件

tinypng

tinypng采用了一种称作Quantization的压缩技术,通过合并图片中相似的颜色,将24bit的图片文件压缩成8bit图片,同时去除图片中不必要的元数据,图片最高能达到70%以上的压缩率。截止文章完成之前,tinypng仅提供了线上压缩功能,暂未提供工具下载

pngquant

根据官方介绍,pngquant24bit以上的图片转换成8bit的保留透明度通道的压缩图片,压缩算法的压缩比非常显著,通常都能减少70%的大小。pngquant提供了命令行工具来完成解压任务:

pngquant --quality=0-100 imagepath
复制代码

命令行更多调试参数可以在官网参阅

ImageAlpha

ImageAlpha是一个macOS系统下的有损图片压缩工具,内置了pngquantpngnq-s9等多个压缩工具,多数情况下通过将图片降至8bit来获取高压缩比。由于ImageAlpha的可视化界面无法批量处理图片,直接使用提供的命令工具可以实现批量压缩图片:

for file in $(ls $1); do
    imagepath=$1"/"$file
    if [ -d imagepath ]
    then
        /// 路径为文件夹
    else
        if [[ $file == *.png ]]
        then
            beforeSize=`ls -l $imagepath | awk '{print $5}'`
            /Applications/ImageAlpha.app/Contents/MacOS/pngquant $imagepath
            afterSize=`ls -l ${imagepath/.png/-fs8.png} | awk '{print $5}'`
            
            if [[ $afterSize -lt $beforeSize]]
            then
                mv ${imagepath/.png/-fs8.png} $imagepath
            fi
        fi
    fi
done
复制代码

使用ImageAlpha需要注意两点:

  1. 压缩后的图片命名会自动添加-fs8后缀,需要使用mv命令实现替换
  2. 有损压缩会修改关键数据块,可能导致压缩图片尺寸增大,需要过滤

在使用有损压缩时需要注意单张png图片是可以被多次压缩的,但这会导致图片的清晰度和色彩都受到影响,不建议对图片超过一次以上的有损压缩

ImageOptim

ImageOptim是介绍的四种工具中唯一的无损压缩,它采用了包括去除exif信息重新排列像素存储方式等手段实现图片的压缩。无损代表着一张图片被ImageOptim压缩后,后续无法再次进行压缩,同时它的压缩比往往比不上其他的有损压缩方案,但最大程度上保证了图片的原始清晰度和色彩

for file in $(ls $1); do
    imagepath=$1"/"$file
    if [ -d imagepath ]
    then
        /// 路径为文件夹
    else
        if [[ $file == *.png ]]
        then
            /Applications/ImageOptim.app/Contents/MacOS/ImageOptim $imagepath
        fi
    fi
done
复制代码

ImageOptim同样存在可视化的工具并且支持批量压缩图片

多方案对比

考虑到ImageAlpha几乎都是使用pngquant作为压缩工具,因此只列出三种压缩工具的对比:

原始尺寸 压缩工具 压缩后尺寸 压缩比
319.5KB tinypng 120.5KB 62%
319.5KB ImageAlpha-pngquant 395KB -24%
319.5KB ImageOptim 252KB 21%

测试图片采用qq聊天截图生成的pngtinypng压缩率非常高,而pngquant的表现不尽人意

删除重复图片

通常来说,出现重复图片的原因包括模块间需求开发没有打通或是缺少统一的图片命名规范。通过图片MD5摘要是识别重复图片的最快方法,以python为例,匹配重复图片的代码如下:

md5list = {}
for file in files:
    if os.path.isdir(file.path):
        continue
        
    md5obj = hashlib.md5()
    fd = open(file.path, 'rb')
    while True:
        buff = fd.read(2048)
        if not buff:
            break
        md5obj.update(buff)
    fd.close()
    
    filemd5 = str(md5obj.hexdigest()).lower()
    if filemd5 in md5list:
        md5list[filemd5].add(file.path)
    else:
        md5list[filemd5] = set([file.path])
        
for key in md5list:
    list = md5list[key]
    if len(list) > 1:
        print (list)
复制代码

在遍历中以文件MD5字符串作为key,维护具备相同MD5的图片路径,最后遍历这个map查找存在一个以上路径的数组并且输出

寻找相似图片

相似图片在图片内容、色彩上都十分的接近,多数时间可以考虑复用这些图片,但相似图片的问题在于无法通过MD5直接匹配。为了确认两个图片是否相似,要使用简单的一个数学公式来帮忙查找:

方差。在概率论和统计学中,一个随机变量的方差描述的是它的离散程度,也就是该变量离其期望值的距离

举个例子,甲同学五次成绩分别是65, 69, 81, 89, 96,乙同学五次成绩是82, 80, 77, 81, 80,两个人平均成绩都是80,但是引入方差公式计算:

甲: ((65-80)^2 + (69-80)^2 + (81-80)^2 + (89-80)^2 + (96-80)^2) / 5 = 136.8
乙: ((82-80)^2 + (80-80)^2 + (77-80)^2 + (81-80)^2 + (80-80)^2) / 5 = 2.8
复制代码

平均值相同的情况下,方差越大,说明数据偏离期望值的情况越严重。方差越接近的两个随机变量,他们的变化就越加趋同,获取方差代码如下:

def getVariance(nums):
    variance = 0
    average = sum(nums) / len(nums)
    for num in nums:
        variance += (num - average) * (num - average) / len(nums)
    return variance
复制代码

因此将图片划分成连串的一维数据,以此计算出图片的方差,通过方差匹配可以实现一个简单的图片相似度判断工具,实现前还要注意两点:

  1. 图片RGB色彩值会导致方差的计算变得复杂,所以转成灰度图可以降低难度
  2. 不同尺寸需要缩放到相同尺寸进行计算

最终将图片转换成一维数据列表的代码如下:

def getAverageList(img):
    commonlength = 30
    img = cv2.resize(img, (commonlength, commonlength), interpolation=cv2.INTER_CUBIC)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    res = []
    for idx in range(commonlength):
        average = sum(gray[idx]) / len(gray[idx])
        res.append(average)
复制代码

将图片转成灰度图后,仍然可能存在RGB色值不同但灰度值相同的情况导致判断失准,可以考虑两种方案提高算法的检测准确率:

  1. 在不修改以灰度值计算方差的方案下,构建以列平均像素值为单位的一维列表计算另一个方差,两个方差值一并做判断
  2. 摒弃灰度值方差方案,每一行分别生成RGB三种色彩平均值的一维列表,计算出三个方差进行匹配检测

效果

经过两轮图片减包处理后,整个项目资源产生的减包量约有20M,其中通过文中的三种手段产生的减包量在6.5M左右,整体上来看产出还是比较可观的

关注我的公众号获取更新信息




原文地址:访问原文地址
快照地址: 访问文章快照