多平台图片相似度检测算法

多平台图片相似度检测算法

  • 甲方最终选择的是:平均值检测算法

Android

  • 使用的是 opencv-3.4.15.aar
            try
            {
                AssetManager assets = mContext.getAssets();
                String[] images = assets.list("xx");
                //Collections.sort(images);

                Map<String, BigInteger> fileNameAndAHashMap = new HashMap<>();
                for (int indexA = 0; indexA < images.length; indexA++)
                {
                    String childFileName = images[indexA];
                    File tempFileA = new File(getCacheDir(), childFileName);
                    if (!tempFileA.exists())
                        try (FileOutputStream stream = new FileOutputStream(tempFileA))
                        {
                            stream.write(StreamUtils.readAllBytes(assets.open("xx/" + childFileName)));
                        }
                    BigInteger averageHashA = TakePhotoCheckerUtils.getAverageHash(tempFileA.getAbsolutePath());
                    fileNameAndAHashMap.put(childFileName, averageHashA);
                }

                for (int indexA = 0; indexA < images.length; indexA++)
                {
                    String baseFileName = images[indexA];
                    BigInteger AHashBase = fileNameAndAHashMap.get(baseFileName);
                    for (int indexB = indexA + 1; indexB < images.length; indexB++)
                    {
                        String otherFileName = images[indexB];
                        BigInteger AHashOther = fileNameAndAHashMap.get(otherFileName);
                        String distance = "";
                        String vsName = (baseFileName) + " vs " + (otherFileName);
                        LogEx.e("XXXXX"
                                , "AHash汉明距离=", distance
                                //值越小,相似度越高,取值为0-64,即汉明距离中,64位的hash值有多少不同
                                , "相似度=", TakePhotoCheckerUtils.getSimilarityPercentage(AHashBase, AHashOther)
                                , "文件名=", vsName
                        );
                    }
                }
            }
            catch (Exception e)
            {
                LogEx.e(TAG, "", e);
            }

    /**
     * 算法1: 使用 opencv-contrib 里的imageHash.AverageHash;缺点就是增加8MB的安装包体积
     * 算法2: 百度的;优点是省了8MB,缺点使用For循环取汉明距离
     * 最终算法3: 参考百度+C#版本的代码 改进了去汉明距离的方法
     * <li><a href="https://hub.fastgit.xyz/jforshee/ImageHashing/blob/master/ImageHashing/ImageHashing.cs">ImageHashing/ImageHashing.cs at master · jforshee/ImageHashing · GitHub</a></li>
     */
    public static BigInteger getAverageHash(final String photoPath)
    {
        if (!sIsHadLoadedOpenCV && !OpenCVLoader.initDebug())
        {
            LogEx.w(TAG, FileUtils.getFileNameWithoutExtension(photoPath), "load OpenCV Library Failed.");
            return null;
        }
        sIsHadLoadedOpenCV = true;

        long startTime = SystemClock.elapsedRealtime();
        Mat grayImg = Imgcodecs.imread(photoPath, Imgcodecs.IMREAD_GRAYSCALE);
        final long elapsedTime_imread = SystemClock.elapsedRealtime() - startTime;

        startTime = SystemClock.elapsedRealtime();
        Size size = new Size(8, 8);
        Imgproc.resize(grayImg, grayImg, size);
        final long elapsedTime_resize = SystemClock.elapsedRealtime() - startTime;

        startTime = SystemClock.elapsedRealtime();
        //参考官方的C++实现代码,发现人家求完平均值之后,做了一次round四舍五入.这会导致某些特殊图片刚好踩到临界值时,出现了2%左右的 相似度误差!
        //所以为了保证算法计算出来的值全平台统一,将其改为也四舍五入.减少跨平台数据不一致的情况.特别是后台需要补数据的情况下.这个误差可能还是能感知到.
        //opencv_contrib/average_hash.cpp at master · opencv/opencv_contrib · GitHub
        //https://github.com/opencv/opencv_contrib/blob/master/modules/img_hash/src/average_hash.cpp
        double avg = Math.round(Core.mean(grayImg).val[0]);
        final long elapsedTime_avg = SystemClock.elapsedRealtime() - startTime;

        // Compute the hash: each bit is a pixel
        // 1 = higher than average, 0 = lower than average
        //ImageHashing/ImageHashing.cs at master · jforshee/ImageHashing · GitHub		https://hub.fastgit.xyz/jforshee/ImageHashing/blob/master/ImageHashing/ImageHashing.cs
        //No:C#的原始代码为:ulong hash = 0;对应JAVA的long类型会导致溢出,所以需要使用BigInteger进行运算
        BigInteger hash = BigInteger.ZERO;
        startTime = SystemClock.elapsedRealtime();
        int rows = grayImg.rows();
        int cols = grayImg.cols();
        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
            {
                //LogEx.w(TAG, "grayImg.get(i, j)[0]=", "avg=", avg, i, j, grayImg.get(i, j)[0], grayImg.get(i, j)[0] > avg ? 1 : 0, BigInteger.ONE.shiftLeft(63 - i), hash);
                if (grayImg.get(i, j)[0] > avg)
                    //hash |= (1L << (63 - i));
                    //原始C#代码是已经提前转换了坐标 i = (j + (i * 8)
                    //每一位表示一个像素点是否超过平均值
                    hash = hash.or(BigInteger.ONE.shiftLeft(63 - (j + (i * 8))));
            }
        }
        final long elapsedTime_result = SystemClock.elapsedRealtime() - startTime;
        byte[] bytes = hash.toByteArray();
        LogEx.i(TAG, FileUtils.getFileNameWithoutExtension(photoPath)
                , "avg=", avg
                , "aHash="
                //, hash
                //, Arrays.toString(bytes)
                , String.format("%x", new BigInteger(1, bytes))//Converting Between Byte Arrays and Hexadecimal Strings in Java | Baeldung		https://www.baeldung.com/java-byte-arrays-hex-strings
                , "读取耗时=", elapsedTime_imread
                , "缩小耗时=", elapsedTime_resize
                , "求平均耗时=", elapsedTime_avg
                , "计算耗时=", elapsedTime_result);
        return hash;
    }

    /**
     * Returns a percentage-based similarity value between the two given hashes.
     * The higher the percentage, the closer the hashes are to being identical.
     *
     * @param hash1 The first hash.
     * @param hash2 The second hash.
     * @return The similarity percentage.
     * <li><a href="https://github.com/jforshee/ImageHashing/blob/master/ImageHashing/ImageHashing.cs#L111">ImageHashing/ImageHashing.cs at master · jforshee/ImageHashing · GitHub</a></li>
     */
    public static double getSimilarityPercentage(final BigInteger hash1, final BigInteger hash2)
    {
        //汉明距离的4种思路及JAVA代码_阿良善良的良的博客-CSDN博客		https://blog.csdn.net/qq_43515131/article/details/107962124
        //思路2:内置计算等于 1 的位数函数 return Integer.bitCount(x ^ y);//计算x 异或 y 的 1 的位数
        return ((64 - (hash1.xor(hash2)).bitCount()) * 100) / 64.0;
    }

