先做一下需求分析,主要是包含两个方面,一个是开发原因,一个是要的东西。

我本人有收藏图片的习惯,无论是日常生活照片,还是表情包,亦或是美图,梗图,还有见不得人的色图。长年累月下来,图片就屯了很多,大部分我平时都不会去看......其中不乏有一些重复的(指看过并保存了,下次见到时又保存了一遍),高相似度的(差分图片,连拍照片)图片,这些图片虽少,但量多起来,占用的空间可不小,相当于我用两倍甚至多倍的存储空间,存一份信息。

因此我需要这样一个软件,能够扫描一个目录下面所有的图片文件(或递归地扫描子目录下面地图片),并找出相似度较高的图片,列出来,并能够允许我预览相似的图片。

搞明白了需求,接下来就着手设计软件UI和算法实现。在此时我还没做调研,看有无现成的软件实现了我的需求。

在程序实现上,我考虑了跨平台性强的go语言的fyne图形界面库,以及Java的FX库。

在算法上,根据问GPT,得到了几个能用的算法,如图像哈希,结构相似性算法(SSIM)。其中图像哈希算法又分为感知哈希,平均哈希,梯度哈希等,基本都是比较两个图片之间的汉明距离来判断相似度。在这些算法的实现上,Java这边有JImageHash库实现图像哈希算法,有OpenCV实现SSIM并提供了Java接口。Go这边不是那么的方便,因此在开发语言上使用了Java。

在UI上,我请了资深前端工程师为我设计了下图所示的UI界面。左边为扫描的图片目录,以及使用的算法,还有判断两张图片相似的阈值;总监显示扫描图片完毕后,被判断为相似的成对图片;右边对图片进行预览。

UI.webp

我的算法实现核心思路是,遍历目录下的每一张图片,在计算得到这张图片的哈希或SSIM后,再遍历其之后的图片并比较,如果有相似的,则做个标记,后续再遍历到这张相似图像时,则跳过,因为已经被标记为跟之前的某张图片相似了。

尽管我还做了些无足轻重的优化,但还是没法让其时间复杂度低于O(n²)。

最终完成了这个软件后,利用了286张图进行性能测试,结果并不是很理想。扫完这么多张图花了数分钟的时间,并且在内存占用上,大约是将所有图片读入内存所需的大小。我猜测是读入图片计算图像哈希后,没有释放掉这些不会再使用的内存。

在我考虑如何改进的时候,我在Github上找到了一个叫 czkawka的开源项目,使用Rust+Fluent实现的,貌似已经把我想干的事情干完了。

czkawka.webp

它具备不少的功能,包括重复文件,空文件等的查找。就我所需要的功能——相似图像查找而言,它的运行效率非常高,只用了数秒就找全了两百八十多张图里面的相似图像,并且内存占用只保持在500多M,相比起我Java的实现,低了很多。

分析一下自己开发的相比起czkawka的不足。

首先是Java语言自身的问题,相比起Rust本身就太重量级了。

然后是图像处理上,我并没有对图像进行预处理,而是逮着一个图开始算哈希,但czkawaka预先使用了调整算法,这或许可能影响查找速度。但事实上我也可以实现类似的方法,比如说在评估两张图片的哈希之前,可以先比较一下它们的长宽比,如果长宽比截然不同,更极端地说,一张是横向图,一张是纵向图,这绝对就不同,没有比的必要。单纯获取长宽比,肯定比计算图像哈希和汉明距离要快得多。

在图片相似度比较上,czkawka只用了图像哈希算法,没有使用SSIM算法。我使用SSIM还需要编译和捆绑OpenCV的库,才能调用SSIM算法。我注意到czkawka的图像哈希位数最大只能设置64位,我自己编写的程序使用的哈希位数用到了128甚至256......我是啥比,我以为位数越高判定相似得越精确

所以总结一下,在做什么东西之前,最好先找找有没有开源的实现。但凡有了,都不用自己浪费精力去做,还技不如人,做出一个残次品......

我能写吗?写不了,没这个能力知道吗?

没这个能力.webp