Find local maxima in grayscale image using OpenCV - image
Does anybody know how to find the local maxima in a grayscale IPL_DEPTH_8U image using OpenCV? HarrisCorner mentions something like that but I'm actually not interested in corners ...
Thanks!
A pixel is considered a local maximum if it is equal to the maximum value in a 'local' neighborhood. The function below captures this property in two lines of code.
To deal with pixels on 'plateaus' (value equal to their neighborhood) one can use the local minimum property, since plateaus pixels are equal to their local minimum. The rest of the code filters out those pixels.
void non_maxima_suppression(const cv::Mat& image, cv::Mat& mask, bool remove_plateaus) {
// find pixels that are equal to the local neighborhood not maximum (including 'plateaus')
cv::dilate(image, mask, cv::Mat());
cv::compare(image, mask, mask, cv::CMP_GE);
// optionally filter out pixels that are equal to the local minimum ('plateaus')
if (remove_plateaus) {
cv::Mat non_plateau_mask;
cv::erode(image, non_plateau_mask, cv::Mat());
cv::compare(image, non_plateau_mask, non_plateau_mask, cv::CMP_GT);
cv::bitwise_and(mask, non_plateau_mask, mask);
}
}
Here's a simple trick. The idea is to dilate with a kernel that contains a hole in the center. After the dilate operation, each pixel is replaced with the maximum of it's neighbors (using a 5 by 5 neighborhood in this example), excluding the original pixel.
Mat1b kernelLM(Size(5, 5), 1u);
kernelLM.at<uchar>(2, 2) = 0u;
Mat imageLM;
dilate(image, imageLM, kernelLM);
Mat1b localMaxima = (image > imageLM);
Actually after I posted the code above I wrote a better and very very faster one ..
The code above suffers even for a 640x480 picture..
I optimized it and now it is very very fast even for 1600x1200 pic.
Here is the code :
void localMaxima(cv::Mat src,cv::Mat &dst,int squareSize)
{
if (squareSize==0)
{
dst = src.clone();
return;
}
Mat m0;
dst = src.clone();
Point maxLoc(0,0);
//1.Be sure to have at least 3x3 for at least looking at 1 pixel close neighbours
// Also the window must be <odd>x<odd>
SANITYCHECK(squareSize,3,1);
int sqrCenter = (squareSize-1)/2;
//2.Create the localWindow mask to get things done faster
// When we find a local maxima we will multiply the subwindow with this MASK
// So that we will not search for those 0 values again and again
Mat localWindowMask = Mat::zeros(Size(squareSize,squareSize),CV_8U);//boolean
localWindowMask.at<unsigned char>(sqrCenter,sqrCenter)=1;
//3.Find the threshold value to threshold the image
//this function here returns the peak of histogram of picture
//the picture is a thresholded picture it will have a lot of zero values in it
//so that the second boolean variable says :
// (boolean) ? "return peak even if it is at 0" : "return peak discarding 0"
int thrshld = maxUsedValInHistogramData(dst,false);
threshold(dst,m0,thrshld,1,THRESH_BINARY);
//4.Now delete all thresholded values from picture
dst = dst.mul(m0);
//put the src in the middle of the big array
for (int row=sqrCenter;row<dst.size().height-sqrCenter;row++)
for (int col=sqrCenter;col<dst.size().width-sqrCenter;col++)
{
//1.if the value is zero it can not be a local maxima
if (dst.at<unsigned char>(row,col)==0)
continue;
//2.the value at (row,col) is not 0 so it can be a local maxima point
m0 = dst.colRange(col-sqrCenter,col+sqrCenter+1).rowRange(row-sqrCenter,row+sqrCenter+1);
minMaxLoc(m0,NULL,NULL,NULL,&maxLoc);
//if the maximum location of this subWindow is at center
//it means we found the local maxima
//so we should delete the surrounding values which lies in the subWindow area
//hence we will not try to find if a point is at localMaxima when already found a neighbour was
if ((maxLoc.x==sqrCenter)&&(maxLoc.y==sqrCenter))
{
m0 = m0.mul(localWindowMask);
//we can skip the values that we already made 0 by the above function
col+=sqrCenter;
}
}
}
The following listing is a function similar to Matlab's "imregionalmax". It looks for at most nLocMax local maxima above threshold, where the found local maxima are at least minDistBtwLocMax pixels apart. It returns the actual number of local maxima found. Notice that it uses OpenCV's minMaxLoc to find global maxima. It is "opencv-self-contained" except for the (easy to implement) function vdist, which computes the (euclidian) distance between points (r,c) and (row,col).
input is one-channel CV_32F matrix, and locations is nLocMax (rows) by 2 (columns) CV_32S matrix.
int imregionalmax(Mat input, int nLocMax, float threshold, float minDistBtwLocMax, Mat locations)
{
Mat scratch = input.clone();
int nFoundLocMax = 0;
for (int i = 0; i < nLocMax; i++) {
Point location;
double maxVal;
minMaxLoc(scratch, NULL, &maxVal, NULL, &location);
if (maxVal > threshold) {
nFoundLocMax += 1;
int row = location.y;
int col = location.x;
locations.at<int>(i,0) = row;
locations.at<int>(i,1) = col;
int r0 = (row-minDistBtwLocMax > -1 ? row-minDistBtwLocMax : 0);
int r1 = (row+minDistBtwLocMax < scratch.rows ? row+minDistBtwLocMax : scratch.rows-1);
int c0 = (col-minDistBtwLocMax > -1 ? col-minDistBtwLocMax : 0);
int c1 = (col+minDistBtwLocMax < scratch.cols ? col+minDistBtwLocMax : scratch.cols-1);
for (int r = r0; r <= r1; r++) {
for (int c = c0; c <= c1; c++) {
if (vdist(Point2DMake(r, c),Point2DMake(row, col)) <= minDistBtwLocMax) {
scratch.at<float>(r,c) = 0.0;
}
}
}
} else {
break;
}
}
return nFoundLocMax;
}
The first question to answer would be what is "local" in your opinion. The answer may well be a square window (say 3x3 or 5x5) or circular window of a certain radius. You can then scan over the entire image with the window centered at each pixel and pick the highest value in the window.
See this for how to access pixel values in OpenCV.
This is very fast method. It stored founded maxima in a vector of
Points.
vector <Point> GetLocalMaxima(const cv::Mat Src,int MatchingSize, int Threshold, int GaussKernel )
{
vector <Point> vMaxLoc(0);
if ((MatchingSize % 2 == 0) || (GaussKernel % 2 == 0)) // MatchingSize and GaussKernel have to be "odd" and > 0
{
return vMaxLoc;
}
vMaxLoc.reserve(100); // Reserve place for fast access
Mat ProcessImg = Src.clone();
int W = Src.cols;
int H = Src.rows;
int SearchWidth = W - MatchingSize;
int SearchHeight = H - MatchingSize;
int MatchingSquareCenter = MatchingSize/2;
if(GaussKernel > 1) // If You need a smoothing
{
GaussianBlur(ProcessImg,ProcessImg,Size(GaussKernel,GaussKernel),0,0,4);
}
uchar* pProcess = (uchar *) ProcessImg.data; // The pointer to image Data
int Shift = MatchingSquareCenter * ( W + 1);
int k = 0;
for(int y=0; y < SearchHeight; ++y)
{
int m = k + Shift;
for(int x=0;x < SearchWidth ; ++x)
{
if (pProcess[m++] >= Threshold)
{
Point LocMax;
Mat mROI(ProcessImg, Rect(x,y,MatchingSize,MatchingSize));
minMaxLoc(mROI,NULL,NULL,NULL,&LocMax);
if (LocMax.x == MatchingSquareCenter && LocMax.y == MatchingSquareCenter)
{
vMaxLoc.push_back(Point( x+LocMax.x,y + LocMax.y ));
// imshow("W1",mROI);cvWaitKey(0); //For gebug
}
}
}
k += W;
}
return vMaxLoc;
}
Found a simple solution.
In this example, if you are trying to find 2 results of a matchTemplate function with a minimum distance from each other.
cv::Mat result;
matchTemplate(search, target, result, CV_TM_SQDIFF_NORMED);
float score1;
cv::Point displacement1 = MinMax(result, score1);
cv::circle(result, cv::Point(displacement1.x+result.cols/2 , displacement1.y+result.rows/2), 10, cv::Scalar(0), CV_FILLED, 8, 0);
float score2;
cv::Point displacement2 = MinMax(result, score2);
where
cv::Point MinMax(cv::Mat &result, float &score)
{
double minVal, maxVal;
cv::Point minLoc, maxLoc, matchLoc;
minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, cv::Mat());
matchLoc.x = minLoc.x - result.cols/2;
matchLoc.y = minLoc.y - result.rows/2;
return minVal;
}
The process is:
Find global Minimum using minMaxLoc
Draw a filled white circle around global minimum using min distance between minima as radius
Find another minimum
The the scores can be compared to each other to determine, for example, the certainty of the match,
To find more than just the global minimum and maximum try using this function from skimage:
http://scikit-image.org/docs/dev/api/skimage.feature.html#skimage.feature.peak_local_max
You can parameterize the minimum distance between peaks, too. And more. To find minima, use negated values (take care of the array type though, 255-image could do the trick).
You can go over each pixel and test if it is a local maxima. Here is how I would do it.
The input is assumed to be type CV_32FC1
#include <vector>//std::vector
#include <algorithm>//std::sort
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/core/core.hpp"
//structure for maximal values including position
struct SRegionalMaxPoint
{
SRegionalMaxPoint():
values(-FLT_MAX),
row(-1),
col(-1)
{}
float values;
int row;
int col;
//ascending order
bool operator()(const SRegionalMaxPoint& a, const SRegionalMaxPoint& b)
{
return a.values < b.values;
}
};
//checks if pixel is local max
bool isRegionalMax(const float* im_ptr, const int& cols )
{
float center = *im_ptr;
bool is_regional_max = true;
im_ptr -= (cols + 1);
for (int ii = 0; ii < 3; ++ii, im_ptr+= (cols-3))
{
for (int jj = 0; jj < 3; ++jj, im_ptr++)
{
if (ii != 1 || jj != 1)
{
is_regional_max &= (center > *im_ptr);
}
}
}
return is_regional_max;
}
void imregionalmax(
const cv::Mat& input,
std::vector<SRegionalMaxPoint>& buffer)
{
//find local max - top maxima
static const int margin = 1;
const int rows = input.rows;
const int cols = input.cols;
for (int i = margin; i < rows - margin; ++i)
{
const float* im_ptr = input.ptr<float>(i, margin);
for (int j = margin; j < cols - margin; ++j, im_ptr++)
{
//Check if pixel is local maximum
if ( isRegionalMax(im_ptr, cols ) )
{
cv::Rect roi = cv::Rect(j - margin, i - margin, 3, 3);
cv::Mat subMat = input(roi);
float val = *im_ptr;
//replace smallest value in buffer
if ( val > buffer[0].values )
{
buffer[0].values = val;
buffer[0].row = i;
buffer[0].col = j;
std::sort(buffer.begin(), buffer.end(), SRegionalMaxPoint());
}
}
}
}
}
For testing the code you can try this:
cv::Mat temp = cv::Mat::zeros(15, 15, CV_32FC1);
temp.at<float>(7, 7) = 1;
temp.at<float>(3, 5) = 6;
temp.at<float>(8, 10) = 4;
temp.at<float>(11, 13) = 7;
temp.at<float>(10, 3) = 8;
temp.at<float>(7, 13) = 3;
vector<SRegionalMaxPoint> buffer_(5);
imregionalmax(temp, buffer_);
cv::Mat debug;
cv::cvtColor(temp, debug, cv::COLOR_GRAY2BGR);
for (auto it = buffer_.begin(); it != buffer_.end(); ++it)
{
circle(debug, cv::Point(it->col, it->row), 1, cv::Scalar(0, 255, 0));
}
This solution does not take plateaus into account so it is not exactly the same as matlab's imregionalmax()
I think you want to use the
MinMaxLoc(arr, mask=NULL)-> (minVal, maxVal, minLoc, maxLoc)
Finds global minimum and maximum in array or subarray
function on you image
Related
copy an one eigen matrix of vectors
I have A(matrix of vectors with length = depth) is 5x5 (5 rows and 5 cols). depth = 3 (it is the length of vector of any cell of matrix A). B(matrix of single values) is 75 x Any (5*5*3 rows and Any cols). x_size_kernel = 5. block_idx is the index, here for example we have made it equal 0 (for one column of matrix B only) The task of this simple and strict example is to copy all vectors of matrix A to one (first column) of matrix B. Now I solve the problem like this (it is concrete example with precise data) Eigen::MatrixXf B; B = Eigen::MatrixXf(x_size_kernel * y_size_kernel * depth, 100).setZero(); Eigen::Matrix<Eigen::VectorXf, Eigen::Dynamic, Eigen::Dynamic> A; A.resize(5, 5); auto depth = 3; for (auto yy = 0; yy < A.rows(); yy++) { for (auto xx = 0; xx < A.cols(); xx++) { A(yy, xx).resize(depth); } } auto block_idx = 0; // and here are all copy for one column of matrix B for (auto my = 0; my < x_size_kernel; my++) { for (auto mx = 0; mx < x_size_kernel; mx++) { // add the next column of block data B.col(block_idx). segment(mx * depth + my * x_size_kernel * depth, depth).noalias() = A(my, mx); } } But the above code is very slow, so I need more fast code. Maybe somebody know how to copy data in such way using only Eigen one pass. Thank you for helping.
c code is running to slow from nested for loops
my c program is running to slow (right now it is around 40 seconds without parallelization). I have tried using openmp which has brought the timing down significantly but I am looking to use simple and natural ways to make my code run faster other than using parallel for loops. The basic structure of the code is that is takes some command line arguments as inputs and then saves those inputs as variables. Then it recursively computes a variable called Rplus1 using the math.h library and the complex.h library. The problem of the code and where it is taking most of it's time is at the bottom where there are nested for loops. My goal is to get the whole code running in under 5 seconds but as of now it runs in about 40 seconds without using parallel for loops. Please Help! #include "time.h" #include "stdio.h" #include "stdlib.h" #include "complex.h" #include "math.h" #include "string.h" #include "unistd.h" #include "omp.h" #define PI 3.14159265 int main (int argc, char *argv[]){ if(argc >= 8){ double start1 = omp_get_wtime(); // command line arguments are aligned in the following order: [theta] [number of layers in superlattice] [material_1] [lat const_1] [number of unit cells_1] [material_2] [lat const_2] [number of unit cells_2] .... [material_N] [lat const_N] [number of unit cells_N] [Log/Linear] [number of repeating superlattice layers] [yes/no] int N; sscanf(argv[2],"%d",&N); // Number of layers in superlattice specified by second input argument if(strcmp(argv[argc-1],"yes") == 0) //If the substrate is included then add one more layer to the N variable { N = N+1; } int total; sscanf(argv[argc-2],"%d",&total); // Number of repeating superlattice layers specified by second to last argument double layers[N][6], horizangle[1001], vertangle[1001]; double complex (*F_hkl)[1001][1001] = malloc(N*1001*1001*sizeof(complex double)), (*F_0)[1001][1001] = malloc(N*1001*1001*sizeof(complex double)), (*g)[1001][1001] = malloc(N*1001*1001*sizeof(complex double)), (*g_0)[1001][1001] = malloc(N*1001*1001*sizeof(complex double)),SF_table[10];// this array will hold the unit cell structure factors for all of the materials selected for each wavevector in the beam spectrum double real, real2, lam, c_light = 299792458, h_pl = 4.135667516e-15,E = 10e3, r_0 = 2.818e-15, Lccd = 1.013;// just a few variables to hold values through calculations and constants, speed of light, plancks const, photon energy, and detector distance from sample double angle; double complex z;// just a variable to hold complex numbers throughout calculations int i,j,m,n,t; // integers to index through arrays lam = (h_pl*c_light)/E; sscanf(argv[1],"%lf",&angle); //first argument is the angle of incidence, read it angle = angle*(PI/180.0); angle2 = -angle; double (*table)[10] = malloc(10*9*sizeof(double)); // this array holds all the coefficients to calculate the atomic scattering factor below double (*table2)[10] = malloc(10*2*sizeof(double)); FILE*datfile1 = fopen("/home/vhosts/xraydev.engr.wisc.edu/data/coef_table.bin","rb"); // read the binary file containg all the coefficients fread(table,sizeof(double),90,datfile1); fclose(datfile1); FILE*datfile2 = fopen("/home/vhosts/xraydev.engr.wisc.edu/data/dispersioncs.bin","rb"); fread(table2,sizeof(double),20,datfile2); fclose(datfile2); // Calculate scattering factors for all elements double a,b; double k_z = (sin(angle)/lam)*1e-10; // incorporate angular dependence of SF but neglect 0.24 degree divergence because of approximation for(i = 0;i<10;i++) // for each element... { SF_table[i] = 0; for(j = 0;j<4;j++) // summation { a = table[2*j][i]; b = table[2*j+1][i]; SF_table[i] = SF_table[i] + a * exp(-b*k_z*k_z); } SF_table[i] = SF_table[i] + table[8][i] + table2[0][i] + table2[1][i]*I; } free(table); double mm = 4.0, (*phi)[1001][1001] = malloc(N*1001*1001*sizeof(double)); for(i = 1; i < N+1; i++) // for each layer of material... { sscanf(argv[i*3+1],"%lf",&layers[i-1][1]); // get out of plane lattice constant sscanf(argv[i*3+2],"%lf",&layers[i-1][2]); // get the number of unit cells in the layer layers[i-1][1] = layers[i-1][1]*1e-10; // convert lat const input to meters // Define reciprocal space positions at the incident angle h, k, l layers[i-1][3] = 0; // h layers[i-1][4] = 0; // k double l; // l calculated for each wavevector in the spectrum because l changes with angle of incidence for (m = 0; m < 1001; m++) { for (n = 0; n <1001; n++) { l = 4; phi[i-1][m][n] = 2*PI*layers[i-1][1]*sin(angle)/lam; // Caculate phi for each layer if(strcmp(argv[i*3],"GaAs") == 0) { F_hkl[i-1][m][n] = (2+2*cexp(I*PI*l))*(SF_table[2]+SF_table[3]*cexp(I*PI*l/2)); F_0[i-1][m][n] = 0.5*8.0*(31 + table2[0][2] + table2[1][2]*I) + 0.5*8.0*(33 + table2[0][3] + table2[1][3]*I); g[i-1][m][n] = 2*r_0*F_hkl[i-1][m][n]/mm/layers[i-1][1]*cos(2*angle[m][n]); g_0[i-1][m][n] = 2*r_0*F_0[i-1][m][n]/mm/layers[i-1][1]; } if(strcmp(argv[i*3],"AlGaAs") == 0) { F_hkl[i-1][m][n] = (2+2*cexp(I*PI*l))*((0.76*SF_table[2]+ 0.24*SF_table[4])+SF_table[3]*cexp(I*PI*l/2)); F_0[i-1][m][n] = 0.24*4.0*(13 + table2[0][4] + table2[1][4]*I) + 0.76*4.0*(31 + table2[0][2] + table2[1][2]*I) + 4.0*(33 + table2[0][3] + table2[1][3]*I); g[i-1][m][n] = 2*r_0*F_hkl[i-1][m][n]/mm/layers[i-1][1]*cos(2*angle[m][n]); g_0[i-1][m][n] = 2*r_0*F_0[i-1][m][n]/mm/layers[i-1][1]; } } } } double complex (*Rplus1)[1001] = malloc(1001*1001*sizeof(double complex)); for (m = 0; m < 1001; m++) { for (n = 0; n <1001; n++) { Rplus1[m][n] = 0.0; } } double stop1 = omp_get_wtime(); for(i=1;i<N;i++) // For each layer of the film { for(j=0;j<layers[i][2];j++) // For each unit cell { for (m = 0; m < 1001; m++) // For each row of the diffraction pattern { for (n = 0; n <1001; n++) // For each column of the diffraction pattern { Rplus1[m][n] = -I*g[i][m][n] + ((1-I*g_0[i][m][n])*(1-I*g_0[i][m][n]))/(I*g[i][m][n] + (cos(-2*phi[i][m][n])+I*sin(-2*phi[i][m][n]))/Rplus1[m][n]); } } } } double stop2 = omp_get_wtime(); double elapsed1 = (double)(stop1 - start1);// Second user defined function to use Durbin and Follis recursive formula double elapsed2 = (double)(stop2 - start1);// Second user defined function to use Durbin and Follis recursive formula printf("main() through before diffraction function took %f seconds to run\n\n",elapsed1); printf("main() through after diffraction function took %f seconds to run\n\n",elapsed2); } }
How to access intensity of all the pixels of Image in openCV C++
For accessing single point, I am using this line of code and it works int intensity = gray_image.at<uchar>(Point(100, 100)); However when I use this code to access all the pixels in image, it gives memory error, for (int i = 0; i < gray_image.rows;i++) { for (int j = 0; j < gray_image.cols; j++) { intensity += gray_image.at<uchar>(Point(i, j)); } } When I run above code, it does not give compile time error but gives memory exception. Where am I going wrong?
You can just skip the use of Point and do the following. for (int i = 0; i < gray_image.rows;i++) { for (int j = 0; j < gray_image.cols; j++) { intensity += gray_image.at<uchar>(i, j); } }
You're requesting a pixel (j,i) that doesn't exist. This wouldn't have been an error in a square image (where the number of rows = number of columns), but you're using a rectangular image. The Mat::at function has multiple prototypes, the two that you're concerned with are: C++: template<typename T> T& Mat::at(int i, int j) C++: template<typename T> T& Mat::at(Point pt) The documentation for Mat::at states that Point pt is defined as the Element position specified as Point(j,i), so you've effectively swapped your rows and columns. The reason this happens is because the image is stored in a 1D array of pixels, and to get a pixel Point (r,c) is translated to p = r * image.cols + c;
Issue in plotting resultant bit map of two bit maps difference
I want to compare one bitmap with another bitmap (reference bitmap) and draw all the difference of it in resultant bit map. Using below code I am able to draw only difference area but not with exact color of it. Here is my code Bitmap ResultantBitMap = new Bitmap(bitMap1.Height, bitMap2.Height); BitmapData bitMap1Data = bitMap1.LockBits(new Rectangle(0, 0, bitMap1.Width, bitMap1.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); BitmapData bitMap2Data = bitMap2.LockBits(new Rectangle(0, 0, bitMap2.Width, bitMap2.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); BitmapData bitMapResultantData = ResultantBitMap.LockBits(new Rectangle(0, 0, ResultantBitMap.Width, ResultantBitMap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb); IntPtr scan0 = bitMap1Data.Scan0; IntPtr scan02 = bitMap2Data.Scan0; IntPtr scan0ResImg1 = bitMapResultantData.Scan0; int bitMap1Stride = bitMap1Data.Stride; int bitMap2Stride = bitMap2Data.Stride; int ResultantImageStride = bitMapResultantData.Stride; for (int y = 0; y < bitMap1.Height; y++) { //define the pointers inside the first loop for parallelizing byte* p = (byte*)scan0.ToPointer(); p += y * bitMap1Stride; byte* p2 = (byte*)scan02.ToPointer(); p2 += y * bitMap2Stride; byte* pResImg1 = (byte*)scan0ResImg1.ToPointer(); pResImg1 += y * ResultantImageStride; for (int x = 0; x < bitMap1.Width; x++) { //always get the complete pixel when differences are found if (Math.Abs(p[0] - p2[0]) >= 20 || Math.Abs(p[1] - p2[1]) >= 20 || Math.Abs(p[2] - p2[2]) >= 20) { pResImg1[0] = p2[0];// B pResImg1[1] = p2[1];//R pResImg1[2] = p2[2];//G pResImg1[3] = p2[3];//A (Opacity) } p += 4; p2 += 4; pResImg1 += 4; } } bitMap1.UnlockBits(bitMap1Data); bitMap2.UnlockBits(bitMap2Data); ResultantBitMap.UnlockBits(bitMapResultantData); ResultantBitMap.Save(#"c:\\abcd\abcd.jpeg"); What I want is the difference image with exact color of the reference image.
It's hard to tell what's going on without knowing what all those library calls and "+= 4" are but, are you sure p and p2 correspond to the first and second images of your diagram? Also, your "Format32bppArgb" format suggests that [0] corresponds to alpha, not to red. Maybe there's a problem with that, too.
What is an Efficient algorithm to find Area of Overlapping Rectangles
My situation Input: a set of rectangles each rect is comprised of 4 doubles like this: (x0,y0,x1,y1) they are not "rotated" at any angle, all they are "normal" rectangles that go "up/down" and "left/right" with respect to the screen they are randomly placed - they may be touching at the edges, overlapping , or not have any contact I will have several hundred rectangles this is implemented in C# I need to find The area that is formed by their overlap - all the area in the canvas that more than one rectangle "covers" (for example with two rectangles, it would be the intersection) I don't need the geometry of the overlap - just the area (example: 4 sq inches) Overlaps shouldn't be counted multiple times - so for example imagine 3 rects that have the same size and position - they are right on top of each other - this area should be counted once (not three times) Example The image below contains thre rectangles: A,B,C A and B overlap (as indicated by dashes) B and C overlap (as indicated by dashes) What I am looking for is the area where the dashes are shown - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA--------------BBB AAAAAAAAAAAAAAAA--------------BBB AAAAAAAAAAAAAAAA--------------BBB AAAAAAAAAAAAAAAA--------------BBB BBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBB BBBBBB-----------CCCCCCCC BBBBBB-----------CCCCCCCC BBBBBB-----------CCCCCCCC CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCC
An efficient way of computing this area is to use a sweep algorithm. Let us assume that we sweep a vertical line L(x) through the union of rectangles U: first of all, you need to build an event queue Q, which is, in this case, the ordered list of all x-coordinates (left and right) of the rectangles. during the sweep, you should maintain a 1D datastructure, which should give you the total length of the intersection of L(x) and U. The important thing is that this length is constant between two consecutive events q and q' of Q. So, if l(q) denotes the total length of L(q+) (i.e. L just on the rightside of q) intersected with U, the area swept by L between events q and q' is exactly l(q)*(q' - q). you just have to sum up all these swept areas to get the total one. We still have to solve the 1D problem. You want a 1D structure, which computes dynamically a union of (vertical) segments. By dynamically, I mean that you sometimes add a new segment, and sometimes remove one. I already detailed in my answer to this collapsing ranges question how to do it in a static way (which is in fact a 1D sweep). So if you want something simple, you can directly apply that (by recomputing the union for each event). If you want something more efficient, you just need to adapt it a bit: assuming that you know the union of segments S1...Sn consists of disjoints segments D1...Dk. Adding Sn+1 is very easy, you just have to locate both ends of Sn+1 amongs the ends of D1...Dk. assuming that you know the union of segments S1...Sn consists of disjoints segments D1...Dk, removing segment Si (assuming that Si was included in Dj) means recomputing the union of segments that Dj consisted of, except Si (using the static algorithm). This is your dynamic algorithm. Assuming that you will use sorted sets with log-time location queries to represent D1...Dk, this is probably the most efficient non-specialized method you can get.
One way-out approach is to plot it to a canvas! Draw each rectangle using a semi-transparent colour. The .NET runtime will be doing the drawing in optimised, native code - or even using a hardware accelerator. Then, you have to read-back the pixels. Is each pixel the background colour, the rectangle colour, or another colour? The only way it can be another colour is if two or more rectangles overlapped... If this is too much of a cheat, I'd recommend the quad-tree as another answerer did, or the r-tree.
The simplest solution import numpy as np A = np.zeros((100, 100)) B = np.zeros((100, 100)) A[rect1.top : rect1.bottom, rect1.left : rect1.right] = 1 B[rect2.top : rect2.bottom, rect2.left : rect2.right] = 1 area_of_union = np.sum((A + B) > 0) area_of_intersect = np.sum((A + B) > 1) In this example, we create two zero-matrices that are the size of the canvas. For each rectangle, fill one of these matrices with ones where the rectangle takes up space. Then sum the matrices. Now sum(A+B > 0) is the area of the union, and sum(A+B > 1) is the area of the overlap. This example can easily generalize to multiple rectangles.
This is some quick and dirty code that I used in the TopCoder SRM 160 Div 2. t = top b = botttom l = left r = right public class Rect { public int t, b, l, r; public Rect(int _l, int _b, int _r, int _t) { t = _t; b = _b; l = _l; r = _r; } public bool Intersects(Rect R) { return !(l > R.r || R.l > r || R.b > t || b > R.t); } public Rect Intersection(Rect R) { if(!this.Intersects(R)) return new Rect(0,0,0,0); int [] horiz = {l, r, R.l, R.r}; Array.Sort(horiz); int [] vert = {b, t, R.b, R.t}; Array.Sort(vert); return new Rect(horiz[1], vert[1], horiz[2], vert[2]); } public int Area() { return (t - b)*(r-l); } public override string ToString() { return l + " " + b + " " + r + " " + t; } }
Here's something that off the top of my head sounds like it might work: Create a dictionary with a double key, and a list of rectangle+boolean values, like this: Dictionary< Double, List< KeyValuePair< Rectangle, Boolean>>> rectangles; For each rectangle in your set, find the corresponding list for the x0 and the x1 values, and add the rectangle to that list, with a boolean value of true for x0, and false for x1. This way you now have a complete list of all the x-coordinates that each rectangle either enters (true) or leaves (false) the x-direction Grab all the keys from that dictionary (all the distinct x-coordinates), sort them, and loop through them in order, make sure you can get at both the current x-value, and the next one as well (you need them both). This gives you individual strips of rectangles Maintain a set of rectangles you're currently looking at, which starts out empty. For each x-value you iterate over in point 3, if the rectangle is registered with a true value, add it to the set, otherwise remove it. For a strip, sort the rectangles by their y-coordinate Loop through the rectangles in the strip, counting overlapping distances (unclear to me as of yet how to do this efficiently) Calculate width of strip times height of overlapping distances to get areas Example, 5 rectangles, draw on top of each other, from a to e: aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbb aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbb aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbb aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbb aaaaaaaadddddddddddddddddddddddddddddbbbbbb aaaaaaaadddddddddddddddddddddddddddddbbbbbb ddddddddddddddddddddddddddddd ddddddddddddddddddddddddddddd ddddddddddddddeeeeeeeeeeeeeeeeee ddddddddddddddeeeeeeeeeeeeeeeeee ddddddddddddddeeeeeeeeeeeeeeeeee ccccccccddddddddddddddeeeeeeeeeeeeeeeeee ccccccccddddddddddddddeeeeeeeeeeeeeeeeee cccccccccccc eeeeeeeeeeeeeeeeee cccccccccccc eeeeeeeeeeeeeeeeee cccccccccccc cccccccccccc Here's the list of x-coordinates: v v v v v v v v v |aaaaaaa|aa|aaaa | bbbbbbbbbb|bb|bbb |aaaaaaa|aa|aaaa | bbbbbbbbbb|bb|bbb |aaaaaaa|aa|aaaa | bbbbbbbbbb|bb|bbb |aaaaaaa|aa|aaaa | bbbbbbbbbb|bb|bbb |aaaaaaaddd|dddddddddd|ddddddddddddddbb|bbb |aaaaaaaddd|dddddddddd|ddddddddddddddbb|bbb | ddd|dddddddddd|dddddddddddddd | | ddd|dddddddddd|dddddddddddddd | | ddd|ddddddddddeeeeeeeeeeeeeeeeee | ddd|ddddddddddeeeeeeeeeeeeeeeeee | ddd|ddddddddddeeeeeeeeeeeeeeeeee ccccccccddd|ddddddddddeeeeeeeeeeeeeeeeee ccccccccddd|ddddddddddeeeeeeeeeeeeeeeeee cccccccccccc eeeeeeeeeeeeeeeeee cccccccccccc eeeeeeeeeeeeeeeeee cccccccccccc cccccccccccc The list would be (where each v is simply given a coordinate starting at 0 and going up): 0: +a, +c 1: +d 2: -c 3: -a 4: +e 5: +b 6: -d 7: -e 8: -b Each strip would thus be (rectangles sorted from top to bottom): 0-1: a, c 1-2: a, d, c 2-3: a, d 3-4: d 4-5: d, e 5-6: b, d, e 6-7: b, e 7-8: b for each strip, the overlap would be: 0-1: none 1-2: a/d, d/c 2-3: a/d 3-4: none 4-5: d/e 5-6: b/d, d/e 6-7: none 7-8: none I'd imagine that a variation of the sort + enter/leave algorithm for the top-bottom check would be doable as well: sort the rectangles we're currently analyzing in the strip, top to bottom, for rectangles with the same top-coordinate, sort them by bottom coordinate as well iterate through the y-coordinates, and when you enter a rectangle, add it to the set, when you leave a rectangle, remove it from the set whenever the set has more than one rectangle, you have overlap (and if you make sure to add/remove all rectangles that have the same top/bottom coordinate you're currently looking at, multiple overlapping rectangles would not be a problem For the 1-2 strip above, you would iterate like this: 0. empty set, zero sum 1. enter a, add a to set (1 rectangle in set) 2. enter d, add d to set (>1 rectangles in set = overlap, store this y-coordinate) 3. leave a, remove a from set (now back from >1 rectangles in set, add to sum: y - stored_y 4. enter c, add c to set (>1 rectangles in set = overlap, store this y-coordinate) 5. leave d, remove d from set (now back from >1 rectangles in set, add to sum: y - stored_y) 6. multiply sum with width of strip to get overlapping areas You would not actually have to maintain an actual set here either, just the count of the rectangles you're inside, whenever this goes from 1 to 2, store the y, and whenever it goes from 2 down to 1, calculate current y - stored y, and sum this difference. Hope this was understandable, and as I said, this is off the top of my head, not tested in any way.
Using the example: 1 2 3 4 5 6 1 +---+---+ | | 2 + A +---+---+ | | B | 3 + + +---+---+ | | | | | 4 +---+---+---+---+ + | | 5 + C + | | 6 +---+---+ 1) collect all the x coordinates (both left and right) into a list, then sort it and remove duplicates 1 3 4 5 6 2) collect all the y coordinates (both top and bottom) into a list, then sort it and remove duplicates 1 2 3 4 6 3) create a 2D array by number of gaps between the unique x coordinates * number of gaps between the unique y coordinates. 4 * 4 4) paint all the rectangles into this grid, incrementing the count of each cell it occurs over: 1 3 4 5 6 1 +---+ | 1 | 0 0 0 2 +---+---+---+ | 1 | 1 | 1 | 0 3 +---+---+---+---+ | 1 | 1 | 2 | 1 | 4 +---+---+---+---+ 0 0 | 1 | 1 | 6 +---+---+ 5) the sum total of the areas of the cells in the grid that have a count greater than one is the area of overlap. For better efficiency in sparse use-cases, you can actually keep a running total of the area as you paint the rectangles, each time you move a cell from 1 to 2. In the question, the rectangles are described as being four doubles. Doubles typically contain rounding errors, and error might creep into your computed area of overlap. If the legal coordinates are at finite points, consider using an integer representation. PS using the hardware accelerator as in my other answer is not such a shabby idea, if the resolution is acceptable. Its also easy to implement in a lot less code than the approach I outline above. Horses for courses.
Here's the code I wrote for the area sweep algorithm: #include <iostream> #include <vector> using namespace std; class Rectangle { public: int x[2], y[2]; Rectangle(int x1, int y1, int x2, int y2) { x[0] = x1; y[0] = y1; x[1] = x2; y[1] = y2; }; void print(void) { cout << "Rect: " << x[0] << " " << y[0] << " " << x[1] << " " << y[1] << " " <<endl; }; }; // return the iterator of rec in list vector<Rectangle *>::iterator bin_search(vector<Rectangle *> &list, int begin, int end, Rectangle *rec) { cout << begin << " " <<end <<endl; int mid = (begin+end)/2; if (list[mid]->y[0] == rec->y[0]) { if (list[mid]->y[1] == rec->y[1]) return list.begin() + mid; else if (list[mid]->y[1] < rec->y[1]) { if (mid == end) return list.begin() + mid+1; return bin_search(list,mid+1,mid,rec); } else { if (mid == begin) return list.begin()+mid; return bin_search(list,begin,mid-1,rec); } } else if (list[mid]->y[0] < rec->y[0]) { if (mid == end) { return list.begin() + mid+1; } return bin_search(list, mid+1, end, rec); } else { if (mid == begin) { return list.begin() + mid; } return bin_search(list, begin, mid-1, rec); } } // add rect to rects void add_rec(Rectangle *rect, vector<Rectangle *> &rects) { if (rects.size() == 0) { rects.push_back(rect); } else { vector<Rectangle *>::iterator it = bin_search(rects, 0, rects.size()-1, rect); rects.insert(it, rect); } } // remove rec from rets void remove_rec(Rectangle *rect, vector<Rectangle *> &rects) { vector<Rectangle *>::iterator it = bin_search(rects, 0, rects.size()-1, rect); rects.erase(it); } // calculate the total vertical length covered by rectangles in the active set int vert_dist(vector<Rectangle *> as) { int n = as.size(); int totallength = 0; int start, end; int i = 0; while (i < n) { start = as[i]->y[0]; end = as[i]->y[1]; while (i < n && as[i]->y[0] <= end) { if (as[i]->y[1] > end) { end = as[i]->y[1]; } i++; } totallength += end-start; } return totallength; } bool mycomp1(Rectangle* a, Rectangle* b) { return (a->x[0] < b->x[0]); } bool mycomp2(Rectangle* a, Rectangle* b) { return (a->x[1] < b->x[1]); } int findarea(vector<Rectangle *> rects) { vector<Rectangle *> start = rects; vector<Rectangle *> end = rects; sort(start.begin(), start.end(), mycomp1); sort(end.begin(), end.end(), mycomp2); // active set vector<Rectangle *> as; int n = rects.size(); int totalarea = 0; int current = start[0]->x[0]; int next; int i = 0, j = 0; // big loop while (j < n) { cout << "loop---------------"<<endl; // add all recs that start at current while (i < n && start[i]->x[0] == current) { cout << "add" <<endl; // add start[i] to AS add_rec(start[i], as); cout << "after" <<endl; i++; } // remove all recs that end at current while (j < n && end[j]->x[1] == current) { cout << "remove" <<endl; // remove end[j] from AS remove_rec(end[j], as); cout << "after" <<endl; j++; } // find next event x if (i < n && j < n) { if (start[i]->x[0] <= end[j]->x[1]) { next = start[i]->x[0]; } else { next = end[j]->x[1]; } } else if (j < n) { next = end[j]->x[1]; } // distance to next event int horiz = next - current; cout << "horiz: " << horiz <<endl; // figure out vertical dist int vert = vert_dist(as); cout << "vert: " << vert <<endl; totalarea += vert * horiz; current = next; } return totalarea; } int main() { vector<Rectangle *> rects; rects.push_back(new Rectangle(0,0,1,1)); rects.push_back(new Rectangle(1,0,2,3)); rects.push_back(new Rectangle(0,0,3,3)); rects.push_back(new Rectangle(1,0,5,1)); cout << findarea(rects) <<endl; }
You can simplify this problem quite a bit if you split each rectangle into smaller rectangles. Collect all of the X and Y coordinates of all the rectangles, and these become your split points - if a rectangle crosses the line, split it in two. When you're done, you have a list of rectangles that overlap either 0% or 100%, if you sort them it should be easy to find the identical ones.
There is a solution listed at the link http://codercareer.blogspot.com/2011/12/no-27-area-of-rectangles.html for finding the total area of multiple rectangles such that the overlapped area is counted only once. The above solution can be extended to compute only the overlapped area(and that too only once even if the overlapped area is covered by multiple rectangles) with horizontal sweep lines for every pair of consecutive vertical sweep lines. If aim is just to find out the total area covered by the all the rectangles, then horizontal sweep lines are not needed and just a merge of all the rectangles between two vertical sweep lines would give the area. On the other hand, if you want to compute the overlapped area only, the horizontal sweep lines are needed to find out how many rectangles are overlapping in between vertical (y1, y2) sweep lines. Here is the working code for the solution I implemented in Java. import java.io.*; import java.util.*; class Solution { static class Rectangle{ int x; int y; int dx; int dy; Rectangle(int x, int y, int dx, int dy){ this.x = x; this.y = y; this.dx = dx; this.dy = dy; } Range getBottomLeft(){ return new Range(x, y); } Range getTopRight(){ return new Range(x + dx, y + dy); } #Override public int hashCode(){ return (x+y+dx+dy)/4; } #Override public boolean equals(Object other){ Rectangle o = (Rectangle) other; return o.x == this.x && o.y == this.y && o.dx == this.dx && o.dy == this.dy; } #Override public String toString(){ return String.format("X = %d, Y = %d, dx : %d, dy : %d", x, y, dx, dy); } } static class RW{ Rectangle r; boolean start; RW (Rectangle r, boolean start){ this.r = r; this.start = start; } #Override public int hashCode(){ return r.hashCode() + (start ? 1 : 0); } #Override public boolean equals(Object other){ RW o = (RW)other; return o.start == this.start && o.r.equals(this.r); } #Override public String toString(){ return "Rectangle : " + r.toString() + ", start = " + this.start; } } static class Range{ int l; int u; public Range(int l, int u){ this.l = l; this.u = u; } #Override public int hashCode(){ return (l+u)/2; } #Override public boolean equals(Object other){ Range o = (Range) other; return o.l == this.l && o.u == this.u; } #Override public String toString(){ return String.format("L = %d, U = %d", l, u); } } static class XComp implements Comparator<RW>{ #Override public int compare(RW rw1, RW rw2){ //TODO : revisit these values. Integer x1 = -1; Integer x2 = -1; if(rw1.start){ x1 = rw1.r.x; }else{ x1 = rw1.r.x + rw1.r.dx; } if(rw2.start){ x2 = rw2.r.x; }else{ x2 = rw2.r.x + rw2.r.dx; } return x1.compareTo(x2); } } static class YComp implements Comparator<RW>{ #Override public int compare(RW rw1, RW rw2){ //TODO : revisit these values. Integer y1 = -1; Integer y2 = -1; if(rw1.start){ y1 = rw1.r.y; }else{ y1 = rw1.r.y + rw1.r.dy; } if(rw2.start){ y2 = rw2.r.y; }else{ y2 = rw2.r.y + rw2.r.dy; } return y1.compareTo(y2); } } public static void main(String []args){ Rectangle [] rects = new Rectangle[4]; rects[0] = new Rectangle(10, 10, 10, 10); rects[1] = new Rectangle(15, 10, 10, 10); rects[2] = new Rectangle(20, 10, 10, 10); rects[3] = new Rectangle(25, 10, 10, 10); int totalArea = getArea(rects, false); System.out.println("Total Area : " + totalArea); int overlapArea = getArea(rects, true); System.out.println("Overlap Area : " + overlapArea); } static int getArea(Rectangle []rects, boolean overlapOrTotal){ printArr(rects); // step 1: create two wrappers for every rectangle RW []rws = getWrappers(rects); printArr(rws); // steps 2 : sort rectangles by their x-coordinates Arrays.sort(rws, new XComp()); printArr(rws); // step 3 : group the rectangles in every range. Map<Range, List<Rectangle>> rangeGroups = groupRects(rws, true); for(Range xrange : rangeGroups.keySet()){ List<Rectangle> xRangeRects = rangeGroups.get(xrange); System.out.println("Range : " + xrange); System.out.println("Rectangles : "); for(Rectangle rectx : xRangeRects){ System.out.println("\t" + rectx); } } // step 4 : iterate through each of the pairs and their rectangles int sum = 0; for(Range range : rangeGroups.keySet()){ List<Rectangle> rangeRects = rangeGroups.get(range); sum += getOverlapOrTotalArea(rangeRects, range, overlapOrTotal); } return sum; } static Map<Range, List<Rectangle>> groupRects(RW []rws, boolean isX){ //group the rws with either x or y coordinates. Map<Range, List<Rectangle>> rangeGroups = new HashMap<Range, List<Rectangle>>(); List<Rectangle> rangeRects = new ArrayList<Rectangle>(); int i=0; int prev = Integer.MAX_VALUE; while(i < rws.length){ int curr = isX ? (rws[i].start ? rws[i].r.x : rws[i].r.x + rws[i].r.dx): (rws[i].start ? rws[i].r.y : rws[i].r.y + rws[i].r.dy); if(prev < curr){ Range nRange = new Range(prev, curr); rangeGroups.put(nRange, rangeRects); rangeRects = new ArrayList<Rectangle>(rangeRects); } prev = curr; if(rws[i].start){ rangeRects.add(rws[i].r); }else{ rangeRects.remove(rws[i].r); } i++; } return rangeGroups; } static int getOverlapOrTotalArea(List<Rectangle> rangeRects, Range range, boolean isOverlap){ //create horizontal sweep lines similar to vertical ones created above // Step 1 : create wrappers again RW []rws = getWrappers(rangeRects); // steps 2 : sort rectangles by their y-coordinates Arrays.sort(rws, new YComp()); // step 3 : group the rectangles in every range. Map<Range, List<Rectangle>> yRangeGroups = groupRects(rws, false); //step 4 : for every range if there are more than one rectangles then computer their area only once. int sum = 0; for(Range yRange : yRangeGroups.keySet()){ List<Rectangle> yRangeRects = yRangeGroups.get(yRange); if(isOverlap){ if(yRangeRects.size() > 1){ sum += getArea(range, yRange); } }else{ if(yRangeRects.size() > 0){ sum += getArea(range, yRange); } } } return sum; } static int getArea(Range r1, Range r2){ return (r2.u-r2.l)*(r1.u-r1.l); } static RW[] getWrappers(Rectangle []rects){ RW[] wrappers = new RW[rects.length * 2]; for(int i=0,j=0;i<rects.length;i++, j+=2){ wrappers[j] = new RW(rects[i], true); wrappers[j+1] = new RW(rects[i], false); } return wrappers; } static RW[] getWrappers(List<Rectangle> rects){ RW[] wrappers = new RW[rects.size() * 2]; for(int i=0,j=0;i<rects.size();i++, j+=2){ wrappers[j] = new RW(rects.get(i), true); wrappers[j+1] = new RW(rects.get(i), false); } return wrappers; } static void printArr(Object []a){ for(int i=0; i < a.length;i++){ System.out.println(a[i]); } System.out.println(); }
The following answer should give the total Area only once. it comes previous answers, but implemented now in C#. It works also with floats (or double, if you need[it doesn't itterate over the VALUES). Credits: http://codercareer.blogspot.co.il/2011/12/no-27-area-of-rectangles.html EDIT: The OP asked for the overlapping area - thats obviously very simple: var totArea = rects.Sum(x => x.Width * x.Height); and then the answer is: var overlappingArea =totArea-GetArea(rects) Here is the code: #region rectangle overlapping /// <summary> /// see algorithm for detecting overlapping areas here: https://stackoverflow.com/a/245245/3225391 /// or easier here: /// http://codercareer.blogspot.co.il/2011/12/no-27-area-of-rectangles.html /// </summary> /// <param name="dim"></param> /// <returns></returns> public static float GetArea(RectangleF[] rects) { List<float> xs = new List<float>(); foreach (var item in rects) { xs.Add(item.X); xs.Add(item.Right); } xs = xs.OrderBy(x => x).Cast<float>().ToList(); rects = rects.OrderBy(rec => rec.X).Cast<RectangleF>().ToArray(); float area = 0f; for (int i = 0; i < xs.Count - 1; i++) { if (xs[i] == xs[i + 1])//not duplicate continue; int j = 0; while (rects[j].Right < xs[i]) j++; List<Range> rangesOfY = new List<Range>(); var rangeX = new Range(xs[i], xs[i + 1]); GetRangesOfY(rects, j, rangeX, out rangesOfY); area += GetRectArea(rangeX, rangesOfY); } return area; } private static void GetRangesOfY(RectangleF[] rects, int rectIdx, Range rangeX, out List<Range> rangesOfY) { rangesOfY = new List<Range>(); for (int j = rectIdx; j < rects.Length; j++) { if (rangeX.less < rects[j].Right && rangeX.greater > rects[j].Left) { rangesOfY = Range.AddRange(rangesOfY, new Range(rects[j].Top, rects[j].Bottom)); #if DEBUG Range rectXRange = new Range(rects[j].Left, rects[j].Right); #endif } } } static float GetRectArea(Range rangeX, List<Range> rangesOfY) { float width = rangeX.greater - rangeX.less, area = 0; foreach (var item in rangesOfY) { float height = item.greater - item.less; area += width * height; } return area; } internal class Range { internal static List<Range> AddRange(List<Range> lst, Range rng2add) { if (lst.isNullOrEmpty()) { return new List<Range>() { rng2add }; } for (int i = lst.Count - 1; i >= 0; i--) { var item = lst[i]; if (item.IsOverlapping(rng2add)) { rng2add.Merge(item); lst.Remove(item); } } lst.Add(rng2add); return lst; } internal float greater, less; public override string ToString() { return $"ln{less} gtn{greater}"; } internal Range(float less, float greater) { this.less = less; this.greater = greater; } private void Merge(Range rng2add) { this.less = Math.Min(rng2add.less, this.less); this.greater = Math.Max(rng2add.greater, this.greater); } private bool IsOverlapping(Range rng2add) { return !(less > rng2add.greater || rng2add.less > greater); //return // this.greater < rng2add.greater && this.greater > rng2add.less // || this.less > rng2add.less && this.less < rng2add.greater // || rng2add.greater < this.greater && rng2add.greater > this.less // || rng2add.less > this.less && rng2add.less < this.greater; } } #endregion rectangle overlapping
If your rectangles are going to be sparse (mostly not intersecting) then it might be worth a look at recursive dimensional clustering. Otherwise a quad-tree seems to be the way to go (as has been mentioned by other posters. This is a common problem in collision detection in computer games, so there is no shortage of resources suggesting ways to solve it. Here is a nice blog post summarizing RCD. Here is a Dr.Dobbs article summarizing various collision detection algorithms, which would be suitable.
This type of collision detection is often called AABB (Axis Aligned Bounding Boxes), that's a good starting point for a google search.
You can find the overlap on the x and on the y axis and multiply those. int LineOverlap(int line1a, line1b, line2a, line2b) { // assume line1a <= line1b and line2a <= line2b if (line1a < line2a) { if (line1b > line2b) return line2b-line2a; else if (line1b > line2a) return line1b-line2a; else return 0; } else if (line2a < line1b) return line2b-line1a; else return 0; } int RectangleOverlap(Rect rectA, rectB) { return LineOverlap(rectA.x1, rectA.x2, rectB.x1, rectB.x2) * LineOverlap(rectA.y1, rectA.y2, rectB.y1, rectB.y2); }
I found a different solution than the sweep algorithm. Since your rectangles are all rectangular placed, the horizontal and vertical lines of the rectangles will form a rectangular irregular grid. You can 'paint' the rectangles on this grid; which means, you can determine which fields of the grid will be filled out. Since the grid lines are formed from the boundaries of the given rectangles, a field in this grid will always either completely empty or completely filled by an rectangle. I had to solve the problem in Java, so here's my solution: http://pastebin.com/03mss8yf This function calculates of the complete area occupied by the rectangles. If you are interested only in the 'overlapping' part, you must extend the code block between lines 70 and 72. Maybe you can use a second set to store which grid fields are used more than once. Your code between line 70 and 72 should be replaced with a block like: GridLocation gl = new GridLocation(curX, curY); if(usedLocations.contains(gl) && usedLocations2.add(gl)) { ret += width*height; } else { usedLocations.add(gl); } The variable usedLocations2 here is of the same type as usedLocations; it will be constructed at the same point. I'm not really familiar with complexity calculations; so I don't know which of the two solutions (sweep or my grid solution) will perform/scale better.
Considering we have two rectangles (A and B) and we have their bottom left (x1,y1) and top right (x2,y2) coordination. The Using following piece of code you can calculate the overlapped area in C++. #include <iostream> using namespace std; int rectoverlap (int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2) { int width, heigh, area; if (ax2<bx1 || ay2<by1 || ax1>bx2 || ay1>by2) { cout << "Rectangles are not overlapped" << endl; return 0; } if (ax2>=bx2 && bx1>=ax1){ width=bx2-bx1; heigh=by2-by1; } else if (bx2>=ax2 && ax1>=bx1) { width=ax2-ax1; heigh=ay2-ay1; } else { if (ax2>bx2){ width=bx2-ax1; } else { width=ax2-bx1; } if (ay2>by2){ heigh=by2-ay1; } else { heigh=ay2-by1; } } area= heigh*width; return (area); } int main() { int ax1,ay1,ax2,ay2,bx1,by1,bx2,by2; cout << "Inter the x value for bottom left for rectangle A" << endl; cin >> ax1; cout << "Inter the y value for bottom left for rectangle A" << endl; cin >> ay1; cout << "Inter the x value for top right for rectangle A" << endl; cin >> ax2; cout << "Inter the y value for top right for rectangle A" << endl; cin >> ay2; cout << "Inter the x value for bottom left for rectangle B" << endl; cin >> bx1; cout << "Inter the y value for bottom left for rectangle B" << endl; cin >> by1; cout << "Inter the x value for top right for rectangle B" << endl; cin >> bx2; cout << "Inter the y value for top right for rectangle B" << endl; cin >> by2; cout << "The overlapped area is " << rectoverlap (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) << endl; }
The post by user3048546 contains an error in the logic on lines 12-17. Here is a working implementation: int rectoverlap (int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2) { int width, height, area; if (ax2<bx1 || ay2<by1 || ax1>bx2 || ay1>by2) { cout << "Rectangles are not overlapped" << endl; return 0; } if (ax2>=bx2 && bx1>=ax1){ width=bx2-bx1; } else if (bx2>=ax2 && ax1>=bx1) { width=ax2-ax1; } else if (ax2>bx2) { width=bx2-ax1; } else { width=ax2-bx1; } if (ay2>=by2 && by1>=ay1){ height=by2-by1; } else if (by2>=ay2 && ay1>=by1) { height=ay2-ay1; } else if (ay2>by2) { height=by2-ay1; } else { height=ay2-by1; } area = heigh*width; return (area); }