输出结果:

相似度=81.25 | 文件名=23022816211316629018_1.jpg vs 23022816211316629018_2.jpg
相似度=82.8125 | 文件名=23022816211316629018_1.jpg vs 23022816211316629018_3.jpg
相似度=82.8125 | 文件名=23022816211316629018_1.jpg vs 23022816211316629018_3_SAME.jpg
相似度=42.1875 | 文件名=23022816211316629018_1.jpg vs black.jpg
相似度=45.3125 | 文件名=23022816211316629018_1.jpg vs diff_all.jpg
相似度=42.1875 | 文件名=23022816211316629018_1.jpg vs white.jpg
相似度=79.6875 | 文件名=23022816211316629018_2.jpg vs 23022816211316629018_3.jpg
相似度=79.6875 | 文件名=23022816211316629018_2.jpg vs 23022816211316629018_3_SAME.jpg
相似度=48.4375 | 文件名=23022816211316629018_2.jpg vs black.jpg
相似度=42.1875 | 文件名=23022816211316629018_2.jpg vs diff_all.jpg
相似度=48.4375 | 文件名=23022816211316629018_2.jpg vs white.jpg
相似度=100.0 | 文件名=23022816211316629018_3.jpg vs 23022816211316629018_3_SAME.jpg
相似度=46.875 | 文件名=23022816211316629018_3.jpg vs black.jpg
相似度=37.5 | 文件名=23022816211316629018_3.jpg vs diff_all.jpg
相似度=46.875 | 文件名=23022816211316629018_3.jpg vs white.jpg
相似度=46.875 | 文件名=23022816211316629018_3_SAME.jpg vs black.jpg
相似度=37.5 | 文件名=23022816211316629018_3_SAME.jpg vs diff_all.jpg
相似度=46.875 | 文件名=23022816211316629018_3_SAME.jpg vs white.jpg
相似度=53.125 | 文件名=black.jpg vs diff_all.jpg
相似度=100.0 | 文件名=black.jpg vs white.jpg
相似度=53.125 | 文件名=diff_all.jpg vs white.jpg

