ImageHasher.cs 24 KB

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