Răsfoiți Sursa

优化ImageHasher性能和内存占用

懒得勤快 4 luni în urmă
părinte
comite
b1e041c963

+ 1 - 1
Directory.Build.props

@@ -1,6 +1,6 @@
 <Project>
  <PropertyGroup>
-   <Version>2025.4.2</Version>
+   <Version>2025.4.3</Version>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
 </Project>

+ 2 - 2
Masuit.Tools.Abstractions/Media/IImageTransformer.cs

@@ -25,5 +25,5 @@ public interface IImageTransformer
     /// <param name="width">给定宽度</param>
     /// <param name="height">给定高度</param>
     /// <returns>包含转换图像的8位像素值的字节数组。</returns>
-    byte[] TransformImage(Image<Rgba32> image, int width, int height);
-}
+    byte[] TransformImage(Image<L8> image, int width, int height);
+}

+ 145 - 20
Masuit.Tools.Abstractions/Media/ImageHasher.cs

@@ -1,12 +1,16 @@
-using System;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.PixelFormats;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.PixelFormats;
 
 namespace Masuit.Tools.Media;
 
+/// <summary>
+/// 图像hash机算器
+/// </summary>
 public class ImageHasher
 {
     private readonly IImageTransformer _transformer;
@@ -38,8 +42,18 @@ public class ImageHasher
     /// <returns>64位hash值</returns>
     public ulong AverageHash64(string pathToImage)
     {
-        using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
-        return AverageHash64(stream);
+#if NET6_0_OR_GREATER
+
+        var decoderOptions = new DecoderOptions
+        {
+            TargetSize = new Size(144),
+            SkipMetadata = true
+        };
+        using var image = Image.Load<L8>(decoderOptions, pathToImage);
+#else
+        using var image = Image.Load<L8>(pathToImage);
+#endif
+        return AverageHash64(image);
     }
 
     /// <summary>
@@ -70,7 +84,18 @@ public class ImageHasher
     /// </summary>
     /// <param name="image">读取到的图片流</param>
     /// <returns>64位hash值</returns>
-    public ulong AverageHash64(Image<Rgba32> image)
+    public ulong AverageHash64(Image image)
+    {
+        using var source = image.CloneAs<L8>();
+        return AverageHash64(source);
+    }
+
+    /// <summary>
+    /// 使用平均值算法计算图像的64位哈希
+    /// </summary>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong AverageHash64(Image<L8> image)
     {
         var pixels = _transformer.TransformImage(image, 8, 8);
         var average = pixels.Sum(b => b) / 64;
@@ -96,8 +121,18 @@ public class ImageHasher
     /// <returns>64位hash值</returns>
     public ulong MedianHash64(string pathToImage)
     {
-        using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
-        return MedianHash64(stream);
+#if NET6_0_OR_GREATER
+
+        var decoderOptions = new DecoderOptions
+        {
+            TargetSize = new Size(144),
+            SkipMetadata = true
+        };
+        using var image = Image.Load<L8>(decoderOptions, pathToImage);
+#else
+        using var image = Image.Load<L8>(pathToImage);
+#endif
+        return MedianHash64(image);
     }
 
     /// <summary>
@@ -136,7 +171,19 @@ public class ImageHasher
     /// </summary>
     /// <param name="image">读取到的图片流</param>
     /// <returns>64位hash值</returns>
-    public ulong MedianHash64(Image<Rgba32> image)
+    public ulong MedianHash64(Image image)
+    {
+        using var source = image.CloneAs<L8>();
+        return MedianHash64(source);
+    }
+
+    /// <summary>
+    /// 使用中值算法计算给定图像的64位哈希
+    /// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
+    /// </summary>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong MedianHash64(Image<L8> image)
     {
         var pixels = _transformer.TransformImage(image, 8, 8);
 
@@ -168,8 +215,18 @@ public class ImageHasher
     /// <returns>256位hash值,生成一个4长度的数组返回</returns>
     public ulong[] MedianHash256(string pathToImage)
     {
-        using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
-        return MedianHash256(stream);
+#if NET6_0_OR_GREATER
+
+        var decoderOptions = new DecoderOptions
+        {
+            TargetSize = new Size(144),
+            SkipMetadata = true
+        };
+        using var image = Image.Load<L8>(decoderOptions, pathToImage);
+#else
+        using var image = Image.Load<L8>(pathToImage);
+#endif
+        return MedianHash256(image);
     }
 
     /// <summary>
@@ -214,7 +271,19 @@ public class ImageHasher
     /// </summary>
     /// <param name="image">读取到的图片流</param>
     /// <returns>256位hash值,生成一个4长度的数组返回</returns>
-    public ulong[] MedianHash256(Image<Rgba32> image)
+    public ulong[] MedianHash256(Image image)
+    {
+        using var source = image.CloneAs<L8>();
+        return MedianHash256(source);
+    }
+
+    /// <summary>
+    /// 使用中值算法计算给定图像的256位哈希
+    /// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
+    /// </summary>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>256位hash值,生成一个4长度的数组返回</returns>
+    public ulong[] MedianHash256(Image<L8> image)
     {
         var pixels = _transformer.TransformImage(image, 16, 16);
 
@@ -252,8 +321,18 @@ public class ImageHasher
     /// <returns>64位hash值</returns>
     public ulong DifferenceHash64(string pathToImage)
     {
-        using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
-        return DifferenceHash64(stream);
+#if NET6_0_OR_GREATER
+
+        var decoderOptions = new DecoderOptions
+        {
+            TargetSize = new Size(144),
+            SkipMetadata = true
+        };
+        using var image = Image.Load<L8>(decoderOptions, pathToImage);
+#else
+        using var image = Image.Load<L8>(pathToImage);
+#endif
+        return DifferenceHash64(image);
     }
 
     /// <summary>
@@ -292,7 +371,19 @@ public class ImageHasher
     /// <see cref="https://segmentfault.com/a/1190000038308093"/>
     /// <param name="image">读取到的图片流</param>
     /// <returns>64位hash值</returns>
-    public ulong DifferenceHash64(Image<Rgba32> image)
+    public ulong DifferenceHash64(Image image)
+    {
+        using var source = image.CloneAs<L8>();
+        return DifferenceHash64(source);
+    }
+
+    /// <summary>
+    /// 使用差分哈希算法计算图像的64位哈希。
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong DifferenceHash64(Image<L8> image)
     {
         var pixels = _transformer.TransformImage(image, 9, 8);
 
@@ -324,8 +415,18 @@ public class ImageHasher
     /// <returns>256位hash值</returns>
     public ulong[] DifferenceHash256(string pathToImage)
     {
-        using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
-        return DifferenceHash256(stream);
+#if NET6_0_OR_GREATER
+
+        var decoderOptions = new DecoderOptions
+        {
+            TargetSize = new Size(144),
+            SkipMetadata = true,
+        };
+        using var image = Image.Load<L8>(decoderOptions, pathToImage);
+#else
+        using var image = Image.Load<L8>(pathToImage);
+#endif
+        return DifferenceHash256(image);
     }
 
     /// <summary>
@@ -373,7 +474,19 @@ public class ImageHasher
     /// <see cref="https://segmentfault.com/a/1190000038308093"/>
     /// <param name="image">读取到的图片流</param>
     /// <returns>256位hash值</returns>
-    public ulong[] DifferenceHash256(Image<Rgba32> image)
+    public ulong[] DifferenceHash256(Image image)
+    {
+        using var source = image.CloneAs<L8>();
+        return DifferenceHash256(source);
+    }
+
+    /// <summary>
+    /// 使用差分哈希算法计算图像的64位哈希。
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>256位hash值</returns>
+    public ulong[] DifferenceHash256(Image<L8> image)
     {
         var pixels = _transformer.TransformImage(image, 17, 16);
 
@@ -471,7 +584,19 @@ public class ImageHasher
     /// <see cref="https://segmentfault.com/a/1190000038308093"/>
     /// <param name="image">读取到的图片流</param>
     /// <returns>64位hash值</returns>
-    public ulong DctHash(Image<Rgba32> image)
+    public ulong DctHash(Image image)
+    {
+        using var source = image.CloneAs<L8>();
+        return DctHash(source);
+    }
+
+    /// <summary>
+    /// 使用DCT算法计算图像的64位哈希
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong DctHash(Image<L8> image)
     {
         lock (_dctMatrixLockObject)
         {
@@ -711,4 +836,4 @@ public class ImageHasher
     private const ulong M2 = 0x3333333333333333; //binary: 00110011..
     private const ulong M4 = 0x0f0f0f0f0f0f0f0f; //binary:  4 个0,  4 个1 ...
     private const ulong H01 = 0x0101010101010101; //the sum of 256 to the power of 0,1,2,3...
-}
+}

+ 30 - 14
Masuit.Tools.Abstractions/Media/ImageSharpTransformer.cs

@@ -1,8 +1,9 @@
-using System.IO;
-using SixLabors.ImageSharp;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing;
 using SixLabors.ImageSharp.Processing.Processors.Transforms;
+using System.IO;
 
 namespace Masuit.Tools.Media;
 
@@ -11,13 +12,28 @@ namespace Masuit.Tools.Media;
 /// </summary>
 public class ImageSharpTransformer : IImageTransformer
 {
+#if NET6_0_OR_GREATER
     public byte[] TransformImage(Stream stream, int width, int height)
     {
-        using var image = Image.Load<Rgba32>(stream);
+        var decoderOptions = new DecoderOptions
+        {
+            TargetSize = new Size(144),
+            SkipMetadata = true,
+        };
+        using var image = Image.Load<L8>(decoderOptions, stream);
         return TransformImage(image, width, height);
     }
+#else
+
+    public byte[] TransformImage(Stream stream, int width, int height)
+    {
+        using var image = Image.Load<L8>(stream);
+        return TransformImage(image, width, height);
+    }
+
+#endif
 
-    public byte[] TransformImage(Image<Rgba32> image, int width, int height)
+    public byte[] TransformImage(Image<L8> image, int width, int height)
     {
         image.Mutate(x => x.Resize(new ResizeOptions()
         {
@@ -28,14 +44,14 @@ public class ImageSharpTransformer : IImageTransformer
             },
             Mode = ResizeMode.Stretch,
             Sampler = new BicubicResampler()
-        }).Grayscale());
+        }));
         image.DangerousTryGetSinglePixelMemory(out var pixelSpan);
         var pixelArray = pixelSpan.ToArray();
         var pixelCount = width * height;
         var bytes = new byte[pixelCount];
         for (var i = 0; i < pixelCount; i++)
         {
-            bytes[i] = pixelArray[i].B;
+            bytes[i] = pixelArray[i].PackedValue;
         }
 
         return bytes;
@@ -49,7 +65,7 @@ public static class ImageHashExt
     /// </summary>
     /// <param name="image">读取到的图片流</param>
     /// <returns>64位hash值</returns>
-    public static ulong AverageHash64(this Image<Rgba32> image)
+    public static ulong AverageHash64(this Image image)
     {
         var hasher = new ImageHasher();
         return hasher.AverageHash64(image);
@@ -61,7 +77,7 @@ public static class ImageHashExt
     /// </summary>
     /// <param name="image">读取到的图片流</param>
     /// <returns>64位hash值</returns>
-    public static ulong MedianHash64(this Image<Rgba32> image)
+    public static ulong MedianHash64(this Image image)
     {
         var hasher = new ImageHasher();
         return hasher.MedianHash64(image);
@@ -73,7 +89,7 @@ public static class ImageHashExt
     /// </summary>
     /// <param name="image">读取到的图片流</param>
     /// <returns>256位hash值,生成一个4长度的数组返回</returns>
-    public static ulong[] MedianHash256(this Image<Rgba32> image)
+    public static ulong[] MedianHash256(this Image image)
     {
         var hasher = new ImageHasher();
         return hasher.MedianHash256(image);
@@ -85,7 +101,7 @@ public static class ImageHashExt
     /// <see cref="https://segmentfault.com/a/1190000038308093"/>
     /// <param name="image">读取到的图片流</param>
     /// <returns>64位hash值</returns>
-    public static ulong DifferenceHash64(this Image<Rgba32> image)
+    public static ulong DifferenceHash64(this Image image)
     {
         var hasher = new ImageHasher();
         return hasher.DifferenceHash64(image);
@@ -97,7 +113,7 @@ public static class ImageHashExt
     /// <see cref="https://segmentfault.com/a/1190000038308093"/>
     /// <param name="image">读取到的图片流</param>
     /// <returns>256位hash值</returns>
-    public static ulong[] DifferenceHash256(this Image<Rgba32> image)
+    public static ulong[] DifferenceHash256(this Image image)
     {
         var hasher = new ImageHasher();
         return hasher.DifferenceHash256(image);
@@ -109,7 +125,7 @@ public static class ImageHashExt
     /// <see cref="https://segmentfault.com/a/1190000038308093"/>
     /// <param name="image">读取到的图片流</param>
     /// <returns>64位hash值</returns>
-    public static ulong DctHash(this Image<Rgba32> image)
+    public static ulong DctHash(this Image image)
     {
         var hasher = new ImageHasher();
         return hasher.DctHash(image);
@@ -121,7 +137,7 @@ public static class ImageHashExt
     /// <param name="image1">图像1</param>
     /// <param name="image2">图像2</param>
     /// <returns>相似度范围:[0,1]</returns>
-    public static float Compare(this Image<Rgba32> image1, Image<Rgba32> image2)
+    public static float Compare(this Image image1, Image image2)
     {
         var hasher = new ImageHasher();
         var hash1 = hasher.DifferenceHash256(image1);
@@ -135,7 +151,7 @@ public static class ImageHashExt
     /// <param name="image1">图像1的hash</param>
     /// <param name="image2path">图像2的路径</param>
     /// <returns>相似度范围:[0,1]</returns>
-    public static float Compare(this Image<Rgba32> image1, string image2path)
+    public static float Compare(this Image image1, string image2path)
     {
         var hasher = new ImageHasher();
         var hash1 = hasher.DifferenceHash256(image1);