Bläddra i källkod

增加一些图像对比扩展函数

懒得勤快 3 år sedan
förälder
incheckning
3d3a5f3364

+ 1 - 1
Masuit.Tools.Abstractions/Masuit.Tools.Abstractions.csproj

@@ -4,7 +4,7 @@
     <LangVersion>latest</LangVersion>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <CodeAnalysisRuleSet />
-    <Version>2.5.0.2</Version>
+    <Version>2.5.1</Version>
     <Authors>懒得勤快</Authors>
     <Description>Masuit.Tools基础公共库,包含一些常用的操作类,大都是静态类,加密解密,反射操作,Excel简单导出,权重随机筛选算法,分布式短id,表达式树,linq扩展,文件压缩,多线程下载和FTP客户端,硬件信息,字符串扩展方法,日期时间扩展操作,中国农历,大文件拷贝,图像裁剪,验证码,断点续传,集合扩展等常用封装。</Description>
     <Copyright>懒得勤快,长空X</Copyright>

+ 11 - 0
Masuit.Tools.Abstractions/Media/IImageTransformer.cs

@@ -1,4 +1,6 @@
 using System.IO;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
 
 namespace Masuit.Tools.Media;
 
@@ -15,4 +17,13 @@ public interface IImageTransformer
     /// <param name="height">给定高度</param>
     /// <returns>包含转换图像的8位像素值的字节数组。</returns>
     byte[] TransformImage(Stream stream, int width, int height);
+
+    /// <summary>
+    /// 将给定图像转换为8bit色深通道的灰度图像,并将其调整为给定的宽度和高度。在调整大小操作期间,应忽略纵横比。
+    /// </summary>
+    /// <param name="image">图像</param>
+    /// <param name="width">给定宽度</param>
+    /// <param name="height">给定高度</param>
+    /// <returns>包含转换图像的8位像素值的字节数组。</returns>
+    byte[] TransformImage(Image<Rgba32> image, int width, int height);
 }

+ 219 - 0
Masuit.Tools.Abstractions/Media/ImageHasher.cs

@@ -2,6 +2,8 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
 
 namespace Masuit.Tools.Media;
 
@@ -64,6 +66,29 @@ public class ImageHasher
         return hash;
     }
 
+    /// <summary>
+    /// 使用平均值算法计算图像的64位哈希
+    /// </summary>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong AverageHash64(Image<Rgba32> image)
+    {
+        var pixels = _transformer.TransformImage(image, 8, 8);
+        var average = pixels.Sum(b => b) / 64;
+
+        // 遍历所有像素,如果超过平均值,则将其设置为1,如果低于平均值,则将其设置为0。
+        var hash = 0UL;
+        for (var i = 0; i < 64; i++)
+        {
+            if (pixels[i] > average)
+            {
+                hash |= 1UL << i;
+            }
+        }
+
+        return hash;
+    }
+
     /// <summary>
     /// 使用中值算法计算给定图像的64位哈希
     /// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
@@ -106,6 +131,36 @@ public class ImageHasher
         return hash;
     }
 
+    /// <summary>
+    /// 使用中值算法计算给定图像的64位哈希
+    /// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
+    /// </summary>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong MedianHash64(Image<Rgba32> image)
+    {
+        var pixels = _transformer.TransformImage(image, 8, 8);
+
+        // 计算中值
+        var pixelList = new List<byte>(pixels);
+        pixelList.Sort();
+
+        // 中间像素中值
+        var median = (byte)((pixelList[31] + pixelList[32]) / 2);
+
+        // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
+        var hash = 0UL;
+        for (var i = 0; i < 64; i++)
+        {
+            if (pixels[i] > median)
+            {
+                hash |= 1UL << i;
+            }
+        }
+
+        return hash;
+    }
+
     /// <summary>
     /// 使用中值算法计算给定图像的256位哈希
     /// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
@@ -154,6 +209,42 @@ public class ImageHasher
         return hash;
     }
 
+    /// <summary>
+    /// 使用中值算法计算给定图像的256位哈希
+    /// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
+    /// </summary>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>256位hash值,生成一个4长度的数组返回</returns>
+    public ulong[] MedianHash256(Image<Rgba32> image)
+    {
+        var pixels = _transformer.TransformImage(image, 16, 16);
+
+        // 计算中值
+        var pixelList = new List<byte>(pixels);
+        pixelList.Sort();
+
+        // 中间像素中值
+        var median = (byte)((pixelList[127] + pixelList[128]) / 2);
+
+        // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
+        var hash64 = 0UL;
+        var hash = new ulong[4];
+        for (var i = 0; i < 4; i++)
+        {
+            for (var j = 0; j < 64; j++)
+            {
+                if (pixels[64 * i + j] > median)
+                {
+                    hash64 |= 1UL << j;
+                }
+            }
+            hash[i] = hash64;
+            hash64 = 0UL;
+        }
+
+        return hash;
+    }
+
     /// <summary>
     /// 使用差分哈希算法计算图像的64位哈希。
     /// </summary>
