多平台图片相似度检测算法
多平台图片相似度检测算法
- 甲方最终选择的是:平均值检测算法
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