【问题标题】:How to find rectangle of difference between two images如何找到两个图像之间的差异矩形
【发布时间】:2010-04-24 14:06:45
【问题描述】:

我有两张相同大小的图片。找到它们不同的矩形的最佳方法是什么。显然我可以在不同的方向浏览图像 4 次,但我想知道是否有更简单的方法。

例子:

【问题讨论】:

  • 背景是否一模一样
  • 是的,我正在尝试找到最小的矩形,在该矩形之外一切都相同。
  • 您可以将图像(矩阵)彼此相减 - 在您没有得到零的任何地方,它们都是不同的。将此矩阵传递给一个算法,该算法计算所有行和列的总和(从外到内),直到找到一个非零值的算法,应该会给你红色矩形。
  • 一个有趣的库可能是 AForge。减去图像后,您可以使用它的 blob 处理功能:aforgenet.com/framework/features/blobs_processing.html,另请参阅此相关问题:stackoverflow.com/questions/1162669/…

标签: c# .net algorithm image comparison


【解决方案1】:

一种天真的方法是从原点开始,逐行、逐列地工作。比较每个像素,记下最顶部、最左侧、最右侧和最底部,从中可以计算出矩形。在某些情况下,这种单通道方法会更快(即差异区域非常小)

【讨论】:

    【解决方案2】:

    像这样的图像处理很昂贵,有很多东西要看。在实际应用中,您几乎总是需要对图像进行过滤,以消除由不完美的图像捕获引起的伪影。

    用于这种位重击的常用库是 OpenCV,它利用可用的专用 CPU 指令来加快速度。有几个可用的 .NET 包装器,Emgu is one of them

    【讨论】:

    • 谈论工件所以如果我这样做this 会得到错误的尺寸吗?因为如果你使用我的代码和他的前 2 张图片,你会发现它的高度几乎是合适的,但宽度却搞砸了
    【解决方案3】:

    如果您想要一个单个矩形,请使用 int.MaxValue 作为阈值。

    var diff = new ImageDiffUtil(filename1, filename2);
    var diffRectangles = diff.GetDiffRectangles(int.MaxValue);
    

    如果您想要多个矩形,请使用较小的阈值。

    var diff = new ImageDiffUtil(filename1, filename2);
    var diffRectangles = diff.GetDiffRectangles(8);
    

    ImageDiffUtil.cs

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    
    namespace diff_images
    {
        public class ImageDiffUtil
        {
            Bitmap image1;
            Bitmap image2;
    
            public ImageDiffUtil(string filename1, string filename2)
            {
                image1 = Image.FromFile(filename1) as Bitmap;
                image2 = Image.FromFile(filename2) as Bitmap;
            }
    
            public IList<Point> GetDiffPixels()
            {
                var widthRange = Enumerable.Range(0, image1.Width);
                var heightRange = Enumerable.Range(0, image1.Height);
    
                var result = widthRange
                                .SelectMany(x => heightRange, (x, y) => new Point(x, y))
                                .Select(point => new
                                {
                                    Point = point,
                                    Pixel1 = image1.GetPixel(point.X, point.Y),
                                    Pixel2 = image2.GetPixel(point.X, point.Y)
                                })
                                .Where(pair => pair.Pixel1 != pair.Pixel2)
                                .Select(pair => pair.Point)
                                .ToList();
    
                return result;
            }
    
            public IEnumerable<Rectangle> GetDiffRectangles(double distanceThreshold)
            {
                var result = new List<Rectangle>();
    
                var differentPixels = GetDiffPixels();
    
                while (differentPixels.Count > 0)
                {
                    var cluster = new List<Point>()
                    {
                        differentPixels[0]
                    };
                    differentPixels.RemoveAt(0);
    
                    while (true)
                    {
                        var left = cluster.Min(p => p.X);
                        var right = cluster.Max(p => p.X);
                        var top = cluster.Min(p => p.Y);
                        var bottom = cluster.Max(p => p.Y);
                        var width = Math.Max(right - left, 1);
                        var height = Math.Max(bottom - top, 1);
                        var clusterBox = new Rectangle(left, top, width, height);
    
                        var proximal = differentPixels
                                            .Where(point => GetDistance(clusterBox, point) <= distanceThreshold)
                                            .ToList();
                        proximal.ForEach(point => differentPixels.Remove(point));
    
                        if (proximal.Count == 0)
                        {
                            result.Add(clusterBox);
                            break;
                        }
                        else
                        {
                            cluster.AddRange(proximal);
                        }
                    };
                }
    
                return result;
            }
    
            static double GetDistance(Rectangle rect, Point p)
            {
                var dx = Math.Max(rect.Left - p.X, 0);
                dx = Math.Max(dx, p.X - rect.Right);
    
                var dy = Math.Max(rect.Top - p.Y, 0);
                dy = Math.Max(dy, p.Y - rect.Bottom);
                return Math.Sqrt(dx * dx + dy * dy);
            }
        }
    }
    

    Form1.cs

    using System.Drawing;
    using System.Linq;
    using System.Windows.Forms;
    
    namespace diff_images
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
    
                var filename1 = @"Gelatin1.PNG";
                var filename2 = @"Gelatin2.PNG";
    
                var diff = new ImageDiffUtil(filename1, filename2);
                var diffRectangles = diff.GetDiffRectangles(8);
    
                var img3 = Image.FromFile(filename2);
                Pen redPen = new Pen(Color.Red, 1);
                var padding = 3;
                using (var graphics = Graphics.FromImage(img3))
                {
                    diffRectangles
                        .ToList()
                        .ForEach(rect =>
                        {
                            var largerRect = new Rectangle(rect.X - padding, rect.Y - padding, rect.Width + padding * 2, rect.Height + padding * 2);
                            graphics.DrawRectangle(redPen, largerRect);
                        });
                }
    
                var pb1 = new PictureBox()
                {
                    Image = Image.FromFile(filename1),
                    Left = 8,
                    Top = 8,
                    SizeMode = PictureBoxSizeMode.AutoSize
                };
    
                var pb2 = new PictureBox()
                {
                    Image = Image.FromFile(filename2),
                    Left = pb1.Left + pb1.Width + 16,
                    Top = 8,
                    SizeMode = PictureBoxSizeMode.AutoSize
                };
    
                var pb3 = new PictureBox()
                {
                    Image = img3,
                    Left = pb2.Left + pb2.Width + 16,
                    Top = 8,
                    SizeMode = PictureBoxSizeMode.AutoSize
                };
    
                Controls.Add(pb1);
                Controls.Add(pb2);
                Controls.Add(pb3);
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      我认为没有更简单的方法。

      事实上,这样做只需要(非常)几行代码,所以除非你找到一个直接为你做这件事的库,否则你不会找到更短的方法。

      【讨论】:

        【解决方案5】:

        想法:

        将图像视为 2D 数组,其中每个 Array 元素作为图像的一个像素。因此,我会说图像差分只不过是 2D 数组差分。

        想法是仅按宽度扫描数组元素并找到像素值存在差异的地方。如果两个 2D 数组的示例 [x, y] 坐标不同,则我们的矩形查找逻辑开始。稍后,矩形将用于修补最后更新的帧缓冲区。

        我们需要扫描矩形的边界以查找差异,如果在矩形边界发现任何差异,则边界将根据所进行的扫描类型在宽度方向或高度方向上增加。

        考虑到我扫描了 2D 数组的宽度方向,我发现一个位置存在两个 2D 数组中不同的坐标,我将创建一个矩形,其起始位置为 [x-1, y- 1],宽度和高度分别为2和2。请注意,宽度和高度是指像素数。

        例如:矩形信息: X = 20 Y = 35 W = 26 H = 23

        即矩形的宽度从坐标 [20, 35] -> [20, 35 + 26 - 1] 开始。也许当你找到代码时,你可能会更好地理解它。

        还有可能你发现的一个更大的矩形里面有更小的矩形,因此我们需要从我们的参考中删除较小的矩形,因为它们对我们没有任何意义,只是它们占据了我宝贵的空间!!

        上述逻辑在 VNC 服务器实现的情况下会很有帮助,在这种情况下,需要用矩形来表示当前拍摄的图像的差异。这些矩形可以通过网络发送到 VNC 客户端,VNC 客户端可以修补它拥有的本地帧缓冲区副本中的矩形,从而将其显示在 VNC 客户端显示板上。

        附注:

        我将附上我实现自己算法的代码。我会要求观众评论任何错误或性能调整。我还要求观众评论任何可以让生活更简单的更好算法。

        代码:

        类矩形:

        public class Rect {
            public int x; // Array Index
            public int y; // Array Index
            public int w; // Number of hops along the Horizontal
            public int h; // Number of hops along the Vertical
        
            @Override
            public boolean equals(Object obj) {
                Rect rect = (Rect) obj;
                if(rect.x == this.x && rect.y == this.y && rect.w == this.w && rect.h == this.h) {
                    return true;
                }
                return false;
            }
        }
        

        类图像差异:

        import java.awt.image.BufferedImage;
        import java.io.File;
        import java.io.IOException;
        import java.util.LinkedList;
        
        import javax.imageio.ImageIO;
        
        public class ImageDifference {
         long start = 0, end = 0;
        
         public LinkedList<Rect> differenceImage(int[][] baseFrame, int[][] screenShot, int xOffset, int yOffset, int width, int height) {
          // Code starts here
          int xRover = 0;
          int yRover = 0;
          int index = 0;
          int limit = 0;
          int rover = 0;
        
          boolean isRectChanged = false;
          boolean shouldSkip = false;
        
          LinkedList<Rect> rectangles = new LinkedList<Rect>();
          Rect rect = null;
        
          start = System.nanoTime();
        
          // xRover - Rovers over the height of 2D Array
          // yRover - Rovers over the width of 2D Array
          int verticalLimit = xOffset + height;
          int horizontalLimit = yOffset + width;
        
          for(xRover = xOffset; xRover < verticalLimit; xRover += 1) {
           for(yRover = yOffset; yRover < horizontalLimit; yRover += 1) {
        
            if(baseFrame[xRover][yRover] != screenShot[xRover][yRover]) {
             // Skip over the already processed Rectangles
             for(Rect itrRect : rectangles) {
              if(( (xRover < itrRect.x + itrRect.h) && (xRover >= itrRect.x) ) && ( (yRover < itrRect.y + itrRect.w) && (yRover >= itrRect.y) )) {
               shouldSkip = true;
               yRover = itrRect.y + itrRect.w - 1;
               break;
              } // End if(( (xRover < itrRect.x + itrRect.h) && (xRover >= itrRect.x) ) && ( (yRover < itrRect.y + itrRect.w) && (yRover >= itrRect.y) ))
             } // End for(Rect itrRect : rectangles)
        
             if(shouldSkip) {
              shouldSkip = false;
              // Need to come out of the if condition as below that is why "continue" has been provided
              // if(( (xRover <= (itrRect.x + itrRect.h)) && (xRover >= itrRect.x) ) && ( (yRover <= (itrRect.y + itrRect.w)) && (yRover >= itrRect.y) ))
              continue;
             } // End if(shouldSkip)
        
             rect = new Rect();
        
             rect.x = ((xRover - 1) < xOffset) ? xOffset : (xRover - 1);
             rect.y = ((yRover - 1) < yOffset) ? yOffset : (yRover - 1);
             rect.w = 2;
             rect.h = 2;
        
             /* Boolean variable used to re-scan the currently found rectangle
              for any change due to previous scanning of boundaries */
             isRectChanged = true;
        
             while(isRectChanged) {
              isRectChanged = false;
              index = 0;
        
        
              /*      I      */
              /* Scanning of left-side boundary of rectangle */
              index = rect.x;
              limit = rect.x + rect.h;
              while(index < limit && rect.y != yOffset) {
               if(baseFrame[index][rect.y] != screenShot[index][rect.y]) {        
                isRectChanged = true;
                rect.y = rect.y - 1;
                rect.w = rect.w + 1;
                index = rect.x;
                continue;
               } // End if(baseFrame[index][rect.y] != screenShot[index][rect.y])
        
               index = index + 1;;
              } // End while(index < limit && rect.y != yOffset)
        
        
              /*      II      */
              /* Scanning of bottom boundary of rectangle */
              index = rect.y;
              limit = rect.y + rect.w;
              while( (index < limit) && (rect.x + rect.h != verticalLimit) ) {
               rover = rect.x + rect.h - 1;
               if(baseFrame[rover][index] != screenShot[rover][index]) {
                isRectChanged = true;
                rect.h = rect.h + 1;        
                index = rect.y;
                continue;
               } // End if(baseFrame[rover][index] != screenShot[rover][index])
        
               index = index + 1;
              } // End while( (index < limit) && (rect.x + rect.h != verticalLimit) )
        
        
              /*      III      */
              /* Scanning of right-side boundary of rectangle */
              index = rect.x;
              limit = rect.x + rect.h;
              while( (index < limit) && (rect.y + rect.w != horizontalLimit) ) {
               rover = rect.y + rect.w - 1;
               if(baseFrame[index][rover] != screenShot[index][rover]) {
                isRectChanged = true;
                rect.w = rect.w + 1;
                index = rect.x;
                continue;
               } // End if(baseFrame[index][rover] != screenShot[index][rover])
        
               index = index + 1;
              } // End while( (index < limit) && (rect.y + rect.w != horizontalLimit) )
        
             } // while(isRectChanged)
        
        
             // Remove those rectangles that come inside "rect" rectangle.
             int idx = 0;
             while(idx < rectangles.size()) {
              Rect r = rectangles.get(idx);
              if( ( (rect.x <= r.x) && (rect.x + rect.h >= r.x + r.h) ) && ( (rect.y <= r.y) && (rect.y + rect.w >= r.y + r.w) ) ) {
               rectangles.remove(r);
              } else {
               idx += 1;
              }  // End if( ( (rect.x <= r.x) && (rect.x + rect.h >= r.x + r.h) ) && ( (rect.y <= r.y) && (rect.y + rect.w >= r.y + r.w) ) ) 
             } // End while(idx < rectangles.size())
        
             // Giving a head start to the yRover when a rectangle is found
             rectangles.addFirst(rect);
        
             yRover = rect.y + rect.w - 1;
             rect = null;
        
            } // End if(baseFrame[xRover][yRover] != screenShot[xRover][yRover])
           } // End for(yRover = yOffset; yRover < horizontalLimit; yRover += 1)
          } // End for(xRover = xOffset; xRover < verticalLimit; xRover += 1)
        
          end = System.nanoTime();    
          return rectangles;
         }
        
         public static void main(String[] args) throws IOException { 
          LinkedList<Rect> rectangles = null;
        
          // Buffering the Base image and Screen Shot Image
          BufferedImage screenShotImg = ImageIO.read(new File("screenShotImg.png"));
          BufferedImage baseImg   = ImageIO.read(new File("baseImg.png"));
        
          int width  = baseImg.getWidth();
          int height = baseImg.getHeight();
          int xOffset = 0;
          int yOffset = 0;
          int length = baseImg.getWidth() * baseImg.getHeight();
        
          // Creating 2 Two Dimensional Arrays for Image Processing
          int[][] baseFrame = new int[height][width];
          int[][] screenShot = new int[height][width];
        
          // Creating 2 Single Dimensional Arrays to retrieve the Pixel Values  
          int[] baseImgPix   = new int[length];
          int[] screenShotImgPix  = new int[length];
        
          // Reading the Pixels from the Buffered Image
          baseImg.getRGB(0, 0, baseImg.getWidth(), baseImg.getHeight(), baseImgPix, 0, baseImg.getWidth());
          screenShotImg.getRGB(0, 0, screenShotImg.getWidth(), screenShotImg.getHeight(), screenShotImgPix, 0, screenShotImg.getWidth());
        
          // Transporting the Single Dimensional Arrays to Two Dimensional Array
          long start = System.nanoTime();
        
          for(int row = 0; row < height; row++) {
           System.arraycopy(baseImgPix, (row * width), baseFrame[row], 0, width);
           System.arraycopy(screenShotImgPix, (row * width), screenShot[row], 0, width);
          }
        
          long end = System.nanoTime();
          System.out.println("Array Copy : " + ((double)(end - start) / 1000000));
        
          // Finding Differences between the Base Image and ScreenShot Image
          ImageDifference imDiff = new ImageDifference();
          rectangles = imDiff.differenceImage(baseFrame, screenShot, xOffset, yOffset, width, height);
        
          // Displaying the rectangles found
          int index = 0;
          for(Rect rect : rectangles) {
           System.out.println("\nRect info : " + (++index));
           System.out.println("X : " + rect.x);
           System.out.println("Y : " + rect.y);
           System.out.println("W : " + rect.w);
           System.out.println("H : " + rect.h);
        
           // Creating Bounding Box
           for(int i = rect.y; i < rect.y + rect.w; i++) {    
            screenShotImgPix[ ( rect.x               * width) + i ] = 0xFFFF0000;
            screenShotImgPix[ ((rect.x + rect.h - 1) * width) + i ] = 0xFFFF0000;
           }
        
           for(int j = rect.x; j < rect.x + rect.h; j++) {
            screenShotImgPix[ (j * width) + rect.y                ] = 0xFFFF0000;
            screenShotImgPix[ (j * width) + (rect.y + rect.w - 1) ] = 0xFFFF0000;
           }
        
          }
        
          // Creating the Resultant Image
          screenShotImg.setRGB(0, 0, width, height, screenShotImgPix, 0, width);
          ImageIO.write(screenShotImg, "PNG", new File("result.png"));
        
          double d = ((double)(imDiff.end - imDiff.start) / 1000000);
          System.out.println("\nTotal Time : " + d + " ms" + "  Array Copy : " + ((double)(end - start) / 1000000) + " ms");
        
         }
        }
        

        说明:

        会有一个名为

        的函数
        public LinkedList<Rect> differenceImage(int[][] baseFrame, int[][] screenShot, int width, int height)
        

        它负责查找图像中的差异并返回对象的链表。对象只不过是矩形。

        有一个主要的功能来测试算法。

        在 main 函数的代码中传递了 2 个示例图像,它们只不过是“baseFrame”和“screenShot”,从而创建了名为“result”的结果图像。

        我不具备发布非常有趣的结果图像所需的声誉。

        有一个博客可以提供输出 Image Difference

        【讨论】:

          【解决方案6】:

          我认为没有什么比从每一侧依次详尽搜索该方向上的第一个不同点更好的了。除非,也就是说,您知道某个事实以某种方式限制了差异点的集合。

          【讨论】:

            【解决方案7】:

            如果你知道如何使用Lockbit,那么这里有一个简单的方法:)

                    Bitmap originalBMP = new Bitmap(pictureBox1.ImageLocation);
                    Bitmap changedBMP = new Bitmap(pictureBox2.ImageLocation);
            
                    int width = Math.Min(originalBMP.Width, changedBMP.Width),
                        height = Math.Min(originalBMP.Height, changedBMP.Height),
            
                        xMin = int.MaxValue,
                        xMax = int.MinValue,
            
                        yMin = int.MaxValue,
                        yMax = int.MinValue;
            
                    var originalLock = originalBMP.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, originalBMP.PixelFormat);
                    var changedLock = changedBMP.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, changedBMP.PixelFormat);
            
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            //generate the address of the colour pixel
                            int pixelIdxOrg = y * originalLock.Stride + (x * 4);
                            int pixelIdxCh = y * changedLock.Stride + (x * 4);
            
            
                            if (( Marshal.ReadByte(originalLock.Scan0, pixelIdxOrg + 2)!= Marshal.ReadByte(changedLock.Scan0, pixelIdxCh + 2))
                                || (Marshal.ReadByte(originalLock.Scan0, pixelIdxOrg + 1) != Marshal.ReadByte(changedLock.Scan0, pixelIdxCh + 1))
                                || (Marshal.ReadByte(originalLock.Scan0, pixelIdxOrg) != Marshal.ReadByte(changedLock.Scan0, pixelIdxCh))
                                )
                            {
                                xMin = Math.Min(xMin, x);
                                xMax = Math.Max(xMax, x);
            
                                yMin = Math.Min(yMin, y);
                                yMax = Math.Max(yMax, y);
                            }
                        }
                    }
            
                    originalBMP.UnlockBits(originalLock);
                    changedBMP.UnlockBits(changedLock);
            
                    var result = changedBMP.Clone(new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin), changedBMP.PixelFormat);
            
                    pictureBox3.Image = result;
            

            声明看起来您的 2 张图片包含的差异比我们用肉眼看到的要多,因此结果会比您预期的要宽,但您可以添加一个容差,即使其余的不是 100% 相同,它也会适合

            为了加快速度,你也许可以给我们Parallel.For,但只为外循环做这件事

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-07-05
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多