1
1

ImageHasher.cs 26 KB

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