3

I have an image, for which I've extracted the contours using OpenCV and calculated their areas.

image = cv2.imread("shapes_and_colors.jpg")

"""Find contours"""
gray = cv2.cvtColor(shapes.copy(), cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
thresh = cv2.threshold(src=blur, thresh=60, maxval=255, type=cv2.THRESH_BINARY)[1]
new_image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

"""Plot image and contours"""
fig, ax = plt.subplots(ncols=2)
ax[0].imshow(image)
ax[0].axis('off')

canvas = np.zeros_like(image)
for i, c in enumerate(contours):
    M = cv2.moments(c)
    if M["m00"] != 0:
        cX = int((M["m10"] / M["m00"]))
        cY = int((M["m01"] / M["m00"]))
    else:
        cX,cY = 0,0
    cv2.drawContours(canvas, [c], -1, (255, 0, 255), 2)
    ax[1].text(x=cX, y=cY, s=u"{}".format(cv2.contourArea(c)), color="cyan", size=8)


ax[1].imshow(canvas)
ax[1].axis('off')

plt.tight_layout()

enter image description here

Now I'd like to plot them using the contour as the marker, something like:

fig, ax = plt.subplots()

"""Convert contour to marker"""
def contour2marker(contour):
     ...

    return marker


for i,c in enumerate(sorted(contours, key=cv2.contourArea)):
    ax.scatter(x=i, y=cv2.contourArea(c), marker=contour2marker(c))

plt.tight_layout()

enter image description here

I don't know where to start with converting the contours to markers. I'm aware the contours are saved as a collection of points, and looking at this post, it is not straightforward to crop them out of an image. Rather, masks are created or rectangles are cropped out of images. However, if the shapes don't conform to regular polygons this technique doesn't work. If the contours could be converted to images then they could easily be plotted like in this example.

HansHirse
  • 18,010
  • 10
  • 38
  • 67
Ian Gilman
  • 107
  • 2
  • 6

1 Answers1

2

It's not that complicated to generate single images from the contours by using the offset parameter of cv2.drawContours. One might just want to pay attention to proper transparent background of the "marker images".

I had to use a different shapes image, since your original image wasn't available to me (necessary preprocessing is slightly different):

Shapes

The output incorporating the answer you linked then looks like this:

Output

That's the full code:

import cv2
from matplotlib import pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import numpy as np


# Modified from https://stackoverflow.com/a/22570069/11089932
def imscatter(x, y, marker, ax=None, zoom=1.0):
    if ax is None:
        ax = plt.gca()
    im = OffsetImage(marker, zoom=zoom)
    x, y = np.atleast_1d(x, y)
    artists = []
    for x0, y0 in zip(x, y):
        ab = AnnotationBbox(im, (x0, y0), xycoords='data', frameon=False)
        artists.append(ax.add_artist(ab))
    ax.update_datalim(np.column_stack([x, y]))
    ax.autoscale()
    return artists


# Read image
image = cv2.imread("shapes.png")

# Convert to grayscale, (inverse) binary threshold
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 250, 255, cv2.THRESH_BINARY_INV)[1]

# Find contours with respect to the OpenCV version
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]

# Sort contours with respect to the area
cnts = sorted(cnts, key=cv2.contourArea)

# Plot contours as markers
plt.figure(1, figsize=(10, 10))
for i, cnt in enumerate(cnts):
    x, y, w, h = cv2.boundingRect(cnt)
    img = np.zeros((h + 11, w + 11, 4), np.uint8)
    img = cv2.drawContours(img, [cnt], -1, (255, 255, 255, 255), cv2.FILLED, offset=(-x+5, -y+5))
    img = cv2.drawContours(img, [cnt], -1, (0, 128, 0, 255), 3, offset=(-x+5, -y+5))
    imscatter(i, cv2.contourArea(cnt), img, zoom=0.5)
plt.tight_layout()
plt.show()

Hope that helps!

----------------------------------------
System information
----------------------------------------
Platform:    Windows-10-10.0.16299-SP0
Python:      3.8.1
Matplotlib:  3.2.0rc3
NumPy:       1.18.1
OpenCV:      4.2.0
----------------------------------------
HansHirse
  • 18,010
  • 10
  • 38
  • 67
  • Yes this is great! Of course I'm still tinkering a bit with the plotting specifics but the incorporation of `OffsetImage` and `AnnotationBbox` is awesome. Thank you! – Ian Gilman Mar 03 '20 at 14:05