如何找到两个图像之间的差异矩形

时间:2010-04-24 14:06:45

标签: c# .net algorithm image comparison

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

示例:

first image http://i44.tinypic.com/2cg0u2h.png

second image http://i43.tinypic.com/14l0y13.png

difference http://i40.tinypic.com/5agshd.png

7 个答案:

答案 0 :(得分:4)

一种天真的方法是从原点开始,逐行逐行工作。比较每个像素,记下最上面,最左边,最右边和最下面的像素,从中可以计算出矩形。将存在这种单程通过方法更快(即,存在非常小的不同区域的情况)的情况

答案 1 :(得分:2)

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

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

答案 2 :(得分:1)

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

用于此类比特打击的公共库是OpenCV,它利用可用的专用CPU指令来实现这一点。有几个.NET包装器,Emgu is one of them

答案 3 :(得分:0)

我认为除了在每个方面依次详尽地搜索该方向的第一个不同点之外,我认为没有比这更好的了。除非,也就是说,你知道一个事实,即在某种程度上限制了差异点的集合。

答案 4 :(得分:0)

如果你知道如何使用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但是只能为外循环

答案 5 :(得分:0)

<强>观

将图像视为2D数组,每个Array元素作为图像的像素。因此,我认为图像差分只不过是2D阵列差分。

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

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

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

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

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

也有可能在你找到的更大的矩形中有更小的矩形,因此我们需要从我们的参考中删除较小的矩形,因为它们对我们没有任何意义,只是它们占据了我宝贵的空间!!

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

<强> P.S:

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

<强>代码:

Class Rect:

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 :(得分:0)

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

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

enter image description here

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

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

enter image description here

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);
        }
    }
}