274

How to draw a rectangle on an image, like this: enter image description here

import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
im = np.array(Image.open('dog.png'), dtype=np.uint8)
plt.imshow(im)

I don't know how to proceed.

pr94
  • 1,263
  • 12
  • 24
Kai ZHAO
  • 3,088
  • 3
  • 15
  • 18

5 Answers5

468

You can add a Rectangle patch to the matplotlib Axes.

For example (using the image from the tutorial here):

import matplotlib.pyplot as plt
import matplotlib.patches as patches
from PIL import Image

im = Image.open('stinkbug.png')

# Create figure and axes
fig, ax = plt.subplots()

# Display the image
ax.imshow(im)

# Create a Rectangle patch
rect = patches.Rectangle((50, 100), 40, 30, linewidth=1, edgecolor='r', facecolor='none')

# Add the patch to the Axes
ax.add_patch(rect)

plt.show()

enter image description here

darthbith
  • 18,484
  • 9
  • 60
  • 76
tmdavison
  • 64,360
  • 12
  • 187
  • 165
  • 1
    Thanks for your answer! It works, but it seems that the rectangle is drawn on the axis, not the picture itself. If I try to save the image to a file, the rectangle won't be saved. Is there a way so that the rectangle replaces the pixel values on the image? Thanks again! – Yanfeng Liu Aug 08 '17 at 14:54
  • Never mind. I found this [link](http://docs.opencv.org/3.1.0/dc/da5/tutorial_py_drawing_functions.html) and it seems to be working : ) – Yanfeng Liu Aug 08 '17 at 15:01
  • If you are still getting filled rectangle, pass `fill=False` flag to `Rectangle` – Ivan Talalaev Oct 22 '17 at 09:52
  • 12
    This is weird. The documentation for `patches.Rectangle` says that the first two number are `The bottom and left rectangle coordinates`. I see here that the first two numbers, (50,100), correspond to the TOP and left coordinate of the rectangle. I'm confused. – Monica Heddneck Jun 23 '18 at 04:02
  • so then you got the rectangle displayed in the wrong place, because imshow flipped it? – Monica Heddneck Jun 25 '18 at 20:42
  • 6
    No, the rectangle is in the right place. It's in data coordinates. You can change the transform if you want it in axes coordinates – tmdavison Jun 25 '18 at 21:20
  • @tmdavison can you elaborate on data vs axes coordinates? For a numpy array, I know it's (rows, cols) with rows ascending moving "down" and to the "right" in the matrix. Also for `cv2.rectangle()` I know it's top left and bottom right as parameters so in this case, what would those two be? – mLstudent33 Sep 03 '19 at 11:16
  • @mLstudent33 axes coordinates always have the bottom left as `(0, 0)` and top right as `(1, 1)`. Usually in matplotlib data coordinates work in the same way (unless you manually change them to something different), with the lowest values on the x and y axes at the bottom left. `imshow` works a bit differently, and you will see the that lowest `y` value is in this case at the top, not the bottom. – tmdavison Sep 04 '19 at 11:40
  • @tmdavision, so in your call to `plt.Rectangle` the first argument is bottom left as Monica points out in the matplotlib docs. Then the y-axis is flipped for `plt.show`. Where does it say in the docs about flipping the axis and for what reason do they do that? – mLstudent33 Sep 04 '19 at 12:08
  • `plt.imshow` flips it, not `plt.show`. See the `origin` kwarg in the [`imshow` docs](https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.imshow.html). – tmdavison Sep 05 '19 at 10:52
  • @MonicaHeddneck I think what it means in the doc is that the point you pass to Rectangle() is the point that closest to the origin out of the four points of the rectangle. But since in image, the top left corner is the origin by convention, so this time the point you pass to the Rectangle() is the top left one – hzh Oct 15 '19 at 20:23
58

There is no need for subplots, and pyplot can display PIL images, so this can be simplified further:

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from PIL import Image

im = Image.open('stinkbug.png')

# Display the image
plt.imshow(im)

# Get the current reference
ax = plt.gca()

# Create a Rectangle patch
rect = Rectangle((50,100),40,30,linewidth=1,edgecolor='r',facecolor='none')

# Add the patch to the Axes
ax.add_patch(rect)

Or, the short version:

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from PIL import Image

# Display the image
plt.imshow(Image.open('stinkbug.png'))

# Add the patch to the Axes
plt.gca().add_patch(Rectangle((50,100),40,30,linewidth=1,edgecolor='r',facecolor='none'))
29

You need use patches.

import matplotlib.pyplot as plt
import matplotlib.patches as patches

fig2 = plt.figure()
ax2 = fig2.add_subplot(111, aspect='equal')

ax2.add_patch(
     patches.Rectangle(
        (0.1, 0.1),
        0.5,
        0.5,
        fill=False      # remove background
     ) ) 
fig2.savefig('rect2.png', dpi=90, bbox_inches='tight')
Monica Heddneck
  • 2,973
  • 10
  • 55
  • 89
Serenity
  • 35,289
  • 20
  • 120
  • 115
  • 4
    I liked how you encapsulated axes within a figure object: axes do the plotting, figure does high-level interface stuff – Alex Mar 11 '20 at 12:11
6

From my understanding matplotlib is a plotting library.

If you want to change the image data (e.g. draw a rectangle on an image), you could use PIL's ImageDraw, OpenCV, or something similar.

Here is PIL's ImageDraw method to draw a rectangle.

Here is one of OpenCV's methods for drawing a rectangle.

Your question asked about Matplotlib, but probably should have just asked about drawing a rectangle on an image.

Here is another question which addresses what I think you wanted to know: Draw a rectangle and a text in it using PIL

user3731622
  • 4,844
  • 8
  • 45
  • 84
2

If you have a set of coordinates of ordered points you can also use the plot function and plot them directly without using the Rect patch. Here I recreate the example proposed by @tmdavison using that:

import matplotlib.pyplot as plt
import matplotlib.patches as patches
from PIL import Image

im = Image.open('/content/stinkbug.png')

# Create figure and axes
fig, ax = plt.subplots()

# Display the image
ax.imshow(im)

# Coordinates of rectangle vertices
# in clockwise order
xs = [50, 90, 90, 50, 50]
ys = [100, 100, 130, 130, 100]
ax.plot(xs, ys, color="red")

plt.show()

stinkbug photo with red rectangle identifying a small zone

Aelius
  • 1,029
  • 11
  • 22