Java实现DHash批量图片去重

# 一、

用node.js爬虫爬了不少图片之后,尽管经过了简单的处理,但是图片重复率还是很高的。

本来打算用node继续写一个去重脚本,可是node处理图片的API文档不太足,也不是太简单,自己也不会用,还是回到了java写一个class一下吧。

对于图片来说,暴力hash算法是不可取的,因为只要有一个小像素点的改变,就导致不一样的hash。

网上查验得知可以用DHash算法,一种相对Hash值的算法进行检验去重。于是参照一篇别人的python计算DHash值的代码。

写了一个批量去重的java文件

# 二、代码如下

package util;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;

/**
 * DHash 算法对图片去重
 *
 * @author a9043
 */
public class DHashUtil {
    /**
     * 计算dHash方法
     *
     * @param file 文件
     * @return hash
     */
    private static long getDHash(File file) {
        //读取文件
        BufferedImage srcImage;
        try {
            srcImage = ImageIO.read(file);
        } catch (IOException e) {
            e.printStackTrace();
            return -1;
        }

        //文件转成9*8像素,为算法比较通用的长宽
        BufferedImage buffImg = new BufferedImage(9, 8, BufferedImage.TYPE_INT_RGB);
        buffImg.getGraphics().drawImage(
                srcImage.getScaledInstance(9, 8, Image.SCALE_SMOOTH), 0,
                0, null);

        //图片灰度化,灰度和加亮算法是网上找的,本人不懂图像
        int width = buffImg.getWidth();
        int height = buffImg.getHeight();
        int[] grayPix = new int[width * height];
        int i = 0;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int rgb = buffImg.getRGB(x, y);
                int r = rgb >> 16 & 0xff;
                int g = rgb >> 8 & 0xff;
                int b = rgb & 0xff;
                int gray = (r * 30 + g * 59 + b * 11) / 100;
                grayPix[i++] = gray;
            }
        }

        //开始计算dHash 总共有9*8像素 每行相对有8个差异值 总共有 8*8=64 个
        long figure = 0;
        for (i = 0; i < 63; i++) {
            long bit = grayPix[i] > grayPix[i + 1] ? 1 : 0;
            figure |= bit << i;
        }
        return figure;
    }

    /**
     * 计算海明距离, 计网接触过的
     * <p>
     * 原本用于编码的检错和纠错的一个算法
     * 现在拿来计算相似度,如果差异值小于一定阈值则相似
     *
     * @param x hash
     * @param y hash
     * @return 距离
     */
    private static long getHammingDistance(long x, long y) {
        int cnt = 0;
        x = x ^ y;
        while (x != 0) {
            if (1 == (x & 0x01)) {
                cnt++;
            }
            x = x >> 1;
        }
        return cnt;
    }

    /**
     * 去重函数
     *
     * @param files 文件列表
     */
    private static void doDistinct(File[] files) {
        //以File作Key, hash作val 新建一个map
        Map<File, Long> hashMap = Arrays
                .stream(files)
                .parallel()
                .collect(Collectors.toMap(File::getAbsoluteFile, DHashUtil::getDHash));

        //有效的hashList
        List<Long> hashList = new ArrayList<>();

        //去重循环
        for (Iterator<Map.Entry<File, Long>> it = hashMap.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<File, Long> entry = it.next();
            //如果判定为重复,则移除map且删除
            if (entry.getValue() != -1 && hashList.parallelStream().anyMatch(hash -> getHammingDistance(entry.getValue(), hash) < 10)) {
                it.remove();
                entry.getKey().deleteOnExit();
                return;
            }
            //不重复/第一次 直接加入hashList
            hashList.add(entry.getValue());
        }
    }

    //DHashUtil 参数值为待处理文件夹
    public static void main(String[] args) {
        File file = new File(args[0]);
        if (file.isDirectory()) {
            doDistinct(file.listFiles());
        }
    }
}