You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:08:01