Idea:
Consider the image as a 2D array with each Array element as the image pixel. Therefore, I would say that Image Differencing is nothing more than a two-dimensional difference in an array.
The idea is to simply scan the elements of the array in width and find the place where there is a difference in pixel values. If the examples of [x, y] coordinates of both 2D arrays are different, then the rectangle search logic begins. Rectangles will later be used to fix the last updated frame buffer.
We need to scan the borders of the rectangles for differences, and if any difference is found on the border of the rectangle, then the border will be increased in width or height, depending on the type of scan.
Consider that I scanned the width of a 2D array, and I found a place where there is a coordinate that is different in both 2D arrays, I will create a rectangle with the original position as [x-1, y-1], and the width and height are 2 and 2 respectively. Please note that width and height are related to the number of pixels.
e.g. Rect Info: X = 20 Y = 35 W = 26 H = 23
ie the width of the rectangle begins with the coordinate [20, 35] β [20, 35 + 26 - 1]. Perhaps when you find the code, you can better understand it.
There is also the possibility that there are smaller rectangles in the large rectangle, so we need to remove the smaller rectangles from our link because they mean nothing to us, except that they occupy my precious space !!
The above logic would be useful in the case of a VNC server implementation, where there is a need for rectangles that indicate the differences in the image that is currently running. These rectangles can be sent on the network to the VNC client, which can correct the rectangles in the local copy of the frame buffer it possesses by displaying it on the VNC client card.
PS :.
I will attach the code in which I implemented my own algorithm. I would ask viewers to comment on any bugs or performance tuning. I would also ask viewers to comment on any best algorithm that makes life easier.
The code:
Rect Class:
public class Rect { public int x;
Differences in class image:
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 <= 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<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"); } }
Description:
There will be a function named
public LinkedList<Rect> differenceImage(int[][] baseFrame, int[][] screenShot, int width, int height)
which searches for differences in images and returns a linked list of objects. Objects are just rectangles.
There is a basic function that performs the task of testing an algorithm.
In the main function, two image samples are transferred, they are nothing more than "baseFrame" and "screenShot", thereby creating a resulting image called "result".
I do not have the desired reputation to publish the resulting image, which will be very interesting.
There is a blog that will provide image difference output