大图中定位小图对比图像相似度

/ C# / 0 条评论 / 3536浏览

需求:

对比图像相似度

场景:

印刷厂流水线上使用,用于检测书页是否是错页。设备启动前,通过basler相机抓拍图片,选择某一块区域作为样图。设备启动后,每个产品过来都会触发相机拍照,然后比对查找样图、定位样图、对比样图,从而检测错页,并通知设备做进一步处理(如警报、剔除错误等操作)。

历史情况:

1.最早版本使用C#做界面,使用C++连接相机,使用opencv对比(定位、sift算法对比)。

2.由于个人对C++不熟悉,优化难度大,经常有程序崩溃问题,而且对比想过不理想。决定自己使用C#实现对比部分算法,界面依然使用C#开发

3.图片不太一致。由于设备并不精密,每次拍摄的图片存在上下左右的偏移、图片亮度也有差异、图片可能存在微量旋转

对比思路:

分析图片查找规律

分析图片发现基本上只有上下左右的偏移,偏移量在30px内(与摄像头焦距有关),旋转情况极少。因此主要解决偏移问题即可。

定位样图坐标

定位图片完整代码

特征点对象

class FeaturePosition
{
        /// <summary>
        /// X轴 第n个像素点
        /// </summary>
        private int x;
        /// <summary>
        /// Y轴 第n个像素点
        /// </summary>
        private int y;

        /// <summary>
        /// 中心点的值
        /// </summary>
        private int value;
        /// <summary>
        /// 平均值
        /// </summary>
        private float avgValue;
        /// <summary>
        /// 最小值
        /// </summary>
        private float minValue;
        /// <summary>
        /// 最大值
        /// </summary>
        private float maxValue;
        /// <summary>
        /// 超过有效值像素点个数
        /// </summary>
        private float count;
        
        .....
}

