简单的 .NET 图像比较






4.89/5 (192投票s)
一组 .NET 扩展方法,用于获取图像之间的差异等...
最初是一组用于 System.Drawing.Image
类的简单扩展方法,它允许你
- 根据你选择的阈值,以百分比值的形式找出两张图片有多大差异
- 获取差异图像,显示出两者不同的地方
- 获取原始数据的差异,供你自己使用
根据收到的反馈,该解决方案已扩展为
- 一个可供 COM 调用的 DLL(仍需测试 - 有人吗?)
- 一个控制台版本,可以将两张图片的路径作为参数,并以错误级别返回差异
- 在文件夹中或图像路径列表中查找重复项的能力
另外,你还可以获得用于调整图像大小或将其转换为灰度的扩展方法。
根据在这里收到的反馈,我
背景
你无需阅读本章即可使用这些扩展方法,所以如果你不关心我是如何创建软件的 - 只需跳到“使用代码”部分.
有一天晚上,我兴致勃勃地编写了一个小工具,用于从 RSS feed 的帖子中下载图片,这时我产生了第一个想法:“如果博客帖子中的链接指向另一个被引用为来源的帖子,为什么不让代码去那里检查是否有更高分辨率的图片版本呢?”然后我想,“但是原始图片的名称可能与我在帖子中第一次看到的名称不同”
所以,我必须找到一种方法来比较两张图片的视觉表示。我数学不是特别好,所以在谷歌搜索并找到各种使用 小波、关键点匹配等算法(这似乎超出了我的理解范围 )后,我发现有些人使用 直方图 取得了不错的效果。所以,我首先沿着这条路走了。
直方图
直方图是一种表示图片中包含哪些颜色类型的方法。你可以创建一个直方图来描述图像中的亮度或红色、绿色或蓝色值。创建直方图的基本方法是查看位图中的每个像素,然后找出你正在查看的属性(RGB)的值。对于每个可能的值(通常是 0-255),你都有一个变量,你对其进行递增。这样,当你处理完所有像素后,你就可以迭代这些变量,看看有多少像素具有低值、中值和高值的亮度、R、G 或 B。我为此折腾了几个小时,但最终发现,仅仅通过直方图不足以检测图片中的差异,因为两张完全不同的图片,如果它们描绘了几乎相同的东西(例如,两片玉米地),并且具有相同的颜色/亮度构成,那么就很难区分。我尝试通过平均颜色和 方差 来比较它们,但都没有成功。
好消息是,作为我工作的副产品,现在有了 Bitmap
类的直方图扩展方法:GetRgbHistogram
和 GetRgbHistogramBitmap
。
//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());
请随意使用它们
注意:基于 Svansickle 在这篇关于直方图的文章上的评论,我实现了 Bhattacharyya 直方图算法,这是一种基于归一化直方图比较两张图片的方法。
你可以尝试一下,看看这种比较两张图片的方法是否更适合你的目的。我添加此功能是因为它看起来很有趣,我很想知道你是否找到了它的用途:)
简化
对我来说,直方图是一条死胡同。但在谷歌搜索更多之后,我读到一些论坛帖子建议,如果将图片缩小到很小的尺寸,甚至将其转换为灰度,那么差异会更快、更简单地找到。值得一试。
使用 .NET Framework,我可以轻松地调整图像大小。然后我找到了 一些可以将图像转换为灰度的代码。现在,剩下的就是遍历两张图片的像素进行比较,然后找出有多少像素是不同的。
这里是我使用的两张图片,包括一个 XBOX 控制器、一张带文字和不带文字的便签,以及两支不同颜色的笔。
我想先使用每张图片的灰度、16x16 像素版本,看看是否需要更高的分辨率才能实际使用。
对于每个像素,我将计算其亮度值与另一张图片相同位置的像素的亮度值之差,并将其保存在一个双精度字节数组中(因为 Bitmap
中的 R、G 和 B 值可以在 0 到 255 之间)。然后,我将计算双精度数组中所有非零值的数量,除以图像中的像素数量(256),然后就得到了百分比的差异值 - 对吗?... 不完全对
正如你在这里看到的,它足以可视化两张图片之间的差异
...而且低分辨率似乎还可以 - 太棒了!
但是...!
不过有个小问题。我的算法还找到了肉眼看不见的差异。在这里或那里,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)
- 清理了功能、命名和注释