.NET


            var images = Directory.GetFiles(Environment.CurrentDirectory, "test_image/*.jpg");
            Array.Sort(images);
            var fileNameAndAHashMap = new System.Collections.Generic.Dictionary<String, Mat>();
            var averageHash = AverageHash.Create();
            var stopWatch = new Stopwatch();
            foreach (var childFileName in images)
            {
                stopWatch.Start();
                var grayImg = Cv2.ImRead(childFileName, ImreadModes.Grayscale);
                stopWatch.Stop();
                var elapsedTimeOfOpenImage = stopWatch.ElapsedMilliseconds;

                stopWatch.Reset();
                stopWatch.Start();
                var matOfByteA = new MatOfByte();
                var outputArray = OutputArray.Create(matOfByteA);
                averageHash.Compute(grayImg, outputArray);
                var aHash = BitConverter.ToString(matOfByteA.ToArray()).Replace("-", "").ToLower();
                stopWatch.Stop();
                var elapsedTimeOfHash = stopWatch.ElapsedMilliseconds;

                fileNameAndAHashMap.Add(childFileName, outputArray.GetMat());

                Console.WriteLine("AHash={1}\t计算耗时(毫秒):{2:N}\t打开文件耗时(毫秒):{3:N}\t\t文件名:{0}"
                    , Path.GetFileName(childFileName)
                    , aHash
                    , elapsedTimeOfOpenImage
                    , elapsedTimeOfHash
                );
            }

            for (var indexA = 0; indexA < images.Length; indexA++)
            {
                var baseFileName = images[indexA];
                var AHashBase = fileNameAndAHashMap[baseFileName];

                for (var indexB = indexA + 1; indexB < images.Length; indexB++)
                {
                    var otherFileName = images[indexB];
                    var AHashOther = fileNameAndAHashMap[otherFileName];
                    var distance = averageHash.Compare(InputArray.Create(AHashBase), InputArray.Create(AHashOther));
                    var vsName = Path.GetFileName(baseFileName) + " vs " + Path.GetFileName(otherFileName);
                    Console.WriteLine("AHash汉明距离={1}\t相似度={2:P}\t\t文件名:{0}"
                        , vsName
                        , distance
                        //值越小,相似度越高,取值为0-64,即汉明距离中,64位的hash值有多少不同
                        , (64 - distance) / 64
                    );
                }
            }

输出结果:

AHash=ffffb7d7808d8098 计算耗时(毫秒):197.00 打开文件耗时(毫秒):36.00 文件名:23022816211316629018_1.jpg
AHash=fffd9fa7a080808c 计算耗时(毫秒):44.00 打开文件耗时(毫秒):0.00 文件名:23022816211316629018_2.jpg
AHash=ffffa7869490809a 计算耗时(毫秒):6.00 打开文件耗时(毫秒):0.00 文件名:23022816211316629018_3.jpg
AHash=ffffa7869490809a 计算耗时(毫秒):6.00 打开文件耗时(毫秒):0.00 文件名:23022816211316629018_3_SAME.jpg
AHash=0000000000000000 计算耗时(毫秒):0.00 打开文件耗时(毫秒):0.00 文件名:black.jpg
AHash=00f4f06b6eff9100 计算耗时(毫秒):4.00 打开文件耗时(毫秒):0.00 文件名:diff_all.jpg
AHash=0000000000000000 计算耗时(毫秒):0.00 打开文件耗时(毫秒):0.00 文件名:white.jpg
AHash汉明距离=12 相似度=81.25% 文件名:23022816211316629018_1.jpg vs 23022816211316629018_2.jpg
AHash汉明距离=11 相似度=82.81% 文件名:23022816211316629018_1.jpg vs 23022816211316629018_3.jpg
AHash汉明距离=11 相似度=82.81% 文件名:23022816211316629018_1.jpg vs 23022816211316629018_3_SAME.jpg
AHash汉明距离=37 相似度=42.19% 文件名:23022816211316629018_1.jpg vs black.jpg
AHash汉明距离=35 相似度=45.31% 文件名:23022816211316629018_1.jpg vs diff_all.jpg
AHash汉明距离=37 相似度=42.19% 文件名:23022816211316629018_1.jpg vs white.jpg
AHash汉明距离=13 相似度=79.69% 文件名:23022816211316629018_2.jpg vs 23022816211316629018_3.jpg
AHash汉明距离=13 相似度=79.69% 文件名:23022816211316629018_2.jpg vs 23022816211316629018_3_SAME.jpg
AHash汉明距离=33 相似度=48.44% 文件名:23022816211316629018_2.jpg vs black.jpg
AHash汉明距离=37 相似度=42.19% 文件名:23022816211316629018_2.jpg vs diff_all.jpg
AHash汉明距离=33 相似度=48.44% 文件名:23022816211316629018_2.jpg vs white.jpg
AHash汉明距离=0 相似度=100.00% 文件名:23022816211316629018_3.jpg vs 23022816211316629018_3_SAME.jpg
AHash汉明距离=34 相似度=46.88% 文件名:23022816211316629018_3.jpg vs black.jpg
AHash汉明距离=40 相似度=37.50% 文件名:23022816211316629018_3.jpg vs diff_all.jpg
AHash汉明距离=34 相似度=46.88% 文件名:23022816211316629018_3.jpg vs white.jpg
AHash汉明距离=34 相似度=46.88% 文件名:23022816211316629018_3_SAME.jpg vs black.jpg
AHash汉明距离=40 相似度=37.50% 文件名:23022816211316629018_3_SAME.jpg vs diff_all.jpg
AHash汉明距离=34 相似度=46.88% 文件名:23022816211316629018_3_SAME.jpg vs white.jpg
AHash汉明距离=30 相似度=53.13% 文件名:black.jpg vs diff_all.jpg
AHash汉明距离=0 相似度=100.00% 文件名:black.jpg vs white.jpg
AHash汉明距离=30 相似度=53.13% 文件名:diff_all.jpg vs white.jpg

