ImageHasher.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using SixLabors.ImageSharp;
  6. using SixLabors.ImageSharp.PixelFormats;
  7. namespace Masuit.Tools.Media;
  8. public class ImageHasher
  9. {
  10. private readonly IImageTransformer _transformer;
  11. private float[][] _dctMatrix;
  12. private bool _isDctMatrixInitialized;
  13. private readonly object _dctMatrixLockObject = new();
  14. /// <summary>
  15. /// 默认使用ImageSharpTransformer初始化实例
  16. /// </summary>
  17. public ImageHasher()
  18. {
  19. _transformer = new ImageSharpTransformer();
  20. }
  21. /// <summary>
  22. /// 使用给定的IImageTransformer初始化实例
  23. /// </summary>
  24. /// <param name="transformer">用于图像变换的IImageTransformer的实现类</param>
  25. public ImageHasher(IImageTransformer transformer)
  26. {
  27. _transformer = transformer;
  28. }
  29. /// <summary>
  30. /// 使用平均值算法计算图像的64位哈希
  31. /// </summary>
  32. /// <param name="pathToImage">图片的文件路径</param>
  33. /// <returns>64位hash值</returns>
  34. public ulong AverageHash64(string pathToImage)
  35. {
  36. using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
  37. return AverageHash64(stream);
  38. }
  39. /// <summary>
  40. /// 使用平均值算法计算图像的64位哈希
  41. /// </summary>
  42. /// <param name="sourceStream">读取到的图片流</param>
  43. /// <returns>64位hash值</returns>
  44. public ulong AverageHash64(Stream sourceStream)
  45. {
  46. var pixels = _transformer.TransformImage(sourceStream, 8, 8);
  47. var average = pixels.Sum(b => b) / 64;
  48. // 遍历所有像素,如果超过平均值,则将其设置为1,如果低于平均值,则将其设置为0。
  49. var hash = 0UL;
  50. for (var i = 0; i < 64; i++)
  51. {
  52. if (pixels[i] > average)
  53. {
  54. hash |= 1UL << i;
  55. }
  56. }
  57. return hash;
  58. }
  59. /// <summary>
  60. /// 使用平均值算法计算图像的64位哈希
  61. /// </summary>
  62. /// <param name="image">读取到的图片流</param>
  63. /// <returns>64位hash值</returns>
  64. public ulong AverageHash64(Image<Rgba32> image)
  65. {
  66. var pixels = _transformer.TransformImage(image, 8, 8);
  67. var average = pixels.Sum(b => b) / 64;
  68. // 遍历所有像素,如果超过平均值,则将其设置为1,如果低于平均值,则将其设置为0。
  69. var hash = 0UL;
  70. for (var i = 0; i < 64; i++)
  71. {
  72. if (pixels[i] > average)
  73. {
  74. hash |= 1UL << i;
  75. }
  76. }
  77. return hash;
  78. }
  79. /// <summary>
  80. /// 使用中值算法计算给定图像的64位哈希
  81. /// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
  82. /// </summary>
  83. /// <param name="pathToImage">图片的文件路径</param>
  84. /// <returns>64位hash值</returns>
  85. public ulong MedianHash64(string pathToImage)
  86. {
  87. using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
  88. return MedianHash64(stream);
  89. }
  90. /// <summary>
  91. /// 使用中值算法计算给定图像的64位哈希
  92. /// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
  93. /// </summary>
  94. /// <param name="sourceStream">读取到的图片流</param>
  95. /// <returns>64位hash值</returns>
  96. public ulong MedianHash64(Stream sourceStream)
  97. {
  98. var pixels = _transformer.TransformImage(sourceStream, 8, 8);
  99. // 计算中值
  100. var pixelList = new List<byte>(pixels);
  101. pixelList.Sort();
  102. // 中间像素中值
  103. var median = (byte)((pixelList[31] + pixelList[32]) / 2);
  104. // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
  105. var hash = 0UL;
  106. for (var i = 0; i < 64; i++)
  107. {
  108. if (pixels[i] > median)
  109. {
  110. hash |= 1UL << i;
  111. }
  112. }
  113. return hash;
  114. }
  115. /// <summary>
  116. /// 使用中值算法计算给定图像的64位哈希
  117. /// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
  118. /// </summary>
  119. /// <param name="image">读取到的图片流</param>
  120. /// <returns>64位hash值</returns>
  121. public ulong MedianHash64(Image<Rgba32> image)
  122. {
  123. var pixels = _transformer.TransformImage(image, 8, 8);
  124. // 计算中值
  125. var pixelList = new List<byte>(pixels);
  126. pixelList.Sort();
  127. // 中间像素中值
  128. var median = (byte)((pixelList[31] + pixelList[32]) / 2);
  129. // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
  130. var hash = 0UL;
  131. for (var i = 0; i < 64; i++)
  132. {
  133. if (pixels[i] > median)
  134. {
  135. hash |= 1UL << i;
  136. }
  137. }
  138. return hash;
  139. }
  140. /// <summary>
  141. /// 使用中值算法计算给定图像的256位哈希
  142. /// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
  143. /// </summary>
  144. /// <param name="pathToImage">图片的文件路径</param>
  145. /// <returns>256位hash值,生成一个4长度的数组返回</returns>
  146. public ulong[] MedianHash256(string pathToImage)
  147. {
  148. using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
  149. return MedianHash256(stream);
  150. }
  151. /// <summary>
  152. /// 使用中值算法计算给定图像的256位哈希
  153. /// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
  154. /// </summary>
  155. /// <param name="sourceStream">读取到的图片流</param>
  156. /// <returns>256位hash值,生成一个4长度的数组返回</returns>
  157. public ulong[] MedianHash256(Stream sourceStream)
  158. {
  159. var pixels = _transformer.TransformImage(sourceStream, 16, 16);
  160. // 计算中值
  161. var pixelList = new List<byte>(pixels);
  162. pixelList.Sort();
  163. // 中间像素中值
  164. var median = (byte)((pixelList[127] + pixelList[128]) / 2);
  165. // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
  166. var hash64 = 0UL;
  167. var hash = new ulong[4];
  168. for (var i = 0; i < 4; i++)
  169. {
  170. for (var j = 0; j < 64; j++)
  171. {
  172. if (pixels[64 * i + j] > median)
  173. {
  174. hash64 |= 1UL << j;
  175. }
  176. }
  177. hash[i] = hash64;
  178. hash64 = 0UL;
  179. }
  180. return hash;
  181. }
  182. /// <summary>
  183. /// 使用中值算法计算给定图像的256位哈希
  184. /// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
  185. /// </summary>
  186. /// <param name="image">读取到的图片流</param>
  187. /// <returns>256位hash值,生成一个4长度的数组返回</returns>
  188. public ulong[] MedianHash256(Image<Rgba32> image)
  189. {
  190. var pixels = _transformer.TransformImage(image, 16, 16);
  191. // 计算中值
  192. var pixelList = new List<byte>(pixels);
  193. pixelList.Sort();
  194. // 中间像素中值
  195. var median = (byte)((pixelList[127] + pixelList[128]) / 2);
  196. // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
  197. var hash64 = 0UL;
  198. var hash = new ulong[4];
  199. for (var i = 0; i < 4; i++)
  200. {
  201. for (var j = 0; j < 64; j++)
  202. {
  203. if (pixels[64 * i + j] > median)
  204. {
  205. hash64 |= 1UL << j;
  206. }
  207. }
  208. hash[i] = hash64;
  209. hash64 = 0UL;
  210. }
  211. return hash;
  212. }
  213. /// <summary>
  214. /// 使用差分哈希算法计算图像的64位哈希。
  215. /// </summary>
  216. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  217. /// <param name="pathToImage">图片的文件路径</param>
  218. /// <returns>64位hash值</returns>
  219. public ulong DifferenceHash64(string pathToImage)
  220. {
  221. using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
  222. return DifferenceHash64(stream);
  223. }
  224. /// <summary>
  225. /// 使用差分哈希算法计算图像的64位哈希。
  226. /// </summary>
  227. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  228. /// <param name="sourceStream">读取到的图片流</param>
  229. /// <returns>64位hash值</returns>
  230. public ulong DifferenceHash64(Stream sourceStream)
  231. {
  232. var pixels = _transformer.TransformImage(sourceStream, 9, 8);
  233. // 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
  234. var hash = 0UL;
  235. var hashPos = 0;
  236. for (var i = 0; i < 8; i++)
  237. {
  238. var rowStart = i * 9;
  239. for (var j = 0; j < 8; j++)
  240. {
  241. if (pixels[rowStart + j] > pixels[rowStart + j + 1])
  242. {
  243. hash |= 1UL << hashPos;
  244. }
  245. hashPos++;
  246. }
  247. }
  248. return hash;
  249. }
  250. /// <summary>
  251. /// 使用差分哈希算法计算图像的64位哈希。
  252. /// </summary>
  253. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  254. /// <param name="image">读取到的图片流</param>
  255. /// <returns>64位hash值</returns>
  256. public ulong DifferenceHash64(Image<Rgba32> image)
  257. {
  258. var pixels = _transformer.TransformImage(image, 9, 8);
  259. // 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
  260. var hash = 0UL;
  261. var hashPos = 0;
  262. for (var i = 0; i < 8; i++)
  263. {
  264. var rowStart = i * 9;
  265. for (var j = 0; j < 8; j++)
  266. {
  267. if (pixels[rowStart + j] > pixels[rowStart + j + 1])
  268. {
  269. hash |= 1UL << hashPos;
  270. }
  271. hashPos++;
  272. }
  273. }
  274. return hash;
  275. }
  276. /// <summary>
  277. /// 使用差分哈希算法计算图像的256位哈希。
  278. /// </summary>
  279. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  280. /// <param name="pathToImage">图片的文件路径</param>
  281. /// <returns>256位hash值</returns>
  282. public ulong[] DifferenceHash256(string pathToImage)
  283. {
  284. using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
  285. return DifferenceHash256(stream);
  286. }
  287. /// <summary>
  288. /// 使用差分哈希算法计算图像的64位哈希。
  289. /// </summary>
  290. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  291. /// <param name="sourceStream">读取到的图片流</param>
  292. /// <returns>256位hash值</returns>
  293. public ulong[] DifferenceHash256(Stream sourceStream)
  294. {
  295. var pixels = _transformer.TransformImage(sourceStream, 17, 16);
  296. // 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
  297. var hash = new ulong[4];
  298. var hashPos = 0;
  299. var hashPart = 0;
  300. for (var i = 0; i < 16; i++)
  301. {
  302. var rowStart = i * 17;
  303. for (var j = 0; j < 16; j++)
  304. {
  305. if (pixels[rowStart + j] > pixels[rowStart + j + 1])
  306. {
  307. hash[hashPart] |= 1UL << hashPos;
  308. }
  309. if (hashPos == 63)
  310. {
  311. hashPos = 0;
  312. hashPart++;
  313. }
  314. else
  315. {
  316. hashPos++;
  317. }
  318. }
  319. }
  320. return hash;
  321. }
  322. /// <summary>
  323. /// 使用差分哈希算法计算图像的64位哈希。
  324. /// </summary>
  325. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  326. /// <param name="image">读取到的图片流</param>
  327. /// <returns>256位hash值</returns>
  328. public ulong[] DifferenceHash256(Image<Rgba32> image)
  329. {
  330. var pixels = _transformer.TransformImage(image, 17, 16);
  331. // 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
  332. var hash = new ulong[4];
  333. var hashPos = 0;
  334. var hashPart = 0;
  335. for (var i = 0; i < 16; i++)
  336. {
  337. var rowStart = i * 17;
  338. for (var j = 0; j < 16; j++)
  339. {
  340. if (pixels[rowStart + j] > pixels[rowStart + j + 1])
  341. {
  342. hash[hashPart] |= 1UL << hashPos;
  343. }
  344. if (hashPos == 63)
  345. {
  346. hashPos = 0;
  347. hashPart++;
  348. }
  349. else
  350. {
  351. hashPos++;
  352. }
  353. }
  354. }
  355. return hash;
  356. }
  357. /// <summary>
  358. /// 使用DCT算法计算图像的64位哈希
  359. /// </summary>
  360. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  361. /// <param name="sourceStream">读取到的图片流</param>
  362. /// <returns>64位hash值</returns>
  363. public ulong DctHash(Stream sourceStream)
  364. {
  365. lock (_dctMatrixLockObject)
  366. {
  367. if (!_isDctMatrixInitialized)
  368. {
  369. _dctMatrix = GenerateDctMatrix(32);
  370. _isDctMatrixInitialized = true;
  371. }
  372. }
  373. var pixels = _transformer.TransformImage(sourceStream, 32, 32);
  374. // 将像素转换成float类型数组
  375. var fPixels = new float[1024];
  376. for (var i = 0; i < 1024; i++)
  377. {
  378. fPixels[i] = pixels[i] / 255.0f;
  379. }
  380. // 计算 dct 矩阵
  381. var dctPixels = ComputeDct(fPixels, _dctMatrix);
  382. // 从矩阵中1,1到8,8获得8x8的区域,忽略最低频率以提高检测
  383. var dctHashPixels = new float[64];
  384. for (var x = 0; x < 8; x++)
  385. {
  386. for (var y = 0; y < 8; y++)
  387. {
  388. dctHashPixels[x + y * 8] = dctPixels[x + 1][y + 1];
  389. }
  390. }
  391. // 计算中值
  392. var pixelList = new List<float>(dctHashPixels);
  393. pixelList.Sort();
  394. // 中间像素的平均值
  395. var median = (pixelList[31] + pixelList[32]) / 2;
  396. // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
  397. var hash = 0UL;
  398. for (var i = 0; i < 64; i++)
  399. {
  400. if (dctHashPixels[i] > median)
  401. {
  402. hash |= 1UL << i;
  403. }
  404. }
  405. return hash;
  406. }
  407. /// <summary>
  408. /// 使用DCT算法计算图像的64位哈希
  409. /// </summary>
  410. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  411. /// <param name="image">读取到的图片流</param>
  412. /// <returns>64位hash值</returns>
  413. public ulong DctHash(Image<Rgba32> image)
  414. {
  415. lock (_dctMatrixLockObject)
  416. {
  417. if (!_isDctMatrixInitialized)
  418. {
  419. _dctMatrix = GenerateDctMatrix(32);
  420. _isDctMatrixInitialized = true;
  421. }
  422. }
  423. var pixels = _transformer.TransformImage(image, 32, 32);
  424. // 将像素转换成float类型数组
  425. var fPixels = new float[1024];
  426. for (var i = 0; i < 1024; i++)
  427. {
  428. fPixels[i] = pixels[i] / 255.0f;
  429. }
  430. // 计算 dct 矩阵
  431. var dctPixels = ComputeDct(fPixels, _dctMatrix);
  432. // 从矩阵中1,1到8,8获得8x8的区域,忽略最低频率以提高检测
  433. var dctHashPixels = new float[64];
  434. for (var x = 0; x < 8; x++)
  435. {
  436. for (var y = 0; y < 8; y++)
  437. {
  438. dctHashPixels[x + y * 8] = dctPixels[x + 1][y + 1];
  439. }
  440. }
  441. // 计算中值
  442. var pixelList = new List<float>(dctHashPixels);
  443. pixelList.Sort();
  444. // 中间像素的平均值
  445. var median = (pixelList[31] + pixelList[32]) / 2;
  446. // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
  447. var hash = 0UL;
  448. for (var i = 0; i < 64; i++)
  449. {
  450. if (dctHashPixels[i] > median)
  451. {
  452. hash |= 1UL << i;
  453. }
  454. }
  455. return hash;
  456. }
  457. /// <summary>
  458. /// 使用DCT算法计算图像的64位哈希
  459. /// </summary>
  460. /// <param name="path">图片的文件路径</param>
  461. /// <returns>64位hash值</returns>
  462. public ulong DctHash(string path)
  463. {
  464. using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
  465. return DctHash(stream);
  466. }
  467. /// <summary>
  468. /// 计算图像的DCT矩阵
  469. /// </summary>
  470. /// <param name="image">用于计算dct的图像</param>
  471. /// <param name="dctMatrix">DCT系数矩阵</param>
  472. /// <returns>图像的DCT矩阵</returns>
  473. private static float[][] ComputeDct(float[] image, float[][] dctMatrix)
  474. {
  475. // dct矩阵的大小,图像的大小与DCT矩阵相同
  476. var size = dctMatrix.GetLength(0);
  477. // 降图像转换成矩阵
  478. var imageMat = new float[size][];
  479. for (var i = 0; i < size; i++)
  480. {
  481. imageMat[i] = new float[size];
  482. }
  483. for (var y = 0; y < size; y++)
  484. {
  485. for (var x = 0; x < size; x++)
  486. {
  487. imageMat[y][x] = image[x + y * size];
  488. }
  489. }
  490. return Multiply(Multiply(dctMatrix, imageMat), Transpose(dctMatrix));
  491. }
  492. /// <summary>
  493. /// 生成DCT系数矩阵
  494. /// </summary>
  495. /// <param name="size">矩阵的大小</param>
  496. /// <returns>DCT系数矩阵</returns>
  497. private static float[][] GenerateDctMatrix(int size)
  498. {
  499. var matrix = new float[size][];
  500. for (int i = 0; i < size; i++)
  501. {
  502. matrix[i] = new float[size];
  503. }
  504. var c1 = Math.Sqrt(2.0f / size);
  505. for (var j = 0; j < size; j++)
  506. {
  507. matrix[0][j] = (float)Math.Sqrt(1.0d / size);
  508. }
  509. for (var j = 0; j < size; j++)
  510. {
  511. for (var i = 1; i < size; i++)
  512. {
  513. matrix[i][j] = (float)(c1 * Math.Cos(((2 * j + 1) * i * Math.PI) / (2.0d * size)));
  514. }
  515. }
  516. return matrix;
  517. }
  518. /// <summary>
  519. /// 矩阵的乘法运算
  520. /// </summary>
  521. /// <param name="a">矩阵a</param>
  522. /// <param name="b">矩阵b</param>
  523. /// <returns>Result matrix.</returns>
  524. private static float[][] Multiply(float[][] a, float[][] b)
  525. {
  526. var n = a[0].Length;
  527. var c = new float[n][];
  528. for (var i = 0; i < n; i++)
  529. {
  530. c[i] = new float[n];
  531. }
  532. for (var i = 0; i < n; i++)
  533. for (var k = 0; k < n; k++)
  534. for (var j = 0; j < n; j++)
  535. c[i][j] += a[i][k] * b[k][j];
  536. return c;
  537. }
  538. /// <summary>
  539. /// 矩阵转置
  540. /// </summary>
  541. /// <param name="mat">待转换的矩阵</param>
  542. /// <returns>转换后的矩阵</returns>
  543. private static float[][] Transpose(float[][] mat)
  544. {
  545. var size = mat[0].Length;
  546. var transpose = new float[size][];
  547. for (var i = 0; i < size; i++)
  548. {
  549. transpose[i] = new float[size];
  550. for (var j = 0; j < size; j++)
  551. {
  552. transpose[i][j] = mat[j][i];
  553. }
  554. }
  555. return transpose;
  556. }
  557. /// <summary>
  558. /// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
  559. /// </summary>
  560. /// <param name="hash1">图像1的hash</param>
  561. /// <param name="hash2">图像2的hash</param>
  562. /// <returns>相似度范围:[0,1]</returns>
  563. public static float Compare(ulong hash1, ulong hash2)
  564. {
  565. // hash异或运算
  566. var hashDifference = hash1 ^ hash2;
  567. // 计算汉明距离
  568. var onesInHash = HammingWeight(hashDifference);
  569. // 得到相似度
  570. return 1.0f - onesInHash / 64.0f;
  571. }
  572. /// <summary>
  573. /// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
  574. /// </summary>
  575. /// <param name="hash1">图像1的hash</param>
  576. /// <param name="hash2">图像2的hash</param>
  577. /// <returns>相似度范围:[0,1]</returns>
  578. public static float Compare(ulong[] hash1, ulong[] hash2)
  579. {
  580. // 检查两个图像的hash长度是否一致
  581. if (hash1.Length != hash2.Length)
  582. {
  583. throw new ArgumentException("hash1 与 hash2长度不匹配");
  584. }
  585. var hashSize = hash1.Length;
  586. ulong onesInHash = 0;
  587. // hash异或运算
  588. var hashDifference = new ulong[hashSize];
  589. for (var i = 0; i < hashSize; i++)
  590. {
  591. hashDifference[i] = hash1[i] ^ hash2[i];
  592. }
  593. // 逐个计算汉明距离
  594. for (var i = 0; i < hashSize; i++)
  595. {
  596. onesInHash += HammingWeight(hashDifference[i]);
  597. }
  598. // 得到相似度
  599. return 1.0f - onesInHash / (hashSize * 64.0f);
  600. }
  601. /// <summary>
  602. /// 计算hash的汉明权重.
  603. /// </summary>
  604. /// <see cref="http://en.wikipedia.org/wiki/Hamming_weight"/>
  605. /// <param name="hash"></param>
  606. /// <returns></returns>
  607. private static ulong HammingWeight(ulong hash)
  608. {
  609. hash -= (hash >> 1) & M1;
  610. hash = (hash & M2) + ((hash >> 2) & M2);
  611. hash = (hash + (hash >> 4)) & M4;
  612. var onesInHash = (hash * H01) >> 56;
  613. return onesInHash;
  614. }
  615. // 汉明距离常量. http://en.wikipedia.org/wiki/Hamming_weight
  616. private const ulong M1 = 0x5555555555555555; //binary: 0101...
  617. private const ulong M2 = 0x3333333333333333; //binary: 00110011..
  618. private const ulong M4 = 0x0f0f0f0f0f0f0f0f; //binary: 4 个0, 4 个1 ...
  619. private const ulong H01 = 0x0101010101010101; //the sum of 256 to the power of 0,1,2,3...
  620. }