65.9K
CodeProject 正在变化。 阅读更多。
Home

简单的 .NET 图像比较

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (192投票s)

2012年4月27日

CPOL

8分钟阅读

viewsIcon

766608

downloadIcon

47608

一组 .NET 扩展方法,用于获取图像之间的差异等...

引言  

最初是一组用于 System.Drawing.Image 类的简单扩展方法,它允许你

  • 根据你选择的阈值,以百分比值的形式找出两张图片有多大差异
  • 获取差异图像,显示出两者不同的地方
  • 获取原始数据的差异,供你自己使用  

根据收到的反馈,该解决方案已扩展为

  • 一个可供 COM 调用的 DLL(仍需测试 - 有人吗?)
  • 一个控制台版本,可以将两张图片的路径作为参数,并以错误级别返回差异
  • 在文件夹中或图像路径列表中查找重复项的能力

另外,你还可以获得用于调整图像大小或将其转换为灰度的扩展方法。 

根据在这里收到的反馈,我

背景

你无需阅读本章即可使用这些扩展方法,所以如果你不关心我是如何创建软件的 - 只需跳到“使用代码”部分.

有一天晚上,我兴致勃勃地编写了一个小工具,用于从 RSS feed 的帖子中下载图片,这时我产生了第一个想法:“如果博客帖子中的链接指向另一个被引用为来源的帖子,为什么不让代码去那里检查是否有更高分辨率的图片版本呢?”然后我想,“但是原始图片的名称可能与我在帖子中第一次看到的名称不同” Frown | <img src=

所以,我必须找到一种方法来比较两张图片的视觉表示。我数学不是特别好,所以在谷歌搜索并找到各种使用 小波、关键点匹配等算法(这似乎超出了我的理解范围 Wink | <img src= )后,我发现有些人使用 直方图 取得了不错的效果。所以,我首先沿着这条路走了。

直方图

直方图是一种表示图片中包含哪些颜色类型的方法。你可以创建一个直方图来描述图像中的亮度或红色、绿色或蓝色值。创建直方图的基本方法是查看位图中的每个像素,然后找出你正在查看的属性(RGB)的值。对于每个可能的值(通常是 0-255),你都有一个变量,你对其进行递增。这样,当你处理完所有像素后,你就可以迭代这些变量,看看有多少像素具有低值、中值和高值的亮度、R、G 或 B。我为此折腾了几个小时,但最终发现,仅仅通过直方图不足以检测图片中的差异,因为两张完全不同的图片,如果它们描绘了几乎相同的东西(例如,两片玉米地),并且具有相同的颜色/亮度构成,那么就很难区分。我尝试通过平均颜色和 方差 来比较它们,但都没有成功。

好消息是,作为我工作的副产品,现在有了 Bitmap 类的直方图扩展方法:GetRgbHistogramGetRgbHistogramBitmap

//get a histogram as an image
Bitmap bmpHist =  img1.GetRgbHistogramBitmap();   
//save it
bmpHist.Save("C:\\bmphist.png");

//get a histogram object
Histogram hist =  img1.GetRgbHistogram(); //basically three arrays for RGB values
//show it in the console
Console.WriteLine(hist.ToString());

请随意使用它们 Smile | <img src= 
注意:基于 Svansickle 在这篇关于直方图的文章上的评论,我实现了 Bhattacharyya 直方图算法,这是一种基于归一化直方图比较两张图片的方法。
你可以尝试一下,看看这种比较两张图片的方法是否更适合你的目的。我添加此功能是因为它看起来很有趣,我很想知道你是否找到了它的用途:)

简化

对我来说,直方图是一条死胡同。但在谷歌搜索更多之后,我读到一些论坛帖子建议,如果将图片缩小到很小的尺寸,甚至将其转换为灰度,那么差异会更快、更简单地找到。值得一试。

使用 .NET Framework,我可以轻松地调整图像大小。然后我找到了 一些可以将图像转换为灰度的代码。现在,剩下的就是遍历两张图片的像素进行比较,然后找出有多少像素是不同的。

这里是我使用的两张图片,包括一个 XBOX 控制器、一张带文字和不带文字的便签,以及两支不同颜色的笔。

我想先使用每张图片的灰度、16x16 像素版本,看看是否需要更高的分辨率才能实际使用。

两张 16x16 像素图片的放大版本

对于每个像素,我将计算其亮度值与另一张图片相同位置的像素的亮度值之差,并将其保存在一个双精度字节数组中(因为 Bitmap 中的 R、G 和 B 值可以在 0 到 255 之间)。然后,我将计算双精度数组中所有非零值的数量,除以图像中的像素数量(256),然后就得到了百分比的差异值 - 对吗?... 不完全对 Smile | <img src= 

正如你在这里看到的,它足以可视化两张图片之间的差异

...而且低分辨率似乎还可以 - 太棒了!Big Grin | <img src=  

但是...!

不过有个小问题。我的算法还找到了肉眼看不见的差异。在这里或那里,JPEG 在相同分辨率下重新编码,或者相同分辨率但不同图片的图像突然显示出大量值为 1 或 2 的差异。这很容易导致我出现五十个像素的差异,而 255 个像素中的五十个大约是 20%,这使得算法过于粗糙,因为人眼可能看不到任何差异。所以我引入了一个阈值(差异必须超过这个值才会被计算)。

使用阈值

在这里,你可以看到一张 200 像素宽的图片和同一张图片的 100 像素宽版本之间的差异

你可以看到导致上述结果的阈值(红色文本)。默认情况下,低于 4 的像素差异值现在被视为没有差异,并且可以通过提供一个可选参数给扩展方法来调整。