@@ -196,6 +287,36 @@ public class ImageHasher
         return hash;
     }
 
+    /// <summary>
+    /// 使用差分哈希算法计算图像的64位哈希。
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong DifferenceHash64(Image<Rgba32> image)
+    {
+        var pixels = _transformer.TransformImage(image, 9, 8);
+
+        // 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
+        var hash = 0UL;
+        var hashPos = 0;
+        for (var i = 0; i < 8; i++)
+        {
+            var rowStart = i * 9;
+            for (var j = 0; j < 8; j++)
+            {
+                if (pixels[rowStart + j] > pixels[rowStart + j + 1])
+                {
+                    hash |= 1UL << hashPos;
+                }
+
+                hashPos++;
+            }
+        }
+
+        return hash;
+    }
+
     /// <summary>
     /// 使用差分哈希算法计算图像的256位哈希。
     /// </summary>
@@ -247,6 +368,45 @@ public class ImageHasher
         return hash;
     }
 
+    /// <summary>
+    /// 使用差分哈希算法计算图像的64位哈希。
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>256位hash值</returns>
+    public ulong[] DifferenceHash256(Image<Rgba32> image)
+    {
+        var pixels = _transformer.TransformImage(image, 17, 16);
+
+        // 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
+        var hash = new ulong[4];
+        var hashPos = 0;
+        var hashPart = 0;
+        for (var i = 0; i < 16; i++)
+        {
+            var rowStart = i * 17;
+            for (var j = 0; j < 16; j++)
+            {
+                if (pixels[rowStart + j] > pixels[rowStart + j + 1])
+                {
+                    hash[hashPart] |= 1UL << hashPos;
+                }
+
+                if (hashPos == 63)
+                {
+                    hashPos = 0;
+                    hashPart++;
+                }
+                else
+                {
+                    hashPos++;
+                }
+            }
+        }
+
+        return hash;
+    }
+
     /// <summary>
     /// 使用DCT算法计算图像的64位哈希
     /// </summary>
@@ -306,6 +466,65 @@ public class ImageHasher
         return hash;
     }
 
+    /// <summary>
+    /// 使用DCT算法计算图像的64位哈希
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong DctHash(Image<Rgba32> image)
+    {
+        lock (_dctMatrixLockObject)
+        {
+            if (!_isDctMatrixInitialized)
+            {
+                _dctMatrix = GenerateDctMatrix(32);
+                _isDctMatrixInitialized = true;
+            }
+        }
+
+        var pixels = _transformer.TransformImage(image, 32, 32);
+
+        // 将像素转换成float类型数组
+        var fPixels = new float[1024];
+        for (var i = 0; i < 1024; i++)
+        {
+            fPixels[i] = pixels[i] / 255.0f;
+        }
+
+        // 计算 dct 矩阵
+        var dctPixels = ComputeDct(fPixels, _dctMatrix);
+
+        // 从矩阵中1,1到8,8获得8x8的区域,忽略最低频率以提高检测
+        var dctHashPixels = new float[64];
+        for (var x = 0; x < 8; x++)
+        {
+            for (var y = 0; y < 8; y++)
+            {
+                dctHashPixels[x + y * 8] = dctPixels[x + 1][y + 1];
+            }
+        }
+
+        // 计算中值
+        var pixelList = new List<float>(dctHashPixels);
+        pixelList.Sort();
+
+        // 中间像素的平均值
+        var median = (pixelList[31] + pixelList[32]) / 2;
+
+        // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
+        var hash = 0UL;
+        for (var i = 0; i < 64; i++)
+        {
+            if (dctHashPixels[i] > median)
+            {
+                hash |= 1UL << i;
+            }
+        }
+
+        return hash;
+    }
+
     /// <summary>
     /// 使用DCT算法计算图像的64位哈希
     /// </summary>

+ 109 - 0
Masuit.Tools.Abstractions/Media/ImageSharpTransformer.cs

@@ -1,7 +1,9 @@
 using System.IO;
+using SixLabors.ImageSharp;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing;
 using SixLabors.ImageSharp.Processing.Processors.Transforms;
+using static System.Net.WebRequestMethods;
 using Image = SixLabors.ImageSharp.Image;
 using Size = SixLabors.ImageSharp.Size;
 
