I got a large set of infrared images of seeds, their sizes vary slightly.
And I would like to find them (in thefastest possible way).
Below i show zoomed in details of the images i process.
After a first noise removal and blob filter this is what i have :
The bright white is just direct reflection of the IR lamp, white pixels never combine (stretch out) over multiple seeds.
To make it more clear i placed a letter on some individual seeds.
The problems i have:
A is a single seed (dirt on the seed) generates a slight dark line.
B the nearby X close to it is at its darkest intersection its still brighter as some other seeds (cannt change brightnes or remove if gray value is lower then a certain value.
C those are 3 seeds close to each other.
The smallest visible seeds above should not becomme even smaller.
I'm not making use of mathlab or openCV, as i work directly with locked image and memory data. i can acces pixel data by array or simple getpixel / putpixel commands. I wrote my own graphics library which is fast enough for live camera data, processing speed currently at around 13ms at around 25ms i enter stream processing lag
I wonder how to separate those 'cloudy' blobs better.
I'm thinking to find local maxima over a certain pixels range..but that should see A as one seed, while on B find that B and X are not connected.
So I'm not sure here, how such a local peek function or another function should like like. Although i code in C# i looked at other c++ functions as well like dilate etc but thats not it. I also wrote a function to check for slope degree (like if it was a mountain height image) but that couldnt devide areas B and C.
Ok well i made different slope detection code, now i dont look for some degree but just the tilting point over a small range, it works nice on X axis.. but essentially i think it should work on both X and Y
here's the new result :
It can resolve issue A and B !!!
However it wouldnt differentiate between seeds who are aligned in a vertical row, and and it causes small white noise (non connected lines). at places where is there is nearly nothing to detect. I'm not yet sure on how to do the same (combined) over Y axis to get the tops then erase stuff from a certain distance of the top.. (to seperate).
Using this code just showing the pixel operations of it.
for (int y = raw.Height;y>5; y--)
{slopeUP = false;
int[] peek = new int[raw.Width];
for (int x = raw.Width; x> 7; x--)
{
int a = raw.GetPixelBleu(x, y);
int b = raw.GetPixelBleu(x - 1, y);
int c = raw.GetPixelBleu(x - 2, y);
int d = raw.GetPixelBleu(x - 11, y);
int f = raw.GetPixelBleu(x - 12, y);
if ((f + d) > (a + b))slopeUP = true;
if ((f + d) < (a + b))
{
if (slopeUP)
{
slopeUP = false;
peek[x - 6] = 10;
//raw.SetPixel(x, y, Color.GreenYellow);
}
else peek[x - 6] = 0;
}
}
for (int x = raw.Width; x > 7; x--)
{ if (peek[x-1] > 5) raw.SetPixel(x, y, Color.Lavender); }
}
In this SO answer to a similar question I applied persistent homology to find peaks in an image. I took your image, scaled it down to 50%, applied an Guassian blur with radius 20 (in Gimp), and applied the methods described in the other article (click to enlarge):
I only show peaks with persistence (see the other SO answer) of at least 20. The persistence diagram is shown here:
The 20-th peak would be the little peak on the top-left corner with a persistence of about 9. By applying a stronger Gaussian filter the image gets more diffuse and the peaks will be come more prominent.
Python code can be found here.
So, as far as speed, I am only going off of the image you posted here... on which everything runs blazing fast because it is tiny. Note that I padded the image after binarizing and never un-padded, so you will want to either un-pad or shift your results accordingly. You may not even want to pad, but it allows detection of cut off seeds.
Overview of pipeline: removeSaturation>>gaussian blur>>binarize>>padd>>distanceTransform>>peaks>>clustering
That being said here is my code and results:
void drawText(Mat & image);
void onMouse(int event, int x, int y, int, void*);
Mat bruteForceLocalMax(Mat srcImage, int searchRad);
void zoomPixelImage(Mat sourceImage, int multFactor, string name, bool mouseCallback);
Mat mergeLocalPeaks(Mat srcImage, int mergeRadius);
Mat image;
bool debugDisplays = false;
int main()
{
cout << "Built with OpenCV " << CV_VERSION << endl;
TimeStamp precisionClock = TimeStamp();
image = imread("../Raw_Images/Seeds1.png",0);
if (image.empty()) { cout << "failed to load image"<<endl; }
else
{
zoomPixelImage(image, 5, "raw data", false);
precisionClock.labeledlapStamp("image read", true);
//find max value in image that is not oversaturated
int maxVal = 0;
for (int x = 0; x < image.rows; x++)
{
for (int y = 0; y < image.cols; y++)
{
int val = image.at<uchar>(x, y);
if (val >maxVal && val !=255)
{
maxVal = val;
}
}
}
//get rid of oversaturation regions (as they throw off processing)
image.setTo(maxVal, image == 255);
if (debugDisplays)
{zoomPixelImage(image, 5, "unsaturated data", false);}
precisionClock.labeledlapStamp("Unsaturate Data", true);
Mat gaussianBlurred = Mat();
GaussianBlur(image, gaussianBlurred, Size(9, 9), 10, 0);
if (debugDisplays)
{zoomPixelImage(gaussianBlurred, 5, "blurred data", false);}
precisionClock.labeledlapStamp("Gaussian", true);
Mat binarized = Mat();
threshold(gaussianBlurred, binarized, 50, 255, THRESH_BINARY);
if (debugDisplays)
{zoomPixelImage(binarized, 5, "binarized data", false);}
precisionClock.labeledlapStamp("binarized", true);
//pad edges (may or may not be neccesary depending on setup)
Mat paddedImage = Mat();
copyMakeBorder(binarized, paddedImage, 1, 1, 1, 1, BORDER_CONSTANT, 0);
if (debugDisplays)
{zoomPixelImage(paddedImage, 5, "padded data", false);}
precisionClock.labeledlapStamp("add padding", true);
Mat distTrans = Mat();
distanceTransform(paddedImage, distTrans, CV_DIST_L1,3,CV_8U);
if (debugDisplays)
{zoomPixelImage(distTrans, 5, "distanceTransform", true);}
precisionClock.labeledlapStamp("distTransform", true);
Mat peaks = Mat();
peaks = bruteForceLocalMax(distTrans,10);
if (debugDisplays)
{zoomPixelImage(peaks, 5, "peaks", false);}
precisionClock.labeledlapStamp("peaks", true);
//attempt to cluster any colocated peaks and find the best clustering count
Mat mergedPeaks = Mat();
mergedPeaks = mergeLocalPeaks(peaks, 5);
if (debugDisplays)
{zoomPixelImage(mergedPeaks, 5, "peaks final", false);}
precisionClock.labeledlapStamp("final peaks", true);
precisionClock.fullStamp(false);
waitKey(0);
}
}
void drawText(Mat & image)
{
putText(image, "Hello OpenCV",
Point(20, 50),
FONT_HERSHEY_COMPLEX, 1, // font face and scale
Scalar(255, 255, 255), // white
1, LINE_AA); // line thickness and type
}
void onMouse(int event, int x, int y, int, void*)
{
if (event != CV_EVENT_LBUTTONDOWN)
return;
Point pt = Point(x, y);
std::cout << "x=" << pt.x << "\t y=" << pt.y << "\t value=" << int(image.at<uchar>(y,x)) << "\n";
}
void zoomPixelImage(Mat sourceImage, int multFactor, string name, bool normalized)
{
Mat zoomed;// = Mat::zeros(sourceImage.rows*multFactor, sourceImage.cols*multFactor, CV_8U);
resize(sourceImage, zoomed, Size(sourceImage.cols*multFactor, sourceImage.rows*multFactor), sourceImage.cols*multFactor, sourceImage.rows*multFactor, INTER_NEAREST);
if (normalized) { normalize(zoomed, zoomed, 0, 255, NORM_MINMAX); }
namedWindow(name);
imshow(name, zoomed);
}
Mat bruteForceLocalMax(Mat srcImage, int searchRad)
{
Mat outputArray = Mat::zeros(srcImage.rows, srcImage.cols, CV_8U);
//global search top
for (int x = 0; x < srcImage.rows - 1; x++)
{
for (int y = 0; y < srcImage.cols - 1; y++)
{
bool peak = true;
float centerVal = srcImage.at<uchar>(x, y);
if (centerVal == 0) { continue; }
//local search top
for (int a = -searchRad; a <= searchRad; a++)
{
for (int b = -searchRad; b <= searchRad; b++)
{
if (x + a<0 || x + a>srcImage.rows - 1 || y + b < 0 || y + b>srcImage.cols - 1) { continue; }
if (srcImage.at<uchar>(x + a, y + b) > centerVal)
{
peak = false;
}
if (peak == false) { break; }
}
if (peak == false) { break; }
}
if (peak)
{
outputArray.at<uchar>(x, y) = 255;
}
}
}
return outputArray;
}
Mat mergeLocalPeaks(Mat srcImage, int mergeRadius)
{
Mat outputArray = Mat::zeros(srcImage.rows, srcImage.cols, CV_8U);
//global search top
for (int x = 0; x < srcImage.rows - 1; x++)
{
for (int y = 0; y < srcImage.cols - 1; y++)
{
float centerVal = srcImage.at<uchar>(x, y);
if (centerVal == 0) { continue; }
int aveX = x;
int aveY = y;
int xCenter = -1;
int yCenter = -1;
while (aveX != xCenter || aveY != yCenter)
{
xCenter = aveX;
yCenter = aveY;
aveX = 0;
aveY = 0;
int peakCount = 0;
//local search top
for (int a = -mergeRadius; a <= mergeRadius; a++)
{
for (int b = -mergeRadius; b <= mergeRadius; b++)
{
if (xCenter + a<0 || xCenter + a>srcImage.rows - 1 || yCenter + b < 0 || yCenter + b>srcImage.cols - 1) { continue; }
if (srcImage.at<uchar>(xCenter + a, yCenter + b) > 0)
{
aveX += (xCenter + a);
aveY += (yCenter + b);
peakCount += 1;
}
}
}
double dCentX = ((double)aveX / (double)peakCount);
double dCentY = ((double)aveY / (double)peakCount);
aveX = floor(dCentX);
aveY = floor(dCentY);
}
outputArray.at<uchar>(xCenter, yCenter) = 255;
}
}
return outputArray;
}
speed:
debug images:
results:
Hope this helps! Cheers!
Related
Currently i'm trying to produce a simple 2D map generation program, and it is pretty much finished apart from one key thing; The movement of the generated islands. The way the program functions it keeps all the islands in the middle of the map separated by colour like in some like disco ball of puke thing, but my main problem is trying to move the islands into new locations.
The program should randomly place the islands in new places based on colour, but i am having a considerable amount of difficulty doing this, as all solutions i have attempted have either fell on their face in a tsunami of 'index out of bounds of the array' errors or have worked, but taken literal hours to move a single island.
TLDR; Do any algorithms exist that would potentially allow me to move shapes made of pixels to random locations while keeping their existing shapes? mine suck.
Edit: I will try and rewrite this to be easier to read later since i'm in a rush, but in essence it reads all the pixels from the circle using .getpixel and stores them in an array based on their colour, it then generates a random location and runs the same code again, only this time it will accept a colour as an argument and will place the colour at the pixel relative to the centre of the circle if it finds a colour that is the same as the colour it is currently accepting.
In theory this should go through every colour and generate a new position for each one that maintains the shape of the island upon generation, but in practice it just takes forever.
//Thoughts - use the circle generator as a radar to find all the seperate colors, then for each color randomly generate an x and a y. then use the circle generator but only apply the colors that are selected
if (tempi >= 716 || tempib > 0)
{
if(tempib <= 0)
{
tempi = 0;
tempib = 1;
randxb = Rander.Next(10, xlen - 10);
randyb = Rander.Next(10, ylen - 10);
}
tempi += 1;
day += 1;
if(day >= 31)
{
month += 1;
day = 1;
}
if(month >= 13)
{
year += 1;
month = 1;
}
AD = "PF";
era = "Prehistoric era";
age = "Islandic Age";
Point temppb = new Point(randxb, randyb);
if (colours[tempib] == Color.DarkBlue || colours[tempib] == Color.FromArgb(0, 0, 0))
{
tempib += 1;
}
else
{
Radar(0, temppb, "write", colours[tempib]);
}
tempi = 0;
tempib += 1;
randxb = Rander.Next(10, xlen - 10);
randyb = Rander.Next(10, ylen - 10);
if (tempib >= islandnuma)
{
age = "Neanderthalic Age";
}
}
else
{
year += Rander.Next(1, 3);
day = 1;
AD = "PF";
era = "Prehistoric era";
Point tempp = new Point(xlen / 2 - 150, ylen / 2 - 150);
tempi += 1;
Radar(tempi, tempp, "scan", Color.White);
if(tempi >= 716)
{
clearmap();
}
}
}
This is the terrible algorithm it calls
Color[,] scanresults = new Color[717, 4499]; //shell, place in shell
private void Radar(int circle, Point pos, string mode, Color col) //Fuck this doesnt work i need to change it
{
using (var g = Graphics.FromImage(pictureBox1.Image))
{
if (mode == "scan")
{
int mj = 0;
if (circle <= 716)
{
for (double i = 0.0; i < 360.0; i += 0.1)
{
mj += 1;
int radius = circle / 2; //max size = 716
double angle = i * System.Math.PI / 180;
int x = pos.X - (int)(radius * System.Math.Cos(angle));
int y = pos.Y - (int)(radius * System.Math.Sin(angle));
Color m = Map.GetPixel(x, y);
scanresults[circle, mj] = Map.GetPixel(x, y);
}
}
else
{
return;
}
}
else
{
if(mode == "write")
{
for(int c2 = 0; c2 <= 716; c2++)
{
int bmj = 0;
for (double i = 0.0; i < 360.0; i += 0.1)
{
try
{
if (mode == "write")
{
bmj += 1;
int radius = (716 - c2) / 2; //max size = 716
double angle = i * System.Math.PI / 180;
int x = pos.X - (int)(radius * System.Math.Cos(angle));
int y = pos.Y - (int)(radius * System.Math.Sin(angle));
if (scanresults[c2, bmj] == col)
{
Map.SetPixel(x, y, col);
}
}
}
catch (Exception em)
{
Console.Write("error: " + em);
}
//Color m = Map.GetPixel(x, y);
//scanresults[circle, mj] = Map.GetPixel(x, y);
}
}
}
}
//Dont hate me im defensive about my terrible coding style
}
}
I've been experimenting with the image bicubic resampling algorithm present in the AForge framework with the idea of introducing something similar into my image processing solution. See the original algorithm here and interpolation kernel here
Unfortunately I've hit a wall. It looks to me like somehow I am calculating the sample destination position incorrectly, probably due to the algorithm being designed for Format24bppRgb images where as I am using a Format32bppPArgb format.
Here's my code:
public Bitmap Resize(Bitmap source, int width, int height)
{
int sourceWidth = source.Width;
int sourceHeight = source.Height;
Bitmap destination = new Bitmap(width, height, PixelFormat.Format32bppPArgb);
destination.SetResolution(source.HorizontalResolution, source.VerticalResolution);
using (FastBitmap sourceBitmap = new FastBitmap(source))
{
using (FastBitmap destinationBitmap = new FastBitmap(destination))
{
double heightFactor = sourceWidth / (double)width;
double widthFactor = sourceHeight / (double)height;
// Coordinates of source points
double ox, oy, dx, dy, k1, k2;
int ox1, oy1, ox2, oy2;
// Width and height decreased by 1
int maxHeight = height - 1;
int maxWidth = width - 1;
for (int y = 0; y < height; y++)
{
// Y coordinates
oy = (y * widthFactor) - 0.5;
oy1 = (int)oy;
dy = oy - oy1;
for (int x = 0; x < width; x++)
{
// X coordinates
ox = (x * heightFactor) - 0.5f;
ox1 = (int)ox;
dx = ox - ox1;
// Destination color components
double r = 0;
double g = 0;
double b = 0;
double a = 0;
for (int n = -1; n < 3; n++)
{
// Get Y cooefficient
k1 = Interpolation.BiCubicKernel(dy - n);
oy2 = oy1 + n;
if (oy2 < 0)
{
oy2 = 0;
}
if (oy2 > maxHeight)
{
oy2 = maxHeight;
}
for (int m = -1; m < 3; m++)
{
// Get X cooefficient
k2 = k1 * Interpolation.BiCubicKernel(m - dx);
ox2 = ox1 + m;
if (ox2 < 0)
{
ox2 = 0;
}
if (ox2 > maxWidth)
{
ox2 = maxWidth;
}
Color color = sourceBitmap.GetPixel(ox2, oy2);
r += k2 * color.R;
g += k2 * color.G;
b += k2 * color.B;
a += k2 * color.A;
}
}
destinationBitmap.SetPixel(
x,
y,
Color.FromArgb(a.ToByte(), r.ToByte(), g.ToByte(), b.ToByte()));
}
}
}
}
source.Dispose();
return destination;
}
And the kernel which should represent the given equation on Wikipedia
public static double BiCubicKernel(double x)
{
if (x < 0)
{
x = -x;
}
double bicubicCoef = 0;
if (x <= 1)
{
bicubicCoef = (1.5 * x - 2.5) * x * x + 1;
}
else if (x < 2)
{
bicubicCoef = ((-0.5 * x + 2.5) * x - 4) * x + 2;
}
return bicubicCoef;
}
Here's the original image at 500px x 667px.
And the image resized to 400px x 543px.
Visually it appears that the image is over reduced and then the same pixels are repeatedly applied once we hit a particular point.
Can anyone give me some pointers here to solve this?
Note FastBitmap is a wrapper for Bitmap that uses LockBits to manipulate pixels in memory. It works well with everything else I apply it to.
Edit
As per request here's the methods involved in ToByte
public static byte ToByte(this double value)
{
return Convert.ToByte(ImageMaths.Clamp(value, 0, 255));
}
public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>
{
if (value.CompareTo(min) < 0)
{
return min;
}
if (value.CompareTo(max) > 0)
{
return max;
}
return value;
}
You are limiting your ox2 and oy2 to destination image dimensions, instead of source dimensions.
Change this:
// Width and height decreased by 1
int maxHeight = height - 1;
int maxWidth = width - 1;
to this:
// Width and height decreased by 1
int maxHeight = sourceHeight - 1;
int maxWidth = sourceWidth - 1;
Well, I've met a very strange thing, which might be or might be not a souce of the problem.
I've started to try implementing convolution matrix by myself and encountered strange behaviour. I was testing code on a small image 4x4 pixels. The code is following:
var source = Bitmap.FromFile(#"C:\Users\Public\Pictures\Sample Pictures\Безымянный.png");
using (FastBitmap sourceBitmap = new FastBitmap(source))
{
for (int TY = 0; TY < 4; TY++)
{
for (int TX = 0; TX < 4; TX++)
{
Color color = sourceBitmap.GetPixel(TX, TY);
Console.Write(color.B.ToString().PadLeft(5));
}
Console.WriteLine();
}
}
Althought I'm printing out only blue channel value, it's still clearly incorrect.
On the other hand, your solution partitially works, what makes the thing I've found kind of irrelevant. One more guess I have: what is your system's DPI?
From what I have found helpfull, here are some links:
C++ implementation of bicubic interpolation on
matrix
C# implemetation of bicubic interpolation, lacking the part about rescaling
Thread on gamedev.net which has almost working solution
That's my answer so far, but I will try further.
I'm trying to scan 2 images (32bppArgb format), identify when there is a difference and store the difference block's bounds in a list of rectangles.
Suppose these are the images:
second:
I want to get the different rectangle bounds (the opened directory window in our case).
This is what I've done:
private unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
List<Rectangle> rec = new List<Rectangle>();
bmData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, 1920, 1080), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
bmData2 = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, 1920, 1080), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
IntPtr scan0 = bmData.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride = bmData.Stride;
int stride2 = bmData2.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
int minX = int.MaxValue;;
int minY = int.MaxValue;
int maxX = 0;
bool found = false;
for (int y = 0; y < nHeight; y++)
{
byte* p = (byte*)scan0.ToPointer();
p += y * stride;
byte* p2 = (byte*)scan02.ToPointer();
p2 += y * stride2;
for (int x = 0; x < nWidth; x++)
{
if (p[0] != p2[0] || p[1] != p2[1] || p[2] != p2[2] || p[3] != p2[3]) //found differences-began to store positions.
{
found = true;
if (x < minX)
minX = x;
if (x > maxX)
maxX = x;
if (y < minY)
minY = y;
}
else
{
if (found)
{
int height = getBlockHeight(stride, scan0, maxX, minY, scan02, stride2);
found = false;
Rectangle temp = new Rectangle(minX, minY, maxX - minX, height);
rec.Add(temp);
//x += minX;
y += height;
minX = int.MaxValue;
minY = int.MaxValue;
maxX = 0;
}
}
p += 4;
p2 += 4;
}
}
return rec;
}
public unsafe int getBlockHeight(int stride, IntPtr scan, int x, int y1, IntPtr scan02, int stride2) //a function to get an existing block height.
{
int height = 0;;
for (int y = y1; y < 1080; y++) //only for example- in our case its 1080 height.
{
byte* p = (byte*)scan.ToPointer();
p += (y * stride) + (x * 4); //set the pointer to a specific potential point.
byte* p2 = (byte*)scan02.ToPointer();
p2 += (y * stride2) + (x * 4); //set the pointer to a specific potential point.
if (p[0] != p2[0] || p[1] != p2[1] || p[2] != p2[2] || p[3] != p2[3]) //still change on the height in the increasing **y** of the block.
height++;
}
return height;
}
This is actually how I call the method:
Bitmap a = Image.FromFile(#"C:\Users\itapi\Desktop\1.png") as Bitmap;//generates a 32bppRgba bitmap;
Bitmap b = Image.FromFile(#"C:\Users\itapi\Desktop\2.png") as Bitmap;//
List<Rectangle> l1 = CodeImage(a, b);
int i = 0;
foreach (Rectangle rec in l1)
{
i++;
Bitmap tmp = b.Clone(rec, a.PixelFormat);
tmp.Save(i.ToString() + ".png");
}
But I'm not getting the exact rectangle.. I'm getting only half of that and sometimes even worse. I think something in the code's logic is wrong.
Code for #nico
private unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
List<Rectangle> rec = new List<Rectangle>();
var bmData1 = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
var bmData2 = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
int bytesPerPixel = 3;
IntPtr scan01 = bmData1.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride1 = bmData1.Stride;
int stride2 = bmData2.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
bool[] visited = new bool[nWidth * nHeight];
byte* base1 = (byte*)scan01.ToPointer();
byte* base2 = (byte*)scan02.ToPointer();
for (int y = 0; y < nHeight; y += 5)
{
byte* p1 = base1;
byte* p2 = base2;
for (int x = 0; x < nWidth; x += 5)
{
if (!ArePixelsEqual(p1, p2, bytesPerPixel) && !(visited[x + nWidth * y]))
{
// fill the different area
int minX = x;
int maxX = x;
int minY = y;
int maxY = y;
var pt = new Point(x, y);
Stack<Point> toBeProcessed = new Stack<Point> ();
visited[x + nWidth * y] = true;
toBeProcessed.Push(pt);
while (toBeProcessed.Count > 0)
{
var process = toBeProcessed.Pop();
var ptr1 = (byte*)scan01.ToPointer() + process.Y * stride1 + process.X * bytesPerPixel;
var ptr2 = (byte*) scan02.ToPointer() + process.Y * stride2 + process.X * bytesPerPixel;
//Check pixel equality
if (ArePixelsEqual(ptr1, ptr2, bytesPerPixel))
continue;
//This pixel is different
//Update the rectangle
if (process.X < minX) minX = process.X;
if (process.X > maxX) maxX = process.X;
if (process.Y < minY) minY = process.Y;
if (process.Y > maxY) maxY = process.Y;
Point n;
int idx;
//Put neighbors in stack
if (process.X - 1 >= 0)
{
n = new Point(process.X - 1, process.Y);
idx = n.X + nWidth * n.Y;
if (!visited[idx])
{
visited[idx] = true;
toBeProcessed.Push(n);
}
}
if (process.X + 1 < nWidth)
{
n = new Point(process.X + 1, process.Y);
idx = n.X + nWidth * n.Y;
if (!visited[idx])
{
visited[idx] = true;
toBeProcessed.Push(n);
}
}
if (process.Y - 1 >= 0)
{
n = new Point(process.X, process.Y - 1);
idx = n.X + nWidth * n.Y;
if (!visited[idx])
{
visited[idx] = true;
toBeProcessed.Push(n);
}
}
if (process.Y + 1 < nHeight)
{
n = new Point(process.X, process.Y + 1);
idx = n.X + nWidth * n.Y;
if (!visited[idx])
{
visited[idx] = true;
toBeProcessed.Push(n);
}
}
}
if (((maxX - minX + 1) > 5) & ((maxY - minY + 1) > 5))
rec.Add(new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1));
}
p1 += 5 * bytesPerPixel;
p2 += 5 * bytesPerPixel;
}
base1 += 5 * stride1;
base2 += 5 * stride2;
}
bmp.UnlockBits(bmData1);
bmp2.UnlockBits(bmData2);
return rec;
}
I see a couple of problems with your code. If I understand it correctly, you
find a pixel that's different between the two images.
then you continue to scan from there to the right, until you find a position where both images are identical again.
then you scan from the last "different" pixel to the bottom, until you find a position where both images are identical again.
then you store that rectangle and start at the next line below it
Am I right so far?
Two obvious things can go wrong here:
If two rectangles have overlapping y-ranges, you're in trouble: You'll find the first rectangle fine, then skip to the bottom Y-coordinate, ignoring all the pixels left or right of the rectangle you just found.
Even if there is only one rectangle, you assume that every pixel on the rectangle's border is different, and all the other pixels are identical. If that assumption isn't valid, you'll stop searching too early, and only find parts of rectangles.
If your images come from a scanner or digital camera, or if they contain lossy compression (jpeg) artifacts, the second assumption will almost certainly be wrong. To illustrate this, here's what I get when I mark every identical pixel the two jpg images you linked black, and every different pixel white:
What you see is not a rectangle. Instead, a lot of pixels around the rectangles you're looking for are different:
That's because of jpeg compression artifacts. But even if you used lossless source images, pixels at the borders might not form perfect rectangles, because of antialiasing or because the background just happens to have a similar color in that region.
You could try to improve your algorithm, but if you look at that border, you will find all kinds of ugly counterexamples to any geometric assumptions you'll make.
It would probably be better to implement this "the right way". Meaning:
Either implement a flood fill algorithm that erases different pixels (e.g. by setting them to identical or by storing a flag in a separate mask), then recursively checks if the 4 neighbor pixels.
Or implement a connected component labeling algorithm, that marks each different pixel with a temporary integer label, using clever data structures to keep track which temporary labels are connected. If you're only interested in a bounding box, you don't even have to merge the temporary labels, just merge the bounding boxes of adjacent labeled areas.
Connected component labeling is in general a bit faster, but is a bit trickier to get right than flood fill.
One last advice: I would rethink your "no 3rd party libraries" policy if I were you. Even if your final product will contain no 3rd party libraries, development might by a lot faster if you used well-documented, well-tested, useful building blocks from a library, then replaced them one by one with your own code. (And who knows, you might even find an open source library with a suitable license that's so much faster than your own code that you'll stick with it in the end...)
ADD: In case you want to rethink your "no libraries" position: Here's a quick and simple implementation using AForge (which has a more permissive library than emgucv):
private static void ProcessImages()
{
(* load images *)
var img1 = AForge.Imaging.Image.FromFile(#"compare1.jpg");
var img2 = AForge.Imaging.Image.FromFile(#"compare2.jpg");
(* calculate absolute difference *)
var difference = new AForge.Imaging.Filters.ThresholdedDifference(15)
{OverlayImage = img1}
.Apply(img2);
(* create and initialize the blob counter *)
var bc = new AForge.Imaging.BlobCounter();
bc.FilterBlobs = true;
bc.MinWidth = 5;
bc.MinHeight = 5;
(* find blobs *)
bc.ProcessImage(difference);
(* draw result *)
BitmapData data = img2.LockBits(
new Rectangle(0, 0, img2.Width, img2.Height),
ImageLockMode.ReadWrite, img2.PixelFormat);
foreach (var rc in bc.GetObjectsRectangles())
AForge.Imaging.Drawing.FillRectangle(data, rc, Color.FromArgb(128,Color.Red));
img2.UnlockBits(data);
img2.Save(#"compareResult.jpg");
}
The actual difference + blob detection part (without loading and result display) takes about 43ms, for the second run (this first time takes longer of course, due to JITting, cache, etc.)
Result (the rectangle is larger due to jpeg artifacts):
Here is a flood-fill based version of your code. It checks every pixel for difference. If it finds a different pixel, it runs an exploration to find the entire different area.
The code is only meant as an illustration. There are certainly some points that could be improved.
unsafe bool ArePixelsEqual(byte* p1, byte* p2, int bytesPerPixel)
{
for (int i = 0; i < bytesPerPixel; ++i)
if (p1[i] != p2[i])
return false;
return true;
}
private static unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
if (bmp.PixelFormat != bmp2.PixelFormat || bmp.Width != bmp2.Width || bmp.Height != bmp2.Height)
throw new ArgumentException();
List<Rectangle> rec = new List<Rectangle>();
var bmData1 = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
var bmData2 = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
int bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
IntPtr scan01 = bmData1.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride1 = bmData1.Stride;
int stride2 = bmData2.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
bool[] visited = new bool[nWidth * nHeight];
byte* base1 = (byte*)scan01.ToPointer();
byte* base2 = (byte*)scan02.ToPointer();
for (int y = 0; y < nHeight; y++)
{
byte* p1 = base1;
byte* p2 = base2;
for (int x = 0; x < nWidth; ++x)
{
if (!ArePixelsEqual(p1, p2, bytesPerPixel) && !(visited[x + nWidth * y]))
{
// fill the different area
int minX = x;
int maxX = x;
int minY = y;
int maxY = y;
var pt = new Point(x, y);
Stack<Point> toBeProcessed = new Stack<Point>();
visited[x + nWidth * y] = true;
toBeProcessed.Push(pt);
while (toBeProcessed.Count > 0)
{
var process = toBeProcessed.Pop();
var ptr1 = (byte*)scan01.ToPointer() + process.Y * stride1 + process.X * bytesPerPixel;
var ptr2 = (byte*)scan02.ToPointer() + process.Y * stride2 + process.X * bytesPerPixel;
//Check pixel equality
if (ArePixelsEqual(ptr1, ptr2, bytesPerPixel))
continue;
//This pixel is different
//Update the rectangle
if (process.X < minX) minX = process.X;
if (process.X > maxX) maxX = process.X;
if (process.Y < minY) minY = process.Y;
if (process.Y > maxY) maxY = process.Y;
Point n; int idx;
//Put neighbors in stack
if (process.X - 1 >= 0)
{
n = new Point(process.X - 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.X + 1 < nWidth)
{
n = new Point(process.X + 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y - 1 >= 0)
{
n = new Point(process.X, process.Y - 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y + 1 < nHeight)
{
n = new Point(process.X, process.Y + 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
}
rec.Add(new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1));
}
p1 += bytesPerPixel;
p2 += bytesPerPixel;
}
base1 += stride1;
base2 += stride2;
}
bmp.UnlockBits(bmData1);
bmp2.UnlockBits(bmData2);
return rec;
}
You can achieve this easily using a flood fill segmentation algorithm.
First an utility class to make fast bitmap access easier. This will help to encapsulate the complex pointer-logic and make the code more readable:
class BitmapWithAccess
{
public Bitmap Bitmap { get; private set; }
public System.Drawing.Imaging.BitmapData BitmapData { get; private set; }
public BitmapWithAccess(Bitmap bitmap, System.Drawing.Imaging.ImageLockMode lockMode)
{
Bitmap = bitmap;
BitmapData = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), lockMode, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
}
public Color GetPixel(int x, int y)
{
unsafe
{
byte* dataPointer = MovePointer((byte*)BitmapData.Scan0, x, y);
return Color.FromArgb(dataPointer[3], dataPointer[2], dataPointer[1], dataPointer[0]);
}
}
public void SetPixel(int x, int y, Color color)
{
unsafe
{
byte* dataPointer = MovePointer((byte*)BitmapData.Scan0, x, y);
dataPointer[3] = color.A;
dataPointer[2] = color.R;
dataPointer[1] = color.G;
dataPointer[0] = color.B;
}
}
public void Release()
{
Bitmap.UnlockBits(BitmapData);
BitmapData = null;
}
private unsafe byte* MovePointer(byte* pointer, int x, int y)
{
return pointer + x * 4 + y * BitmapData.Stride;
}
}
Then a class representing a rectangle containing different pixels, to mark them in the resulting image. In general this class can also contain a list of Point instances (or a byte[,] map) to make indicating individual pixels in the resulting image possible:
class Segment
{
public int Left { get; set; }
public int Top { get; set; }
public int Right { get; set; }
public int Bottom { get; set; }
public Bitmap Bitmap { get; set; }
public Segment()
{
Left = int.MaxValue;
Right = int.MinValue;
Top = int.MaxValue;
Bottom = int.MinValue;
}
};
Then the steps of a simple algorithm are as follows:
find different pixels
use a flood-fill algorithm to find segments on the difference image
draw bounding rectangles for the segments found
The first step is the easiest one:
static Bitmap FindDifferentPixels(Bitmap i1, Bitmap i2)
{
var result = new Bitmap(i1.Width, i2.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
var ia1 = new BitmapWithAccess(i1, System.Drawing.Imaging.ImageLockMode.ReadOnly);
var ia2 = new BitmapWithAccess(i2, System.Drawing.Imaging.ImageLockMode.ReadOnly);
var ra = new BitmapWithAccess(result, System.Drawing.Imaging.ImageLockMode.ReadWrite);
for (int x = 0; x < i1.Width; ++x)
for (int y = 0; y < i1.Height; ++y)
{
var different = ia1.GetPixel(x, y) != ia2.GetPixel(x, y);
ra.SetPixel(x, y, different ? Color.White : Color.FromArgb(0, 0, 0, 0));
}
ia1.Release();
ia2.Release();
ra.Release();
return result;
}
And the second and the third steps are covered with the following three functions:
static List<Segment> Segmentize(Bitmap blackAndWhite)
{
var bawa = new BitmapWithAccess(blackAndWhite, System.Drawing.Imaging.ImageLockMode.ReadOnly);
var result = new List<Segment>();
HashSet<Point> queue = new HashSet<Point>();
bool[,] visitedPoints = new bool[blackAndWhite.Width, blackAndWhite.Height];
for (int x = 0;x < blackAndWhite.Width;++x)
for (int y = 0;y < blackAndWhite.Height;++y)
{
if (bawa.GetPixel(x, y).A != 0
&& !visitedPoints[x, y])
{
result.Add(BuildSegment(new Point(x, y), bawa, visitedPoints));
}
}
bawa.Release();
return result;
}
static Segment BuildSegment(Point startingPoint, BitmapWithAccess bawa, bool[,] visitedPoints)
{
var result = new Segment();
List<Point> toProcess = new List<Point>();
toProcess.Add(startingPoint);
while (toProcess.Count > 0)
{
Point p = toProcess.First();
toProcess.RemoveAt(0);
ProcessPoint(result, p, bawa, toProcess, visitedPoints);
}
return result;
}
static void ProcessPoint(Segment segment, Point point, BitmapWithAccess bawa, List<Point> toProcess, bool[,] visitedPoints)
{
for (int i = -1; i <= 1; ++i)
{
for (int j = -1; j <= 1; ++j)
{
int x = point.X + i;
int y = point.Y + j;
if (x < 0 || y < 0 || x >= bawa.Bitmap.Width || y >= bawa.Bitmap.Height)
continue;
if (bawa.GetPixel(x, y).A != 0 && !visitedPoints[x, y])
{
segment.Left = Math.Min(segment.Left, x);
segment.Right = Math.Max(segment.Right, x);
segment.Top = Math.Min(segment.Top, y);
segment.Bottom = Math.Max(segment.Bottom, y);
toProcess.Add(new Point(x, y));
visitedPoints[x, y] = true;
}
}
}
}
And the following program given your two images as arguments:
static void Main(string[] args)
{
Image ai1 = Image.FromFile(args[0]);
Image ai2 = Image.FromFile(args[1]);
Bitmap i1 = new Bitmap(ai1.Width, ai1.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Bitmap i2 = new Bitmap(ai2.Width, ai2.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (var g1 = Graphics.FromImage(i1))
using (var g2 = Graphics.FromImage(i2))
{
g1.DrawImage(ai1, Point.Empty);
g2.DrawImage(ai2, Point.Empty);
}
var difference = FindDifferentPixels(i1, i2);
var segments = Segmentize(difference);
using (var g1 = Graphics.FromImage(i1))
{
foreach (var segment in segments)
{
g1.DrawRectangle(Pens.Red, new Rectangle(segment.Left, segment.Top, segment.Right - segment.Left, segment.Bottom - segment.Top));
}
}
i1.Save("result.png");
Console.WriteLine("Done.");
Console.ReadKey();
}
produces the following result:
As you can see there are more differences between the given images. You can filter the resulting segments with regard to their size for example to drop the small artefacts. Also there is of course much work to do in terms of error checking, design and performance.
One idea is to proceed as follows:
1) Rescale images to a smaller size (downsample)
2) Run the above algorithm on smaller images
3) Run the above algorithm on original images, but restricting yourself only to rectangles found in step 2)
This can be of course extended to a multi-level hierarchical approach (using more different image sizes, increasing accuracy with each step).
Ah an algorithm challenge. Like! :-)
There are other answers here using f.ex. floodfill that will work just fine. I just noticed that you wanted something fast, so let me propose a different idea. Unlike the other people, I haven't tested it; it shouldn't be too hard and should be quite fast, but I simply don't have the time at the moment to test it myself. If you do, please share the results. Also, note that it's not a standard algorithm, so there are probably some bugs here and there in my explanation (and no patents).
My idea is derived from the idea of mean adaptive thresholding but with a lot of important differences. I cannot find the link from wikipedia anymore or my code, so I'll do this from the top of my mind. Basically you create a new (64-bit) buffer for both images and fill it with:
f(x,y) = colorvalue + f(x-1, y) + f(x, y-1) - f(x-1, y-1)
f(x,0) = colorvalue + f(x-1, 0)
f(0,y) = colorvalue + f(0, y-1)
The main trick is that you can calculate the sum value of a portion of the image fast, namely by:
g(x1,y1,x2,y2) = f(x2,y2)-f(x1-1,y2)-f(x2,y1-1)+f(x1-1,y1-1)
In other words, this will give the same result as:
result = 0;
for (x=x1; x<=x2; ++x)
for (y=y1; y<=y2; ++y)
result += f(x,y)
In our case this means that with only 4 integer operations this will get you some unique number of the block in question. I'd say that's pretty awesome.
Now, in our case, we don't really care about the average value; we just care about some sort-of unique number. If the image changes, it should change - simple as that. As for colorvalue, usually some gray scale number is used for thresholding - instead, we'll be using the complete 24-bit RGB value. Because there are only so few compares, we can simply scan until we find a block that doesn't match.
The basic algorithm that I propose works as follows:
for (y=0; y<height;++y)
for (x=0; x<width; ++x)
if (src[x,y] != dst[x,y])
if (!IntersectsWith(x, y, foundBlocks))
FindBlock(foundBlocks);
Now, IntersectsWith can be something like a quad tree of if there are only a few blocks, you can simply iterate through the blocks and check if they are within the bounds of the block. You can also update the x variable accordingly (I would). You can even balance things by re-building the buffer for f(x,y) if you have too many blocks (more precise: merge found blocks back from dst into src, then rebuild the buffer).
FindBlocks is where it gets interesting. Using the formula for g that's now pretty easy:
int x1 = x-1; int y1 = y-1; int x2 = x; int y2 = y;
while (changes)
{
while (g(srcimage,x1-1,y1,x1,y2) == g(dstimage,x1-1,y1,x1,y2)) { --x1; }
while (g(srcimage,x1,y1-1,x1,y2) == g(dstimage,x1,y1-1,x1,y2)) { --y1; }
while (g(srcimage,x1,y1,x1+1,y2) == g(dstimage,x1,y1,x1+1,y2)) { ++x1; }
while (g(srcimage,x1,y1,x1,y2+1) == g(dstimage,x1,y1,x1,y2+1)) { ++y1; }
}
That's it. Note that the complexity of the FindBlocks algorithm is O(x + y), which is pretty awesome for finding a 2D block IMO. :-)
As I said, let me know how it turns out.
I have the following code:
int width = 10;
int height = 7;
bool[,] array1 = new bool[width, height];
string values =
"1100000000" +
"1100000011" +
"0001100011" +
"0001100000" +
"0001110000" +
"0000000110" +
"0000000110";
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
array1[x, y] = (values[x + y * width] == '1');
}
}
im looking for a algorithm that would extract Ranges where we have a 1.
so from this data we would get rectangles
(0,0,2,2),
(8,1,2,2),
(3,2,3,3),
(7,5,2,2)
the order of the rectangles do not matter!
But i have no idea how to do this any one got any pointers?
After reading Rusty Weber answer i came up with the following:
private static List<Rectangle> GetRectangles(bool[,] array)
{
List<Rectangle> rectangles = new List<Rectangle>();
for (int x = 0; x < array.GetLength(0); x++)
{
for (int y = 0; y < array.GetLength(1); y++)
{
if (array[x, y])
{
rectangles.Add(GetRectangle(array, new Point(x, y)));
}
}
}
return rectangles;
}
static Rectangle GetRectangle(bool[,] array, Point startLocation)
{
int maxX = int.MinValue;
int minX = int.MaxValue;
int maxY = int.MinValue;
int minY = int.MaxValue;
HashSet<Point> visitedLocations = new HashSet<Point>();
Stack<Point> pointsToGo = new Stack<Point>();
Point location;
pointsToGo.Push(startLocation);
while (pointsToGo.Count > 0)
{
location = pointsToGo.Pop();
if (!location.X.IsBetween(0, array.GetLength(0) - 1))
continue;
if (!location.Y.IsBetween(0, array.GetLength(1) - 1))
continue;
if (!array[location.X, location.Y])
continue;
if (visitedLocations.Contains(location))
continue;
visitedLocations.Add(location);
pointsToGo.Push(new Point(location.X + 1, location.Y));
pointsToGo.Push(new Point(location.X, location.Y + 1));
pointsToGo.Push(new Point(location.X - 1, location.Y));
pointsToGo.Push(new Point(location.X, location.Y - 1));
}
foreach (Point location2 in visitedLocations)
{
array[location2.X, location2.Y] = false;
if (location2.X > maxX)
maxX = location2.X;
if (location2.X < minX)
minX = location2.X;
if (location2.Y > maxY)
maxY = location2.Y;
if (location2.Y < minY)
minY = location2.Y;
}
return new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1);
}
public static bool IsBetween<T>(this T item, T start, T end)
{
return Comparer<T>.Default.Compare(item, start) >= 0
&& Comparer<T>.Default.Compare(item, end) <= 0;
}
COMMENT :: It might help me to answer your question if you have better defined coordinates. (0,0,2,2) isn't exactly Cartesian and it may need some explaining. Is this the top left corner followed by the widths?
Ok. The easiest to program way, in my opinion at least, to extract all possible rectangles from the graph is to have a recursively defined method that searches in a specific direction for the symmetric rectangle pattern. This however could end up being really slow so I hope that speed isn't a constraint for you. Looking at the style of code, I would say that this is a school assignment for either recursion or dynamic programming.
something along the lines of the following pseudocode
`
for i in width
{
for j in height
{
if(point[i,j] == 1)
{
potentials = searh_in_direction(i,j,graph,width,height,RIGHT,[[i,j]] )
listOfAllRects.append(potentials)
}
}
}
list_of_rectangle searh_in_direction(i,j,graph,width,height,direction, listofpoints )
{
nextdirection = direction.nextdirection; //Right -> down -> left-> up
//DEVELOP METHOD FOR RECURSION HERE THAT RETURNS ALL SETS OF 4 POINTS THAT
for every point in the direction of travel
if the point is the origional point and we have 4 points including the point we are looking at, we have a rectangle and we need to return
if point on direction of travel is a one travel on the next direction
posiblerects.append(searh_in_direction(i,j,graph,width,height,nextdirection , listofpoints.append(currentpoint)))
//after all points in direction have bee searched
return posiblerects.
}
`
I know that this code could be very confusing but that is the gist of what you need as a recursive element.
I will also note that I can already see several bugs in this code but I have run out of the 15 minutes that I said that I was going to spend on this post so you might have to pick them out yourself.
This gives you the same results you're looking for:
static void Main(string[] args)
{
string values =
"1100000000" +
"1100000011" +
"0001100011" +
"0001100000" +
"0001110000" +
"0000000110" +
"0000000110";
int width = 10;
int height = 7;
bool[,] array = new bool[width, height];
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
array[x, y] = (values[x + y * width] == '1');
List<Rectangle> rectangles = new List<Rectangle>();
for (int x = 0; x < width; ++x)
{
for (int y = 0; y < height; ++y)
{
if (array[x, y] && !Used(rectangles, x, y))
{
int rHeight = 1;
for (int rX = x + 1; rX < width && array[rX, y] && !Used(rectangles, rX, y); ++rX)
for (int rY = y + 1; rY < height && array[rX, rY] && !Used(rectangles, rX, rY); ++rY)
if (rY - y >= rHeight)
rHeight = rY - y + 1;
int rWidth = 1;
for (int rY = y + 1; rY < height && rY - y <= rHeight && array[x, rY] && !Used(rectangles, x, rY); ++rY)
for (int rX = x + 1; rX < width && array[rX, rY] && !Used(rectangles, rX, rY); ++rX)
if (rX - x >= rWidth)
rWidth = rX - x + 1;
rectangles.Add(new Rectangle(x, y, rWidth, rHeight));
}
}
}
foreach (Rectangle rect in rectangles)
Console.WriteLine(rect);
}
private static bool Used(IEnumerable<Rectangle> rectangles, int x, int y)
{
return rectangles.Any(r => r.Contains(x, y));
}
I made an adhoc Rectangle struct since I didn't reference System.Drawing, but you can pass a System.Drawing.Point to the System.Drawing.Rectangle.Contains() and get the same results.
Also, notice that the width of your array should actually be 10 and your indexing math was wrong. You should be multiplying y by the width, not the height.
It is not clear from the question if you really want rectangles that cover the 1's exactly, or if you want bounding volumes that can contain zeroes, but will cover all the 1's with a reasonably small number of rectangles.
Assuming you want rectangles to cover the 1's, and you don't need a perfect solution:
Make a temporary copy of the array.
Iterate over the temporary looking for 1's
When you hit a 1, begin a new rectagle that starts as 1x1, offset to that location ( e.g. covers just that 1 )
Expand that rectangle rightward so long as there is a 1 in the next cell
Expand that rectangle downards so long as the row below has 1's matching the width
of the current rectangle.
ONce you can't expand down any more, emit that recgantle, and clear all the 1's covered by that rectangle from the temporary
continue scanning for 1's starting with the cell directly after the top right corner of the current rectangle.
This will produce a decent covering - but by no means ideal. If you need a perfect covering - e.g. the guaranteed minimum number of rectangles then it is harder.
I'm attempting to add semi-realistic water into my tile-based, 2D platformer. The water must act somewhat lifelike, with a pressure model that runs entirely local. (IE. Can only use data from cells near it) This model is needed because of the nature of my game, where you cannot be certain that the data you need isn't inside an area that isn't in memory.
I've tried one method so far, but I could not refine it enough to work with my constraints.
For that model, each cell would be slightly compressible, depending on the amount of water in the above cell. When a cell's water content was larger than the normal capacity, the cell would try to expand upwards. This created a fairly nice simulation, abeit slow (Not lag; Changes in the water were taking a while to propagate.), at times. When I tried to implement this into my engine, I found that my limitations lacked the precision required for it to work. I can provide a more indepth explanation or a link to the original concept if you wish.
My constraints:
Only 256 discrete values for water level. (No floating point variables :( ) -- EDIT. Floats are fine.
Fixed grid size.
2D Only.
U-Bend Configurations must work.
The language that I'm using is C#, but I can probably take other languages and translate it to C#.
The question is, can anyone give me a pressure model for water, following my constraints as closely as possible?
How about a different approach?
Forget about floats, that's asking for roundoff problems in the long run. Instead, how about a unit of water?
Each cell contains a certain number of units of water. Each iteration you compare the cell with it's 4 neighbors and move say 10% (change this to alter the propagation speed) of the difference in the number of units of water. A mapping function translates the units of water into a water level.
To avoid calculation order problems use two values, one for the old units, one for the new. Calculate everything and then copy the updated values back. 2 ints = 8 bytes per cell. If you have a million cells that's still only 8mb.
If you are actually trying to simulate waves you'll need to also store the flow--4 values, 16 mb. To make a wave put some inertia to the flow--after you calculate the desired flow then move the previous flow say 10% of the way towards the desired value.
Try treating each contiguous area of water as a single area (like flood fill) and track 1) the lowest cell(s) where water can escape and 2) the highest cell(s) from which water can come, then move water from the top to the bottom. This isn't local, but I think you can treat the edges of the area you want to affect as not connected and process any subset that you want. Re-evaluate what areas are contiguous on each frame (re-flood on each frame) so that when blobs converge, they can start being treated as one.
Here's my code from a Windows Forms demo of the idea. It may need some fine tuning, but seems to work quite well in my tests:
public partial class Form1 : Form
{
byte[,] tiles;
const int rows = 50;
const int cols = 50;
public Form1()
{
SetStyle(ControlStyles.ResizeRedraw, true);
InitializeComponent();
tiles = new byte[cols, rows];
for (int i = 0; i < 10; i++)
{
tiles[20, i+20] = 1;
tiles[23, i+20] = 1;
tiles[32, i+20] = 1;
tiles[35, i+20] = 1;
tiles[i + 23, 30] = 1;
tiles[i + 23, 32] = 1;
tiles[21, i + 15] = 2;
tiles[21, i + 4] = 2;
if (i % 2 == 0) tiles[22, i] = 2;
}
tiles[20, 30] = 1;
tiles[20, 31] = 1;
tiles[20, 32] = 1;
tiles[21, 32] = 1;
tiles[22, 32] = 1;
tiles[33, 32] = 1;
tiles[34, 32] = 1;
tiles[35, 32] = 1;
tiles[35, 31] = 1;
tiles[35, 30] = 1;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (SolidBrush b = new SolidBrush(Color.White))
{
for (int y = 0; y < rows; y++)
{
for (int x = 0; x < cols; x++)
{
switch (tiles[x, y])
{
case 0:
b.Color = Color.White;
break;
case 1:
b.Color = Color.Black;
break;
default:
b.Color = Color.Blue;
break;
}
e.Graphics.FillRectangle(b, x * ClientSize.Width / cols, y * ClientSize.Height / rows,
ClientSize.Width / cols + 1, ClientSize.Height / rows + 1);
}
}
}
}
private bool IsLiquid(int x, int y)
{
return tiles[x, y] > 1;
}
private bool IsSolid(int x, int y)
{
return tiles[x, y] == 1;
}
private bool IsEmpty(int x, int y)
{
return IsEmpty(tiles, x, y);
}
public static bool IsEmpty(byte[,] tiles, int x, int y)
{
return tiles[x, y] == 0;
}
private void ProcessTiles()
{
byte processedValue = 0xFF;
byte unprocessedValue = 0xFF;
for (int y = 0; y < rows; y ++)
for (int x = 0; x < cols; x++)
{
if (IsLiquid(x, y))
{
if (processedValue == 0xff)
{
unprocessedValue = tiles[x, y];
processedValue = (byte)(5 - tiles[x, y]);
}
if (tiles[x, y] == unprocessedValue)
{
BlobInfo blob = GetWaterAt(new Point(x, y), unprocessedValue, processedValue, new Rectangle(0, 0, 50, 50));
blob.ProcessMovement(tiles);
}
}
}
}
class BlobInfo
{
private int minY;
private int maxEscapeY;
private List<int> TopXes = new List<int>();
private List<int> BottomEscapeXes = new List<int>();
public BlobInfo(int x, int y)
{
minY = y;
maxEscapeY = -1;
TopXes.Add(x);
}
public void NoteEscapePoint(int x, int y)
{
if (maxEscapeY < 0)
{
maxEscapeY = y;
BottomEscapeXes.Clear();
}
else if (y < maxEscapeY)
return;
else if (y > maxEscapeY)
{
maxEscapeY = y;
BottomEscapeXes.Clear();
}
BottomEscapeXes.Add(x);
}
public void NoteLiquidPoint(int x, int y)
{
if (y < minY)
{
minY = y;
TopXes.Clear();
}
else if (y > minY)
return;
TopXes.Add(x);
}
public void ProcessMovement(byte[,] tiles)
{
int min = TopXes.Count < BottomEscapeXes.Count ? TopXes.Count : BottomEscapeXes.Count;
for (int i = 0; i < min; i++)
{
if (IsEmpty(tiles, BottomEscapeXes[i], maxEscapeY) && (maxEscapeY > minY))
{
tiles[BottomEscapeXes[i], maxEscapeY] = tiles[TopXes[i], minY];
tiles[TopXes[i], minY] = 0;
}
}
}
}
private BlobInfo GetWaterAt(Point start, byte unprocessedValue, byte processedValue, Rectangle bounds)
{
Stack<Point> toFill = new Stack<Point>();
BlobInfo result = new BlobInfo(start.X, start.Y);
toFill.Push(start);
do
{
Point cur = toFill.Pop();
while ((cur.X > bounds.X) && (tiles[cur.X - 1, cur.Y] == unprocessedValue))
cur.X--;
if ((cur.X > bounds.X) && IsEmpty(cur.X - 1, cur.Y))
result.NoteEscapePoint(cur.X - 1, cur.Y);
bool pushedAbove = false;
bool pushedBelow = false;
for (; ((cur.X < bounds.X + bounds.Width) && tiles[cur.X, cur.Y] == unprocessedValue); cur.X++)
{
result.NoteLiquidPoint(cur.X, cur.Y);
tiles[cur.X, cur.Y] = processedValue;
if (cur.Y > bounds.Y)
{
if (IsEmpty(cur.X, cur.Y - 1))
{
result.NoteEscapePoint(cur.X, cur.Y - 1);
}
if ((tiles[cur.X, cur.Y - 1] == unprocessedValue) && !pushedAbove)
{
pushedAbove = true;
toFill.Push(new Point(cur.X, cur.Y - 1));
}
if (tiles[cur.X, cur.Y - 1] != unprocessedValue)
pushedAbove = false;
}
if (cur.Y < bounds.Y + bounds.Height - 1)
{
if (IsEmpty(cur.X, cur.Y + 1))
{
result.NoteEscapePoint(cur.X, cur.Y + 1);
}
if ((tiles[cur.X, cur.Y + 1] == unprocessedValue) && !pushedBelow)
{
pushedBelow = true;
toFill.Push(new Point(cur.X, cur.Y + 1));
}
if (tiles[cur.X, cur.Y + 1] != unprocessedValue)
pushedBelow = false;
}
}
if ((cur.X < bounds.X + bounds.Width) && (IsEmpty(cur.X, cur.Y)))
{
result.NoteEscapePoint(cur.X, cur.Y);
}
} while (toFill.Count > 0);
return result;
}
private void timer1_Tick(object sender, EventArgs e)
{
ProcessTiles();
Invalidate();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
int x = e.X * cols / ClientSize.Width;
int y = e.Y * rows / ClientSize.Height;
if ((x >= 0) && (x < cols) && (y >= 0) && (y < rows))
tiles[x, y] = 2;
}
}
}
From a fluid dynamics viewpoint, a reasonably popular lattice-based algorithm family is the so-called Lattice Boltzmann method. A simple implementation, ignoring all the fine detail that makes academics happy, should be relatively simple and fast and also get reasonably correct dynamics.