//find out how different two images are, with a threshold of 5 in the lightness of pixels
int threshold = 5;
float percentageDifference = img1.PercentageDifference(img2, threshold);

这还可以让你根据自己的需求调整代码的灵敏度。对我来说,默认阈值为三就可以了,但请随意尝试,根据你的心情或任务需求进行调整。

大家,这就是全部内容了!

我的故事就到这里。我现在能够检测两张图片是否相似,它们差异有多大,以及在哪里不同,这正是我想要的 - 太棒了!XD。

希望你喜欢阅读我的小编码故事,希望你能以某种方式使用这些代码。

此致 - Jakob "XnaFan" Krarup。

使用代码

获取 DLL

你只需要下载 DLL 或完整解决方案(见文章顶部),在你的代码文件中添加对 XnaFan.ImageComparison.dll 的引用,以及一个 using XnaFan.ImageComparison 语句 - 这样就可以开始使用了。

使用 ImageTool 的公共方法

//use this method to find the difference between two images (returns a float between 0 and 1)
float GetPercentageDifference(string image1Path, string image2Path, byte threshold = 3)

//use this method to find the difference in percent between the Bhattacharyya histograms
//Bhattacharyya histogram is a normalized histogram, see comments...
float GetBhattacharyyaDifference(string image1Path, string image2Path)

//Use these methods to get a list of duplicate images in a specific folder
​List<List<string>> GetDuplicateImages(string folderPath, bool checkSubfolders)
List<List<string>> GetDuplicateImages(IEnumerable<string> pathsOfPossibleDuplicateImages)

使用 Image 和 Bitmap 的扩展方法

//get the difference between an image and another
float PercentageDifference(this Image img1, Image img2, byte threshold = 3)

//get a visualization of the difference between an image and another
//  (see "Visualizing the differences" below)
Bitmap GetDifferenceImage(this Image img1, Image img2, 
        bool adjustColorSchemeToMaxDifferenceFound = false, bool absoluteText = false)

//get the differences as a 16x16 array of bytes
byte[,] GetDifferences(this Image img1, Image img2)

//get the grayscalevalues as a 16x16 array of bytes
byte[,] GetGrayScaleValues(this Image img)

//get a grayscaled version of an image
Image GetGrayScaleVersion(this Image original)

//get a resized version of an image
Image Resize(this Image originalImage, int newWidth, int newHeight)

//get a bitmap containing the histogram for an image (see "Histograms" above)
Bitmap GetRgbHistogramBitmap(this Bitmap bmp)

//get histogram information about bitmap
Histogram GetRgbHistogram(this Bitmap bmp)

可视化差异

我包含了使用黑色到粉红色的调色板(对应于 0 到 255 的值)或让调色板映射到当前最大值的可能性,来对差异位图进行着色。这将使你能够根据需要突出显示小差异或保持它们暗淡。在这里看到差异

这是使用参数 adjustColorSchemeToMaxDifferenceFound 为 true 或 false 时的差异

Bitmap diffNoAdjust = bmp1.GetDifferenceBitmap(bmp2);
Bitmap diffAdjusted = bmp1.GetDifferenceBitmap(bmp2, true);

在使用代码时遇到任何问题,或有改进建议 - 请告诉我。

包含示例控制台和 WPF 应用程序

为了帮助你入门,我包含了一个示例控制台应用程序和一个 WPF 应用 - 展示了如何使用代码。WPF 还有代码可以让你在 System.Drawing.Image(“常规” .NET)和 System.Windows.Media.Imaging.BitmapSource(WPF)之间进行转换。我刚开始接触 WPF,所以这是一次失败中学习的经历。不要在那里寻找任何最佳实践 ;-D。

控制台版本 

一位读者要求提供该功能的命令行版本,所以现在也有了一个命令行版本,它将图像之间的差异返回为错误级别,这样你就可以从批处理文件或其他编程语言中使用它。

用法 

 ImageComparisonConsole.exe [image1 path] [image2 path] 

这是一个使用它的示例批处理文件: 

@echo off 
<pre>REM saving paths to images 
REM you can also use absolute paths. i.e "C:\something.png"  
set image1="firefox1.png" 
set image2="firefox2.png"  
REM print what is about to happen
echo 'ConsoleImageComparison.exe %image1% %image2%'   
REM execute the program  
call ConsoleImageComparison.exe %image1% %image2% 
REM tell what the detected difference is 
echo The difference is %ERRORLEVEL%%%  

你只需将两个图像文件拖到控制台应用程序上,就会设置错误级别,或者将它们拖到一个调用应用程序并显示错误级别的批处理文件中,等等。

这只是一个工具 - 你决定如何使用它:)

历史 

  • 2012 年 4 月 - 1.0 版本。 
  • 2012 年 12 月 - 1.1 版本 - 小澄清
  • 2013 年 1 月 - 1.2 版本 - 添加了控制台版本,该版本设置错误级别
  • 2014 年 9 月 - 1.3 版本 -释放了图片 - 感谢下面的评论者:)
  • 2014 年 11 月 - 1.4 版本 - 比较方法的 COM 兼容性
  • 2014 年 11 月 - 1.5 版本 - 添加了查找重复项功能
  • 2013 年 11 月 - 1.6 版本 - 实现Bhattacharyya(归一化)直方图,并释放了 Image 对象(感谢评论者!... 你知道你是谁 ;-))
  • 2021 年 6 月 - 在 Github 上发布了 .NET Core 版本 
    • 仅包含比较两张图片和查找图片重复项的核心功能
    • 并行版本,用于更快地比较(使用 Parallel.ForEach)
    • 清理了功能、命名和注释
© . All rights reserved.