Python


import csv
import logging
import os
import time
from binascii import hexlify

import cv2

logging.basicConfig(level=logging.INFO)

default_hash_size = 8

if __name__ == '__main__':
    imgdir_path = 'C:\\Users\\AsionTang\\PyCharmProjects\\YePythonProject\\3zhang'

    fileNamesAndAHashDistanceMap = {}

    # 遍历路径下的所有图像,做质量预检
    all_path = os.listdir(imgdir_path)
    for dirName in all_path:
        # 组 (25) 下的照片比较特殊
        # if '25' not in dirName:
        #     continue

        logging.info('*' * 50)
        logging.info('子目录名称:%s', dirName)

        fileNameAndAHashMap = {}

        # [OpenCV实战]45 基于OpenCV实现图像哈希算法_hash image opencv_落痕的寒假的博客-CSDN博客
        # https://blog.csdn.net/LuohenYJ/article/details/108267229
        # 创建类
        average_hash = cv2.img_hash.AverageHash_create()
        # average_hash = cv2

        dirPath = os.path.join(imgdir_path, dirName)
        allChildFileNames = os.listdir(dirPath)
        for childFileName in allChildFileNames:
            childFilePath = os.path.join(dirPath, childFileName)

            startTime = time.time()
            img = cv2.imread(childFilePath, cv2.IMREAD_GRAYSCALE)
            endTime = time.time()
            elapsedTimeOfOpenImage = (endTime - startTime) * 1000

            # 计算图a的哈希值
            startTime = time.time()
            aHash = average_hash.compute(img)
            endTime = time.time()
            elapsedTimeOfHash = (endTime - startTime) * 1000

            logging.info('AHash=%s\t计算耗时(毫秒):%03.f\t打开文件耗时(毫秒):%03.f\t\t文件名:%s',
                         hexlify(aHash), elapsedTimeOfHash, elapsedTimeOfOpenImage, childFileName)

            fileNameAndAHashMap[childFileName] = aHash

        length = len(allChildFileNames)
        for i in range(0, length):
            baseFileName = allChildFileNames[i]
            baseFilePath = os.path.join(dirPath, baseFileName)
            AHashBase = fileNameAndAHashMap[baseFileName]
            for j in range(i + 1, length):
                otherFileName = allChildFileNames[j]
                otherFilePath = os.path.join(dirPath, otherFileName)
                key = baseFilePath + " vs " + otherFilePath
                # fileNamesAndAHashDistanceMap[key] = (AHashBase - fileNameAndAHashMap[otherFileName]) / len(AHashBase) * 100
                fileNamesAndAHashDistanceMap[key] = average_hash.compare(AHashBase, fileNameAndAHashMap[otherFileName])

    timeName = time.strftime("%y%m%d%H%M%S", time.localtime())
    with open(imgdir_path + ".计算结果." + timeName + ".tsv", "w", newline="\n") as datacsv:
        csvwriter = csv.writer(datacsv, dialect="excel", delimiter="\t")
        csvwriter.writerow(["目录", "文件名", "AHash相似度"])

        for i in fileNamesAndAHashDistanceMap:
            iItems = i.split(' vs ')
            dirName = (os.path.basename(os.path.dirname(iItems[0])))
            fileName1 = (os.path.basename(iItems[0].strip()))
            fileName2 = (os.path.basename(iItems[1].strip()))
            vsName = fileName1 + ' vs ' + fileName2
            distance = int(fileNamesAndAHashDistanceMap[i])
            csvwriter.writerow([dirName, vsName, distance])

            logging.info('AHash汉明距离=%d\t相似度=%02.2f%%\t\t文件名:%s'
                         , distance
                         # 值越小,相似度越高,取值为0-64,即汉明距离中,64位的hash值有多少不同
                         , (64 - distance) / 64.0 * 100.0
                         , vsName
                         )

