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

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

例:

第一张图片http://sofzh.miximages.com/c%23/2cg0u2h.png

第二张图片http://sofzh.miximages.com/c%23/14l0y13.png

差异http://sofzh.miximages.com/c%23/5agshd.png

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

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

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

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

用于此类比特攻击的公共库是OpenCV,它利用可用的专用CPU指令来实现这一点。 有几个.NET包装器可用, Emgu就是其中之一 。

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

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

理念:

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

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

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

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

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

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

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

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

PS:

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

码:

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

class级形象差异:

 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 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 rectangles = new LinkedList(); 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 <= rx) && (rect.x + rect.h >= rx + rh) ) && ( (rect.y <= ry) && (rect.y + rect.w >= ry + rw) ) ) { rectangles.remove(r); } else { idx += 1; } // End if( ( (rect.x <= rx) && (rect.x + rect.h >= rx + rh) ) && ( (rect.y <= ry) && (rect.y + rect.w >= ry + rw) ) ) } // 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 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 differenceImage(int[][] baseFrame, int[][] screenShot, int width, int height) 

它可以找到图像中的差异并返回对象的链接列表。 对象只是矩形。

有主要function来完成测试算法的工作。

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

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

有一个博客可以提供输出图像差异