基于C#实现图像相似度百分比计算的技术问询及代码修正
修复并完善C#图片相似度比较方法
我来帮你搞定这段图片相似度比较的代码,先梳理下原代码里的几个问题,再给出优化后的完整实现:
首先是原代码的问题点:
- 拼写错误:
heigth应该是height,而且比较高度时误写成了和bmp2.width对比,正确应该是bmp2.height contador++语句末尾缺少分号,会导致编译报错- 方法定义为
void,无法返回相似度百分比结果,需要修改返回类型 - 使用
GetPixel方法性能很差,尤其是大图片,建议改用LockBits直接操作像素数据提升效率 - 缺少参数校验,没处理
bmp1或bmp2为null的情况 - 仅判断像素完全相等过于严格,实际场景可以加颜色阈值,允许细微颜色差异
下面是优化后的完整代码,包含两种匹配模式,还能返回百分比结果:
using System; using System.Drawing; using System.Drawing.Imaging; public static class ImageComparer { // 严格匹配:像素完全相等才算匹配 public static double CompareStrict(Bitmap bmp1, Bitmap bmp2) { return CompareImages(bmp1, bmp2, 0); } // 阈值匹配:RGB差值总和小于阈值就算匹配(阈值建议0-255,值越大允许差异越大) public static double CompareWithThreshold(Bitmap bmp1, Bitmap bmp2, int colorThreshold) { if (colorThreshold < 0 || colorThreshold > 255) throw new ArgumentOutOfRangeException(nameof(colorThreshold), "阈值必须在0-255之间"); return CompareImages(bmp1, bmp2, colorThreshold); } private static double CompareImages(Bitmap bmp1, Bitmap bmp2, int colorThreshold) { // 参数非空校验 if (bmp1 == null) throw new ArgumentNullException(nameof(bmp1)); if (bmp2 == null) throw new ArgumentNullException(nameof(bmp2)); // 取两张图片的最小宽高作为比较范围 int width = Math.Min(bmp1.Width, bmp2.Width); int height = Math.Min(bmp1.Height, bmp2.Height); if (width == 0 || height == 0) return 0; // 空图片相似度为0 int totalPixels = width * height; int matchingPixels = 0; // 使用LockBits提升像素读取性能 BitmapData bmpData1 = bmp1.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bmp1.PixelFormat); BitmapData bmpData2 = bmp2.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bmp2.PixelFormat); try { IntPtr ptr1 = bmpData1.Scan0; IntPtr ptr2 = bmpData2.Scan0; int bytesPerRow1 = bmpData1.Stride; int bytesPerRow2 = bmpData2.Stride; int bytesPerPixel = Image.GetPixelFormatSize(bmp1.PixelFormat) / 8; // 遍历每一个像素 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int offset1 = y * bytesPerRow1 + x * bytesPerPixel; int offset2 = y * bytesPerRow2 + x * bytesPerPixel; // 读取RGB颜色值(适配24/32位彩色图,其他格式需调整) Color color1 = Color.FromArgb( System.Runtime.InteropServices.Marshal.ReadByte(ptr1, offset1 + 2), System.Runtime.InteropServices.Marshal.ReadByte(ptr1, offset1 + 1), System.Runtime.InteropServices.Marshal.ReadByte(ptr1, offset1) ); Color color2 = Color.FromArgb( System.Runtime.InteropServices.Marshal.ReadByte(ptr2, offset2 + 2), System.Runtime.InteropServices.Marshal.ReadByte(ptr2, offset2 + 1), System.Runtime.InteropServices.Marshal.ReadByte(ptr2, offset2) ); // 判断像素是否匹配 if (colorThreshold == 0) { if (color1 == color2) matchingPixels++; } else { int redDiff = Math.Abs(color1.R - color2.R); int greenDiff = Math.Abs(color1.G - color2.G); int blueDiff = Math.Abs(color1.B - color2.B); if (redDiff + greenDiff + blueDiff <= colorThreshold) matchingPixels++; } } } } finally { // 解锁像素数据,避免内存泄漏 bmp1.UnlockBits(bmpData1); bmp2.UnlockBits(bmpData2); } // 计算并返回相似度百分比(保留两位小数) return Math.Round((double)matchingPixels / totalPixels * 100, 2); } }
使用示例
// 加载图片 Bitmap img1 = new Bitmap("test1.jpg"); Bitmap img2 = new Bitmap("test2.jpg"); // 严格匹配相似度 double strictSim = ImageComparer.CompareStrict(img1, img2); Console.WriteLine($"严格匹配相似度:{strictSim}%"); // 允许颜色差异总和不超过30的匹配 double thresholdSim = ImageComparer.CompareWithThreshold(img1, img2, 30); Console.WriteLine($"阈值匹配相似度:{thresholdSim}%");
额外说明
- 如果要处理灰度图或其他像素格式,需要调整
Color.FromArgb部分的字节读取逻辑 - 对于尺寸差异大的图片,可以先将其中一张缩放到另一张的尺寸再比较,结果会更合理
LockBits的性能比GetPixel高很多,尤其是高分辨率图片效果明显
内容的提问来源于stack exchange,提问作者Cristian Gerani
相关产品推荐
相关产品推荐