@@ -15,6 +17,11 @@ public class ImageSharpTransformer : IImageTransformer
     public byte[] TransformImage(Stream stream, int width, int height)
     {
         using var image = Image.Load<Rgba32>(stream);
+        return TransformImage(image, width, height);
+    }
+
+    public byte[] TransformImage(Image<Rgba32> image, int width, int height)
+    {
         image.Mutate(x => x.Resize(new ResizeOptions()
         {
             Size = new Size
@@ -37,3 +44,105 @@ public class ImageSharpTransformer : IImageTransformer
         return bytes;
     }
 }
+
+public static class ImageHashExt
+{
+    /// <summary>
+    /// 使用平均值算法计算图像的64位哈希
+    /// </summary>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public static ulong AverageHash64(this Image<Rgba32> image)
+    {
+        var hasher = new ImageHasher();
+        return hasher.AverageHash64(image);
+    }
+
+    /// <summary>
+    /// 使用中值算法计算给定图像的64位哈希
+    /// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
+    /// </summary>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public static ulong MedianHash64(this Image<Rgba32> image)
+    {
+        var hasher = new ImageHasher();
+        return hasher.MedianHash64(image);
+    }
+
+    /// <summary>
+    /// 使用中值算法计算给定图像的256位哈希
+    /// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
+    /// </summary>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>256位hash值,生成一个4长度的数组返回</returns>
+    public static ulong[] MedianHash256(this Image<Rgba32> image)
+    {
+        var hasher = new ImageHasher();
+        return hasher.MedianHash256(image);
+    }
+
+    /// <summary>
+    /// 使用差分哈希算法计算图像的64位哈希。
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public static ulong DifferenceHash64(this Image<Rgba32> image)
+    {
+        var hasher = new ImageHasher();
+        return hasher.DifferenceHash64(image);
+    }
+
+    /// <summary>
+    /// 使用差分哈希算法计算图像的64位哈希。
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>256位hash值</returns>
+    public static ulong[] DifferenceHash256(this Image<Rgba32> image)
+    {
+        var hasher = new ImageHasher();
+        return hasher.DifferenceHash256(image);
+    }
+
+    /// <summary>
+    /// 使用DCT算法计算图像的64位哈希
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="image">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public static ulong DctHash(this Image<Rgba32> image)
+    {
+        var hasher = new ImageHasher();
+        return hasher.DctHash(image);
+    }
+
+    /// <summary>
+    /// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
+    /// </summary>
+    /// <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)
+    {
+        var hasher = new ImageHasher();
+        var hash1 = hasher.DifferenceHash256(image1);
+        var hash2 = hasher.DifferenceHash256(image2);
+        return ImageHasher.Compare(hash1, hash2);
+    }
+
+    /// <summary>
+    /// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
+    /// </summary>
+    /// <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)
+    {
+        var hasher = new ImageHasher();
+        var hash1 = hasher.DifferenceHash256(image1);
+        var hash2 = hasher.DifferenceHash256(image2path);
+        return ImageHasher.Compare(hash1, hash2);
+    }
+}

+ 1 - 1
Masuit.Tools.AspNetCore/Masuit.Tools.AspNetCore.csproj

@@ -18,7 +18,7 @@
         <LangVersion>latest</LangVersion>
         <RepositoryType>Github</RepositoryType>
         <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
-        <Version>1.1.0.2</Version>
+        <Version>1.1.1</Version>
         <FileVersion>1.0</FileVersion>
         <Company>masuit.com</Company>
         <AssemblyVersion>1.0</AssemblyVersion>

+ 1 - 1
Masuit.Tools.Core/Masuit.Tools.Core.csproj

@@ -18,7 +18,7 @@ github:https://github.com/ldqk/Masuit.Tools
         <UserSecretsId>830c282f-f7c1-42be-8651-4cd06ac8e73f</UserSecretsId>
         <RepositoryType>Github</RepositoryType>
         <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
-        <Version>2.5.0.2</Version>
+        <Version>2.5.1</Version>
         <FileVersion>2.4.5.6</FileVersion>
         <Company>masuit.com</Company>
         <AssemblyVersion>2.4.5.6</AssemblyVersion>

+ 1 - 1
Masuit.Tools/package.nuspec

@@ -2,7 +2,7 @@
 <package>
   <metadata>
     <id>Masuit.Tools.Net</id>
-    <version>2.5.0.2</version>
+    <version>2.5.1</version>
     <title>Masuit.Tools</title>
     <authors>懒得勤快</authors>
     <owners>masuit.com</owners>