输出结果:

AHash=b'ffffb7d7808d8098' 计算耗时(毫秒):000 打开文件耗时(毫秒):009 文件名:23022816211316629018_1.jpg
AHash=b'fffd9fa7a080808c' 计算耗时(毫秒):000 打开文件耗时(毫秒):008 文件名:23022816211316629018_2.jpg
AHash=b'ffffa7869490809a' 计算耗时(毫秒):000 打开文件耗时(毫秒):007 文件名:23022816211316629018_3.jpg
AHash=b'ffffa7869490809a' 计算耗时(毫秒):000 打开文件耗时(毫秒):009 文件名:23022816211316629018_3_SAME.jpg
AHash=b'0000000000000000' 计算耗时(毫秒):000 打开文件耗时(毫秒):000 文件名:black.jpg
AHash=b'00f4f06b6eff9100' 计算耗时(毫秒):000 打开文件耗时(毫秒):003 文件名:diff_all.jpg
AHash=b'0000000000000000' 计算耗时(毫秒):000 打开文件耗时(毫秒):000 文件名:white.jpg
AHash汉明距离=12 相似度=81.25% 文件名:23022816211316629018_1.jpg vs 23022816211316629018_2.jpg
AHash汉明距离=11 相似度=82.81% 文件名:23022816211316629018_1.jpg vs 23022816211316629018_3.jpg
AHash汉明距离=11 相似度=82.81% 文件名:23022816211316629018_1.jpg vs 23022816211316629018_3_SAME.jpg
AHash汉明距离=37 相似度=42.19% 文件名:23022816211316629018_1.jpg vs black.jpg
AHash汉明距离=35 相似度=45.31% 文件名:23022816211316629018_1.jpg vs diff_all.jpg
AHash汉明距离=37 相似度=42.19% 文件名:23022816211316629018_1.jpg vs white.jpg
AHash汉明距离=13 相似度=79.69% 文件名:23022816211316629018_2.jpg vs 23022816211316629018_3.jpg
AHash汉明距离=13 相似度=79.69% 文件名:23022816211316629018_2.jpg vs 23022816211316629018_3_SAME.jpg
AHash汉明距离=33 相似度=48.44% 文件名:23022816211316629018_2.jpg vs black.jpg
AHash汉明距离=37 相似度=42.19% 文件名:23022816211316629018_2.jpg vs diff_all.jpg
AHash汉明距离=33 相似度=48.44% 文件名:23022816211316629018_2.jpg vs white.jpg
AHash汉明距离=0 相似度=100.00% 文件名:23022816211316629018_3.jpg vs 23022816211316629018_3_SAME.jpg
AHash汉明距离=34 相似度=46.88% 文件名:23022816211316629018_3.jpg vs black.jpg
AHash汉明距离=40 相似度=37.50% 文件名:23022816211316629018_3.jpg vs diff_all.jpg
AHash汉明距离=34 相似度=46.88% 文件名:23022816211316629018_3.jpg vs white.jpg
AHash汉明距离=34 相似度=46.88% 文件名:23022816211316629018_3_SAME.jpg vs black.jpg
AHash汉明距离=40 相似度=37.50% 文件名:23022816211316629018_3_SAME.jpg vs diff_all.jpg
AHash汉明距离=34 相似度=46.88% 文件名:23022816211316629018_3_SAME.jpg vs white.jpg
AHash汉明距离=30 相似度=53.12% 文件名:black.jpg vs diff_all.jpg
AHash汉明距离=0 相似度=100.00% 文件名:black.jpg vs white.jpg
AHash汉明距离=30 相似度=53.12% 文件名:diff_all.jpg vs white.jpg

posted @ 2023-03-01 19:09  Asion Tang  阅读(171)  评论(0编辑  收藏  举报