定位算法

    class FeaturePoint
    {
        /// <summary>
        /// 每个特征点大小 point square
        /// 比如特征点为 3*3 矩阵 则ps=1。 矩阵 = 2*ps+1
        /// </summary>
        private static int ps = 2;
        /// <summary>
        /// 选择特征点步长 feature step
        /// 必须 >= 矩阵,否则选择结构点有重复,浪费系统资源
        /// 默认值
        /// </summary>
        public static int DEFAULT_FS = 2;
        /// <summary>
        /// 有效色度,小于有效色度的才点可以作为特征点
        /// 默认值
        /// </summary>
        public static int DEFAULT_VALIDCHROMA = 100;
        /// <summary>
        /// 默认特征点数
        /// </summary>
        public static int DEFAULT_FEAUTERNUMBER = 100;

        /// <summary>
        /// 选择特征点步长 feature step
        /// 必须 >= 矩阵,否则选择结构点有重复,浪费系统资源
        /// </summary>
        private int fs = DEFAULT_FS;
        /// <summary>
        /// 有效色度,小于有效色度的才点可以作为特征点
        /// </summary>
        private int validChroma = DEFAULT_VALIDCHROMA;
        /// <summary>
        /// 特征点数
        /// </summary>
        private int feauterNumber = DEFAULT_FEAUTERNUMBER;
        /// <summary>
        /// 容错值 value,min,max,avg,count
        /// </summary>
        private int fault = 6;
        /// <summary>
        /// 偏移量
        /// </summary>
        private int offset = 30;

        /// <summary>
        /// 第一个黑点位置
        /// </summary>
        private List<Point> firstPoints = new List<Point> { new Point(0, 0) };
        /// <summary>
        /// 缩放深度
        /// </summary>
        private int deep = 0;

        public FeaturePoint(){}

        public FeaturePoint(int offset)
        {
            this.offset = offset;
        }

        public FeaturePoint(int offset, int fs, int validChroma, int feauterNumber)
        {
            this.offset = offset;
            this.fs = fs;
            this.validChroma = validChroma;
            this.feauterNumber = feauterNumber;
        }

        /// <summary>
        /// 遍历图片上的结构点
        /// </summary>
        /// <param name="bitmap"></param>
        /// <returns>特征点集合</returns>
        public List<FeaturePosition> imageErgodic(Bitmap bitmap)
        {
            List<FeaturePosition> list = new List<FeaturePosition>();

            int xm = 0;
            int ym = 0;
            int sideMin = Math.Min(bitmap.Width, bitmap.Height);
            int ws = bitmap.Width / sideMin;
            int hs = bitmap.Height / sideMin;
            int area = (2 * ps + 1) * (2 * ps + 1);

            for (int c = 0; c < sideMin; c += fs)
            {
                xm = c * ws + ws * fs - 1;
                ym = c * hs + hs * fs - 1;

                for (int y = ym - hs * fs + 1 + fs - 1; y >= 0 && y <= ym; y += fs)
                {
                    for (int x = fs - 1; x <= xm - ws * fs; x += fs)
                    {
                        FeaturePosition fp = generateFeaturePosition(bitmap, x, y, area);
                        if (fp != null && fp.Value < validChroma)
                        {
                            if (list.Count == 0)
                            {
                                firstPoints[deep] = new Point(x, y);
                            }

                            fp.X = fp.X - firstPoints[deep].X;
                            fp.Y = fp.Y - firstPoints[deep].Y;
                            list.Add(fp);
                        }
                    }
                }

                for (int x = xm - ws * fs + 1 + fs - 1; x >= 0 && x <= xm; x += fs)
                {
                    for (int y = fs - 1; y <= ym; y += fs)
                    {
                        FeaturePosition fp = generateFeaturePosition(bitmap, x, y, area);
                        if (fp != null && fp.Value < validChroma)
                        {
                            if (list.Count == 0)
                            {
                                firstPoints[deep] = new Point(x, y);
                            }

                            fp.X = fp.X - firstPoints[deep].X;
                            fp.Y = fp.Y - firstPoints[deep].Y;
                            list.Add(fp);
                        }
                    }
                }
            }
            return list;
        }

        /// <summary>
        /// 生成特征点
        /// </summary>
        /// <param name="bitmap">图片</param>
        /// <param name="x">坐标 X</param>
        /// <param name="y">坐标 Y</param>
        /// <param name="area">特征点面积</param>
        /// <returns>特征点</returns>
        private FeaturePosition generateFeaturePosition(Bitmap bitmap,int x,int y, int area)
        {
            FeaturePosition fp = new FeaturePosition();
            fp.X = x;
            fp.Y = y;
            int sum = 0;
            int max = 0;
            int min = 255;
            int count = 0;
            for(int i = x - ps; i <= x + ps; i++)
            {
                for(int j = y - ps; j <= y + ps; j++)
                {
                    if (i >= 0 && j >= 0 && i < bitmap.Width && j < bitmap.Height)
                    {
                        Color pc = bitmap.GetPixel(i, j);
                        //R、G、B 3色相同,使用任意一个均可
                        sum += pc.R;
                        if (pc.R > max) max = pc.R;
                        if (pc.R < min) min = pc.R;
                        if (pc.R <= validChroma) count++;
                        if (i == x && j == y) fp.Value = pc.R;
                    }
                }
            }
            fp.AvgValue = sum / area;
            fp.MinValue = min;
            fp.MaxValue = max;
            fp.Count = count;
            return fp;
        }

        public Point locationSampleImage(Bitmap sourceImage,Point samplePoint,Size sampleSize, List<FeaturePosition> featureList)
        {
            Point result = new Point(-1, -1);
            Point firstPoint = firstPoints[deep];
            float threshold = 0.0F;
            int valueFault = fault * 4;
            int countFault = fault;
            int area = (2 * ps + 1) * (2 * ps + 1);
            int feauterCount = featureList.Count;

            int checkPositionCount = feauterNumber;

            int compareX = Math.Max(samplePoint.X - offset + firstPoint.X, 0);
            int compareY = Math.Max(samplePoint.Y - offset + firstPoint.Y, 0);
            int compareWidth = Math.Min(samplePoint.X + offset + firstPoint.X, sourceImage.Width - sampleSize.Width + firstPoint.X);
            int compareHeight = Math.Min(samplePoint.Y + offset + firstPoint.Y, sourceImage.Height - sampleSize.Height + firstPoint.Y);

            for(int w = compareX; w <= compareWidth; w++)
            {
                for(int h = compareY; h <= compareHeight; h++)
                {
                    //非特征点直接跳过
                    if (sourceImage.GetPixel(w, h).R >= validChroma) continue;
                    //检测特征点,5成超过对比阀值直接跳过
                    //int goodPoint = 0;
                    //int validOne = Math.Min(checkPositionCount, 10);
                    //for (int s = 1; s <= validOne; s++)
                    //{
                    //    FeaturePosition sposition = featureList[s];
                    //    int x = w + sposition.X;
                    //    int y = h + sposition.Y;
                    //    if (x < 0 || x >= sourceImage.Width || y < 0 || y >= sourceImage.Height)
                    //    {
                    //        goodPoint = 0;
                    //        break;
                    //    }
                    //    if (sourceImage.GetPixel(x, y).R <= validChroma)
                    //    {
                    //        goodPoint++;
                    //    }
                    //}
                    //if ((float)goodPoint / checkPositionCount < 0.5) continue;


                    //对比后匹配特征点数
                    int validCount = 0;
                    //获取与特征点相同面积的像素点
                    for (int s = 0; s < feauterCount; s++)
                    {
                        FeaturePosition sposition = featureList[s];
                        FeaturePosition pposition = generateFeaturePosition(sourceImage, w + sposition.X, h + sposition.Y, area);

                        //检查是否匹配
                        int indexCount = 0;
                        if (Math.Abs(sposition.Value - pposition.Value) <= fault) indexCount++;
                        //if (Math.Abs(sposition.AvgValue - pposition.AvgValue) <= fault) indexCount++;
                        if (Math.Abs(sposition.MaxValue - pposition.MaxValue) <= fault) indexCount++;
                        if (Math.Abs(sposition.MinValue - pposition.MinValue) <= fault) indexCount++;
                        if ((float)Math.Abs(sposition.Count - pposition.Count) / area * 100 <= countFault) indexCount++;

                        //超过3个指标匹配则通过
                        if (indexCount >= 2) validCount++;
                    }
                    float compareValue = (float)validCount / checkPositionCount;
                    if (compareValue > threshold)
                    {
                        //Console.WriteLine("point=" + w + "," + h + " , validCount=" + validCount + ", checkPositionCount=" + checkPositionCount + ",compareValue=" + compareValue);
                        result = new Point(w - firstPoint.X, h - firstPoint.Y);
                        threshold = compareValue;
                        if (threshold > 0.95) break;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 缩放图片并生成结构点
        /// </summary>
        /// <param name="bitmap">原图</param>
        /// <param name="scalTimes">缩放次数</param>
        /// <param name="scalRadio">单次缩放比例 比如 0.5 新图长宽均为原图1/2</param>
        /// <returns>特征点集合</returns>
        public List<List<FeaturePosition>> scalImageErgodic(Bitmap bitmap, int scalTimes, double scalRadio)
        {
            //初始化第一个结构点位置
            firstPoints = new List<Point>();
            for(int i = 0; i < scalTimes; i++)
            {
                firstPoints.Add(new Point(0, 0));
            }
            //缩放图片生成结构点
            List<List<FeaturePosition>> result = new List<List<FeaturePosition>>();
            for (int t = 0; t < scalTimes; t++)
            {
                //bitmap.Save("c://images-" + t + ".jpg");
                deep = scalTimes - 1 - t;
                List<FeaturePosition> list = imageErgodic(bitmap);
                if (list.Count > feauterNumber)
                {
                    int interval = (int)Math.Round((double)list.Count / feauterNumber, 0);
                    List<FeaturePosition> listNew = new List<FeaturePosition>();
                    for (int i = 0; i < list.Count; i += interval)
                    {
                        FeaturePosition fp = list[i];
                        for (int j = 1; j < interval; j++)
                        {
                            if (list.Count > i + j && list[i + j].Count > fp.Count)
                            {
                                fp = list[i + j];
                            }
                        }
                        listNew.Add(fp);
                    }
                    list = listNew;
                }
                result.Insert(0, list);
                bitmap = BitmapScaleHelper.ScaleToSize(bitmap, scalRadio);
            }
            return result;
        }
        /// <summary>
        /// 缩放图片并生成特征点
        /// </summary>
        /// <param name="bitmap">图像</param>
        /// <returns>特征点集合</returns>
        public List<List<FeaturePosition>> scalImageErgodic(Bitmap bitmap)
        {
            return scalImageErgodic(bitmap, 4, 0.5);
        }


        /// <param name="sourceImage">原图</param>
        /// <param name="samplePoint">样图原位置</param>
        /// <param name="sampleSize">样图尺寸</param>
        /// <param name="featureList">样图特征点集合</param>
        /// <param name="scalRadio">缩放比例</param>
        public Point scalLocationSampleImage(Bitmap sourceImage, Point samplePoint, Size sampleSize, List<List<FeaturePosition>> featureList, double scalRadio)
        {
            Point localtionPoint = new Point(-1, -1);
            int listCount = featureList.Count;
            double smallRadio = Math.Pow(scalRadio, listCount - 1);
            Point smallPoint = new Point((int)(samplePoint.X * smallRadio), (int)(samplePoint.Y * smallRadio));

            for (int t = 0; t < listCount; t++)
            {
                smallRadio = Math.Pow(scalRadio, listCount - 1 - t);
                Bitmap smallBitmap = BitmapScaleHelper.ScaleToSize(sourceImage, smallRadio);
                Size smallSize = new Size((int)(sampleSize.Width * smallRadio), (int)(sampleSize.Height * smallRadio));
                deep = t;
                localtionPoint = locationSampleImage(smallBitmap, smallPoint, smallSize, featureList[t]);

                if (t != listCount - 1)
                {
                    smallPoint = new Point((int)((double)localtionPoint.X / scalRadio), (int)((double)localtionPoint.Y / scalRadio));
                }
                //smallBitmap.Save("c://imagep-" + t + ".jpg");
            }
            return localtionPoint;
        }
        /// <summary>
        /// 定位样图位置
        /// </summary>
        /// <param name="sourceImage">原图</param>
        /// <param name="samplePoint">样图原位置</param>
        /// <param name="sampleSize">样图尺寸</param>
        /// <param name="featureList">样图特征点集合</param>
        /// <returns></returns>
        public Point scalLocationSampleImage(Bitmap sourceImage, Point samplePoint, Size sampleSize, List<List<FeaturePosition>> featureList)
        {
            return scalLocationSampleImage(sourceImage, samplePoint, sampleSize, featureList, 0.5);
        }



        /// <summary>
        /// 生成特征点
        /// </summary>
        /// <param name="bitmap">图片</param>
        /// <param name="x">坐标 X</param>
        /// <param name="y">坐标 Y</param>
        /// <returns>特征点</returns>
        public FeaturePosition checkBitmapByPosition(Bitmap bitmap)
        {
            bitmap = BitmapScaleHelper.ScaleToSize(bitmap, Math.Pow(0.5, 6));
            FeaturePosition fp = new FeaturePosition();
            long sum = 0;
            int max = 0;
            int min = 255;
            int count = 0;
            for (int i = 0; i < bitmap.Width; i++)
            {
                for (int j = 0; j < bitmap.Height; j++)
                {
                    Color pc = bitmap.GetPixel(i, j);
                    //R、G、B 3色相同,使用任意一个均可
                    sum += pc.R;
                    if (pc.R > max) max = pc.R;
                    if (pc.R < min) min = pc.R;
                    if (pc.R <= validChroma) count++;
                }
            }
            fp.AvgValue = sum / bitmap.Width / bitmap.Height;
            fp.MinValue = min;
            fp.MaxValue = max;
            fp.Count = count;
            return fp;
        }
    }

图像处理工具类(缩放,剪切 等)

/// <summary>
    /// BitmapHelper
    /// </summary>
    public static class BitmapScaleHelper
    {
        /// <summary>
        /// 缩放图片
        /// </summary>
        /// <param name="bitmap">原图片</param>
        /// <param name="width">新图片宽度</param>
        /// <param name="height">新图片高度</param>
        /// <returns>新图片</returns>
        public static Bitmap ScaleToSize(this Bitmap bitmap, int width, int height)
        {
            if (bitmap.Width == width && bitmap.Height == height)
            {
                return bitmap;
            }

            var scaledBitmap = new Bitmap(width, height);
            using (var g = Graphics.FromImage(scaledBitmap))
            {
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                g.DrawImage(bitmap, 0, 0, width, height);
            }

            return scaledBitmap;
        }

        /// <summary>
        /// 缩放图片
        /// </summary>
        /// <param name="bitmap">原图片</param>
        /// <param name="size">新图片大小</param>
        /// <returns>新图片</returns>
        public static Bitmap ScaleToSize(this Bitmap bitmap, Size size)
        {
            return bitmap.ScaleToSize(size.Width, size.Height);
        }

        /// <summary>
        /// 按比例来缩放
        /// </summary>
        /// <param name="bitmap">原图</param>
        /// <param name="ratio">ratio大于1,则放大;否则缩小</param>
        /// <returns>新图片</returns>
        public static Bitmap ScaleToSize(this Bitmap bitmap, double ratio)
        {
            return bitmap.ScaleToSize((int)(bitmap.Width * ratio), (int)(bitmap.Height * ratio));
        }

        /// <summary>
        /// 按给定长度/宽度等比例缩放
        /// </summary>
        /// <param name="bitmap">原图</param>
        /// <param name="width">新图片宽度</param>
        /// <param name="height">新图片高度</param>
        /// <returns>新图片</returns>
        public static Bitmap ScaleProportional(this Bitmap bitmap, int width, int height)
        {
            float proportionalWidth, proportionalHeight;

            if (width.Equals(0))
            {
                proportionalWidth = ((float)height) / bitmap.Size.Height * bitmap.Width;
                proportionalHeight = height;
            }
            else if (height.Equals(0))
            {
                proportionalWidth = width;
                proportionalHeight = ((float)width) / bitmap.Size.Width * bitmap.Height;
            }
            else if (((float)width) / bitmap.Size.Width * bitmap.Size.Height <= height)
            {
                proportionalWidth = width;
                proportionalHeight = ((float)width) / bitmap.Size.Width * bitmap.Height;
            }
            else
            {
                proportionalWidth = ((float)height) / bitmap.Size.Height * bitmap.Width;
                proportionalHeight = height;
            }

            return bitmap.ScaleToSize((int)proportionalWidth, (int)proportionalHeight);
        }

        /// <summary>
        /// 按给定长度/宽度缩放,同时可以设置背景色
        /// </summary>
        /// <param name="bitmap">原图</param>
        /// <param name="backgroundColor">背景色</param>
        /// <param name="width">新图片宽度</param>
        /// <param name="height">新图片高度</param>
        /// <returns></returns>
        public static Bitmap ScaleToSize(this Bitmap bitmap, Color backgroundColor, int width, int height)
        {
            var scaledBitmap = new Bitmap(width, height);
            using (var g = Graphics.FromImage(scaledBitmap))
            {
                g.Clear(backgroundColor);

                var proportionalBitmap = bitmap.ScaleProportional(width, height);

                var imagePosition = new Point((int)((width - proportionalBitmap.Width) / 2m), (int)((height - proportionalBitmap.Height) / 2m));
                g.DrawImage(proportionalBitmap, imagePosition);
            }

            return scaledBitmap;
        }
    }

生成样图特征点

特征点就是图片上比较特殊的点,最好是能支持旋转定位。对于我来说难度太大,我直接根据颜色值<100点来生成。第一个特征点firstPoint的位置很重要,最好是左上方第一个符合要求的点。这样其他特征点都以firstPointz为坐标,后续计算中都是正整数,理解起来也容易些。

分析 4*8个像素点

0,0   1,0  2,0  3,0

0,1   1,1  2,1  3,1

0,2   1,2  2,2  3,2

0,3   1,3  2,3  3,3

0,4   1,4  2,4  3,4

0,5   1,5  2,5  3,5

0,6   1,6  2,6  3,6

0,7   1,7  2,7  3,7

从0,0开始,步长为1 遍历像素点

0,0
0,1
0,2
0,3
1,0
1,1
1,2
1,3
0,4
1,4
0,5
1,5
2,0
2,1
2,2
2,3
2,4
2,5
...

从0,0开始,步长为2遍历像素点

1,1
1,3
1,5
1,7
3,1
3,3
3,5
3,7

代码实现,请回头查阅imageErgodic这个方法

步长有什么作用

每个像素遍历,效率必然很低。相当于图片中每个点与样图对比。步长可以在保证结构不变情况下较少特征点数量

每个特征点是x,y坐标上n平方矩阵整体情况,步长>(n-1)/2 可以减避免征点重叠,较少系统资源浪费

从抓拍的图片中定位特征点

遍历行还是遍历列?

都一样,因为图片有可能长度较大,也有可能宽度较大。

从坐标0,0开始遍历?

我的情况应该不需要,因为在选择样图时。可以知道样图的坐标以及大小。偏移不会太大,上线左右+-30像素应该能满足需求。那么可以在样图坐标点上下左右分别预留30像素。

到底怎么计算?

要查找得图片左上角的坐标点,会落在样图位置上下左右+-30像素点的区域。

结构点只过滤小于100颜色值得像素点,那么结构点第一个点并不一定是样图的0,0坐标

定义

偏移量 offset

样图坐标 samplePoint

第一个结构点坐标 firstPoint

那么对比时,第一个结构点可能落在的区域

int compareX = Math.Max(samplePoint.X - offset + firstPoint.X, 0);   // 注意坐标点X不能小于0
int compareY = Math.Max(samplePoint.Y - offset + firstPoint.Y, 0);   // 注意坐标点Y不能小于0
int compareWidth = Math.Min(samplePoint.X + offset + firstPoint.X, sourceImage.Width - sampleSize.Width + firstPoint.X);     // 注意宽度不能超出对比图像,sourceImage是要检测的图像
int compareHeight = Math.Min(samplePoint.Y + offset + firstPoint.Y, sourceImage.Height - sampleSize.Height + firstPoint.Y); // 注意高度不能超出对比图像,sourceImage是要检测的图像

代码实现,请回头查阅locationSampleImage这个方法

特征点取N平方个点?

相机拍摄的图片有事亮度会有微量不同,取单个像素点会有较高的误判率

这样就完成了吗?

执行代码在659494的图中定位241432的样图,耗时3679毫秒。这与理想的300毫秒内完成差距很大,还要想办法解决。

优化定位速度

确保在300毫米内完成定位

已经在最小的区域内查找了,范围不能再缩小了。一个一个像素点查找慢,是否有办法逐步定位,先锁定大范围再逐步缩小范围呢?

图片缩放。假如图片长宽缩放为0.5那么原来4个像素点变为1个,效率提升4倍。显然还是不能满足需求,那么多缩放几倍。经过测试缩放0.5倍,执行4次效果比较理想。相应的结构点也要保存4组。

代码实现,请回头查阅

scalImageErgodic 缩放生成结构点 scalLocationSampleImage 缩放定位图片

Hash算法计算相似度

以上两个类的代码来源于网络,大概思路是二值化、缩放后生成hash串作为图片的指纹,然后对比指纹的相似度。我测试效果比较满意

 class cat_pic_compression
    {

        /// <summary>
        /// 无损压缩图片
        /// 删除原图覆盖为新图,如果不想覆盖,则去掉firstFileInfo.CopyTo(dFile);的注释,以及最后的
        ///   File.Delete(sFile);
        /// File.Move(dFile, sFile);两句代码
        /// </summary>
        /// <param name="sFile">原图片地址</param>
        /// <param name="dFile">压缩后保存图片地址</param>
        /// <param name="flag">压缩质量(数字越小压缩率越高)1-100</param>
        /// <param name="size">压缩后图片的最大大小</param>
        /// <param name="sfsc">是否是第一次调用</param>
        /// <returns></returns>
        public  bool CompressImage(string sFile, string dFile, int flag = 90, int size = 300, bool sfsc = true)
        {
            Image iSource = Image.FromFile(sFile);
            ImageFormat tFormat = iSource.RawFormat;
            //如果是第一次调用,原始图像的大小小于要压缩的大小,则直接复制文件,并且返回true

            //  FileInfo firstFileInfo = new FileInfo(sFile);
            FileStream fs = File.OpenRead(sFile); //OpenRead
            if (sfsc == true && fs.Length < size * 1024)
            {
                // firstFileInfo.CopyTo(dFile);
                //不再修改文件,直接返回true
                fs.Close();
                iSource.Dispose();//释放所有线程的资源,防止无法读取或删除
                Console.WriteLine("不需要压缩");
                return true;
            }

            int dHeight = iSource.Height / 2;
            int dWidth = iSource.Width / 2;
            int sW = 0, sH = 0;
            //按比例缩放
            Size tem_size = new Size(iSource.Width, iSource.Height);
            if (tem_size.Width > dHeight || tem_size.Width > dWidth)
            {
                if ((tem_size.Width * dHeight) > (tem_size.Width * dWidth))
                {
                    sW = dWidth;
                    sH = (dWidth * tem_size.Height) / tem_size.Width;
                }
                else
                {
                    sH = dHeight;
                    sW = (tem_size.Width * dHeight) / tem_size.Height;
                }
            }
            else
            {
                sW = tem_size.Width;
                sH = tem_size.Height;
            }

            Bitmap ob = new Bitmap(dWidth, dHeight);
            Graphics g = Graphics.FromImage(ob);

            g.Clear(Color.WhiteSmoke);
            g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

            g.DrawImage(iSource, new Rectangle((dWidth - sW) / 2, (dHeight - sH) / 2, sW, sH), 0, 0, iSource.Width, iSource.Height, GraphicsUnit.Pixel);

            g.Dispose();

            //以下代码为保存图片时,设置压缩质量
            EncoderParameters ep = new EncoderParameters();
            long[] qy = new long[1];
            qy[0] = flag;//设置压缩的比例1-100
            EncoderParameter eParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qy);
            ep.Param[0] = eParam;

            try
            {
                ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders();
                ImageCodecInfo jpegICIinfo = null;
                for (int x = 0; x < arrayICI.Length; x++)
                {
                    if (arrayICI[x].FormatDescription.Equals("JPEG"))
                    {
                        jpegICIinfo = arrayICI[x];
                        break;
                    }
                }
                if (jpegICIinfo != null)
                {
                    ob.Save(dFile, jpegICIinfo, ep);//dFile是压缩后的新路径
                    FileInfo fi = new FileInfo(dFile);
                    if (fi.Length > 1024 * size)
                    {
                        flag = flag - 10;
                        CompressImage(sFile, dFile, flag, size, false);
                    }
                }
                else
                {
                    ob.Save(dFile, tFormat);
                }
                return true;
            }
            catch
            {
                return false;
            }
            finally
            {
                iSource.Dispose();
                ob.Dispose();
                fs.Close();
                iSource.Dispose();//关闭掉所有正在运行的线程
                File.Delete(sFile);
                File.Move(dFile, sFile);// 删掉旧的替换成新的
            }
        }
    }
//计算直方图 返回
    class cat_pic_hash
    {

        public String GetHash(Image SourceImg)
        {
            Image image = ReduceSize(SourceImg);
            Byte[] grayValues = ReduceColor(image);
            Byte average = CalcAverage(grayValues);
            String reslut = ComputeBits(grayValues, average);
            return reslut;
        }

        // Step 1 : Reduce size to 8*8  改变大小8*8
        private Image ReduceSize(Image SourceImg, int width = 8, int height = 8)
        {
            Image image = SourceImg.GetThumbnailImage(width, height, () => { return false; }, IntPtr.Zero);
            return image;
        }

        // Step 2 : Reduce Color //转换色
        private Byte[] ReduceColor(Image image)
        {
            Bitmap bitMap = new Bitmap(image);
            Byte[] grayValues = new Byte[image.Width * image.Height];

            for (int x = 0; x < image.Width; x++)
                for (int y = 0; y < image.Height; y++)
                {
                    Color color = bitMap.GetPixel(x, y);
                    byte grayValue = (byte)((color.R * 30 + color.G * 59 + color.B * 11) / 100);
                    grayValues[x * image.Width + y] = grayValue;
                }
            return grayValues;
        }

        // Step 3 : Average the colors  平均颜色
        private Byte CalcAverage(byte[] values)
        {
            int sum = 0;
            for (int i = 0; i < values.Length; i++)
                sum += (int)values[i];
            return Convert.ToByte(sum / values.Length);
        }

        // Step 4 : Compute the bits 按位运算
        private String ComputeBits(byte[] values, byte averageValue)
        {
            char[] result = new char[values.Length];
            for (int i = 0; i < values.Length; i++)
            {
                if (values[i] < averageValue)
                    result[i] = '0';
                else
                    result[i] = '1';
            }
            return new String(result);
        }

        // Compare hash  比较哈希
        public static Int32 CalcSimilarDegree(string a, string b)
        {
            if (a.Length != b.Length)
                throw new ArgumentException();
            int count = 0;
            for (int i = 0; i < a.Length; i++)
            {
                if (a[i] != b[i])
                    count++;
            }
            return count;
        }
    }

调用方式如下

cat_pic_hash cat_hash = new cat_pic_hash();//计算hash的类

//生成hash
String hash1 = cat_hash.GetHash(image1);//Bitmap
String hash2 = cat_hash.GetHash(image2);//Bitmap
//对比hash
StringCompute compute = new StringCompute();
compute.SpeedyCompute(hash1, hash2);        // 计算相似度, 不记录比较时间
decimal rate = compute.ComputeResult.Rate;   // 相似度百分之几,完全匹配相似度为1

感谢你的阅读,希望能帮助到你