61

I have two rectangles a and b with their sides parallel to the axes of the coordinate system. I have their co-ordinates as x1,y1,x2,y2.

I'm trying to determine, not only do they overlap, but HOW MUCH do they overlap? I'm trying to figure out if they're really the same rectangle give or take a bit of wiggle room. So is their area 95% the same?

Any help in calculating the % of overlap?

Patrick Collins
  • 4,046
  • 3
  • 26
  • 29
  • See also: [Calculate IoU for AABBs](http://stackoverflow.com/a/42874377/562769) – Martin Thoma Mar 19 '17 at 10:23
  • https://www.ultraimg.com/image/Ou38 : This picture provides test cases. So far I have only identified these 8 cases.Rectagle B can be, relative to rectangle A, at top right, bottom right, top left, bottom left. They can also either intersect or not intersect. All other cases (like two edges of A and B have a common segment, two vertices of A and B overlap) are just particular cases of the above cases, where two coordinates (like A.bottom and B.top), instead of being strictly greater, are greater or equal. – Newton fan 01 Aug 10 '18 at 13:20
  • I have reconsidered this problem, and found a total of 36 cases. Explanation here: https://www.ultraimg.com/image/Ooyk . Before that, the cases like rectangle B is totally inside rectangle A (or rectangle A totally inside rectangle A) were not covered. Now they are counted too. – Newton fan 01 Aug 24 '18 at 09:47

9 Answers9

86

Compute the area of the intersection, which is a rectangle too:

SI = Max(0, Min(XA2, XB2) - Max(XA1, XB1)) * Max(0, Min(YA2, YB2) - Max(YA1, YB1))

From there you compute the area of the union:

SU = SA + SB - SI

And you can consider the ratio

SI / SU

(100% in case of a perfect overlap, down to 0%).

Patrick Collins
  • 4,046
  • 3
  • 26
  • 29
  • 1
    wow. That is exactly what I was after thank you! I wasn't thinking about it correctly. The introduction of the union concept is what I was missing. Thanks!. – Patrick Collins Feb 19 '12 at 04:59
  • Is SA and SB the area of A and B? And for SI, the more i move my rectangle to the right bottom the higher the values get. – clankill3r Apr 28 '14 at 16:33
  • 1
    Give a numerical example. –  Apr 28 '14 at 19:08
  • @YvesDaoust I had to translate those undocumented variables for my own use, so I figured I would share what I got. For class BoundingBox, where UpRt is a Point and LowLt is a point, the equation (uncompiled) is Area = Max(0, Max(this.UpRt.x, other.UpRt.x) - Min(this.LowLt.x, other.LowLt.x)) * Max(0, Max(this.UpRt.y, other.UpRt.y) - Min(this.LowLt.y, other.LowLt.y)); I have not attempted to compile, run, or test this because I did not need the area, just a boolean on whether the two overlap, so what I actually am using in my own code is a little more efficient. – philologon Jul 01 '14 at 00:03
  • 28
    It'd be so much better if he labeled what the variables are. – Louis Hong Jun 01 '15 at 00:24
  • In case anyone was unsure, this formula does work for when one rectangle completely contains the other. Some of the other answers don't work for that. – Vic Apr 15 '20 at 05:09
  • 10
    For others, the variables assume `A` is rect A and `B` is rect B; `X` is the X dimension and `Y` is the Y dimension; and `1` is the (top or left) point while `2` is the (bottom or right) point. Therefore: `XA2` is the right side `X` value of rect `A`; `XB2` is the right side `X` value of rect `B`; `XA1` is the left side `X` value of rect A; `XB1` is the left side `X` value of rect B; `YA2` is the bottom `Y` value of rect `A`; `YB2` is the bottom `Y` value of rect `B`; `YA1` is the top `Y` value of rect `A`; and `YB1` is the top `Y` value of rect B. Finally, `SI` is the area of intersection. – Dan Nissenbaum Jun 13 '20 at 04:07
50

While the accepted answer is correct, I think it's worth exploring this answer in a way that will make the rationale for the answer completely obvious. This is too common an algorithm to have an incomplete (or worse, controversial) answer. Furthermore, with only a passing glance at the given formula, you may miss the beauty and extensibility of the algorithm, and the implicit decisions that are being made.

We're going to build our way up to making these formulas intuitive:

intersecting_area = 
  max(0, 
      min(orange.circle.x, blue.circle.x) 
      - max(orange.triangle.x, blue.triangle.x)
    ) 
  * max(0, 
      min(orange.circle.y, blue.circle.y) 
      - max(orange.triangle.y, blue.triangle.y)
    )
percent_coverage = intersecting_area 
   / (orange_area + blue_area - intersecting_area)

First, consider one way to define a two dimensional box is with:

  • (x, y) for the top left point
  • (x, y) for the bottom right point

This might look like:

Example Rectangle

I indicate the top left with a triangle and the bottom right with a circle. This is to avoid opaque syntax like x1, x2 for this example.

Two overlapping rectangles might look like this:

Two Rectangles

Notice that to find the overlap you're looking for the place where the orange and the blue collide:

Rectangle Overlap

Once you recognize this, it becomes obvious that overlap is the result of finding and multiplying these two darkened lines:

Defining Overlap

The length of each line is the minimum value of the two circle points, minus the maximum value of the two triangle points.

Here, I'm using a two-toned triangle (and circle) to show that the orange and the blue points are compared with each other. The small letter 'y' after the two-toned triangle indicates that the triangles are compared along the y axis, the small 'x' means they are compared along the x axis.

Finding Overlap

For example, to find the length of the darkened blue line you can see the triangles are compared to look for the maximum value between the two. The attribute that is compared is the x attribute. The maximum x value between the triangles is 210.

Another way to say the same thing is: The length of the new line that fits onto both the orange and blue lines is found by subtracting the furthest point on the closest side of the line from the closest point on the furthest side of the line.

Showing Overlap

Finding those lines gives complete information about the overlapping areas.

Demonstrate The Calculation

Once you have this, finding the percentage of overlap is trivial:

Find Percent of Overlap

But wait, if the orange rectangle does not overlap with the blue one then you're going to have a problem:

Final Note: Broken Example

With this example, you get a -850 for our overlapping area, that can't be right. Even worse, if a detection doesn't overlap with either dimension (neither on the x or y axis) then you will still get a positive number because both dimensions are negative. This is why you see the Max(0, ...) * Max(0, ...) as part of the solution; it ensures that if any of the overlaps are negative you'll get a 0 back from your function.

The final formula in keeping with our symbology:

The Formula

It's worth noting that using the max(0, ...) function may not be necessary. You may want to know if something overlaps along one of its dimensions rather than all of them; if you use max then you will obliterate that information. For that reason, consider how you want to deal with non-overlapping bounding boxes. Normally, the max function is fine to use, but it's worth being aware what it's doing.

Finally, notice that since this comparison is only concerned with linear measurements it can be scaled to arbitrary dimensions or arbitrary overlapping quadrilaterals.

To summarize:

intersecting_area = 
  max(0, 
      min(orange.circle.x, blue.circle.x) 
      - max(orange.triangle.x, blue.triangle.x)
    ) 
  * max(0, 
      min(orange.circle.y, blue.circle.y) 
      - max(orange.triangle.y, blue.triangle.y)
    )
percent_coverage = intersecting_area 
   / (orange_area + blue_area - intersecting_area)
Connor
  • 4,216
  • 2
  • 29
  • 40
  • thanks for the nice explanation. What if the bounding box is inside another bounding box? – sam Nov 27 '18 at 07:50
  • 2
    @prb take this equation: `max(0, min(orange.circle.x, blue.circle.x) - max(orange.triangle.x, blue.triangle.x)) * max(0, min(orange.circle.y, blue.circle.y) - max(orange.triangle.y, blue.triangle.y))` and put in numbers so that all the orange triangles are bigger than the blue triangles (but less than the blue circles) and all the orange circles are less than the blue circles (but more than the blue triangles). Report your findings – Connor Nov 27 '18 at 16:51
  • Is there a way we can do it for multiple bounding boxes? – sam Nov 28 '18 at 09:08
  • @prb To scale the number of rectangles increase the number of colors for each shape comparison. E.g. With a third, green rectangle you would look for `max(0, min(orange.circle.x, blue.circle.x, green.circle.x) - max(...`. – Connor Nov 28 '18 at 15:58
  • 1
    what a solution! – Luan Pham Dec 14 '21 at 02:30
9

I recently ran into this problem as well and applied Yves' answer, but somehow that led to the wrong area size, so I rewrote it.

Assuming two rectangles A and B, find out how much they overlap and if so, return the area size:

IF A.right < B.left OR A.left > B.right
    OR A.bottom < B.top OR A.top > B.bottom THEN RETURN 0

width := IF A.right > B.right THEN B.right - A.left ELSE A.right - B.left
height := IF A.bottom > B.bottom THEN B.bottom - A.top ELSE A.bottom - B.top

RETURN width * height
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
6

Just fixing previous answers so that the ratio is between 0 and 1 (using Python):

    # (x1,y1) top-left coord, (x2,y2) bottom-right coord, (w,h) size
    A = {'x1': 0, 'y1': 0, 'x2': 99, 'y2': 99, 'w': 100, 'h': 100}
    B = {'x1': 0, 'y1': 0, 'x2': 49, 'y2': 49, 'w':  50, 'h':  50}

    # overlap between A and B
    SA = A['w']*A['h']
    SB = B['w']*B['h']
    SI = np.max([ 0, 1 + np.min([A['x2'],B['x2']]) - np.max([A['x1'],B['x1']]) ]) * np.max([ 0, 1 + np.min([A['y2'],B['y2']]) - np.max([A['y1'],B['y1']]) ])
    SU = SA + SB - SI
    overlap_AB = float(SI) / float(SU)
    print 'overlap between A and B: %f' % overlap_AB

    # overlap between A and A
    B = A
    SB = B['w']*B['h']
    SI = np.max([ 0, 1 + np.min([A['x2'],B['x2']]) - np.max([A['x1'],B['x1']]) ]) * np.max([ 0, 1 + np.min([A['y2'],B['y2']]) - np.max([A['y1'],B['y1']]) ])
    SU = SA + SB - SI
    overlap_AA = float(SI) / float(SU)
    print 'overlap between A and A: %f' % overlap_AA

The output will be:

    overlap between A and B: 0.250000
    overlap between A and A: 1.000000
Alessio B
  • 353
  • 4
  • 9
4

Assuming that the rectangle must be parallel to x and y axis as that seems to be the situation from the previous comments and answers.

I cannot post comment yet, but I would like to point out that both previous answers seem to ignore the case when one side rectangle is totally within the side of the other rectangle. Please correct me if I am wrong.

Consider the case

a: (1,1), (4,4)
b: (2,2), (5,3)

In this case, we see that for the intersection, height must be bTop - bBottom because the vertical part of b is wholly contained in a.

We just need to add more cases as follows: (The code can be shorted if you treat top and bottom as the same thing as right and left, so that you do not need to duplicate the conditional chunk twice, but this should do.)

if aRight <= bLeft or bRight <= aLeft or aTop <= bBottom or bTop <= aBottom:
    # There is no intersection in these cases
    return 0
else:
    # There is some intersection

    if aRight >= bRight and aLeft <= bLeft:
        # From x axis point of view, b is wholly contained in a
        width = bRight - bLeft
    elif bRight >= aRight and bLeft <= aLeft:
        # From x axis point of view, a is wholly contained in b
        width = aRight - aLeft
    elif aRight >= bRight:
        width = bRight - aLeft
    else:
        width = aRight - bLeft

    if aTop >= bTop and aBottom <= bBottom:
        # From y axis point of view, b is wholly contained in a
        height = bTop - bBottom
    elif bTop >= aTop and bBottom <= aBottom:
        # From y axis point of view, a is wholly contained in b
        height = aTop - aBottom
    elif aTop >= bTop:
        height = bTop - aBottom
    else:
        height = aTop - bBottom

return width * height
Shar
  • 497
  • 1
  • 5
  • 8
3

Here is a working Function in C#:

    public double calculateOverlapPercentage(Rectangle A, Rectangle B)
    {
        double result = 0.0;
        //trivial cases
        if (!A.IntersectsWith(B)) return 0.0; 
        if (A.X == B.X && A.Y == B.Y && A.Width == B.Width && A.Height == B.Height) return 100.0;


        //# overlap between A and B
        double SA = A.Width * A.Height;
        double SB = B.Width * B.Height;
        double SI = Math.Max(0,  Math.Min(A.Right, B.Right) - Math.Max(A.Left, B.Left)) *
                    Math.Max(0, Math.Min(A.Bottom, B.Bottom) - Math.Max(A.Top, B.Top));
        double SU = SA + SB - SI;
        result = SI / SU; //ratio
        result *= 100.0; //percentage
        return result;
    }
Eric Ebert
  • 31
  • 1
2

@User3025064 is correct and is the simplest solution, though, exclusivity must be checked first for rectangles that do not intersect e.g., for rectangles A & B (in Visual Basic):

If A.Top =< B.Bottom or A.Bottom => B.Top or A.Right =< B.Left or A.Left => B.Right then
    Exit sub   'No intersection
else
    width = ABS(Min(XA2, XB2) - Max(XA1, XB1))
    height = ABS(Min(YA2, YB2) - Max(YA1, YB1))
    Area = width * height      'Total intersection area.
End if
cdmh
  • 3,294
  • 2
  • 26
  • 41
2
[ymin_a, xmin_a, ymax_a, xmax_a] = list(bbox_a)
[ymin_b, xmin_b, ymax_b, xmax_b] = list(bbox_b)

x_intersection = min(xmax_a, xmax_b) - max(xmin_a, xmin_b) + 1
y_intersection = min(ymax_a, ymax_b) - max(ymin_a, ymin_b) + 1

if x_intersection <= 0 or y_intersection <= 0:
    return 0
else:
    return x_intersection * y_intersection
Felix
  • 3,351
  • 6
  • 40
  • 68
1

The answer of @user3025064 is the right answer. The accepted answer inadvertently flips the inner MAX and MIN calls. We also don't need to check first if they intersect or not if we use the presented formula, MAX(0,x) as opposed to ABS(x). If they do not intersect, MAX(0,x) returns zero which makes the intersection area 0 (i.e. disjoint).

I suggest that @Yves Daoust fixes his answer because it is the accepted one that pops up to anyone who searches for that problem. Once again, here is the right formula for intersection:

SI = Max(0, Min(XA2, XB2) - Max(XA1, XB1)) * Max(0, Min(YA2, YB2) - Max(YA1, YB1))

The rest as usual. Union:

SU = SA + SB - SI

and ratio:

SI/SU

Hazem
  • 520
  • 5
  • 10
  • 1
    are you sure? I have updated the correct answer based on your advice, but 30 people have suggested Yves was the correct answer, so I'm hoping you can double check your assumption for me. thanks. – Patrick Collins Jun 12 '15 at 16:53
  • Try this counter example: Two rectangles, side by side that do not overlap, so `XA1 – Hazem Jun 14 '15 at 05:06