Ezer Sin
Ezer Sin

Reputation: 21

How do I fill in the holes in an image

I am having some difficulty in filling the internal holes of an image, your help would be greatly appreciated.

In terms of scatter :

mlist_0 = movelist_porous[0]
rx = np.round(mlist_0[:,0])
ry = np.round(mlist_0[:,1])
fig, axs = plt.subplots(1,1,figsize=(12,8))
axs.scatter(mlist_0[:,0], mlist_0[:,1], color='black')
plt.axis('off')
# plt.savefig("test.png", bbox_inches='tight')
plt.show()

In terms of plot :

mlist_0 = movelist_porous[0]
rx = np.round(mlist_0[:,0])
ry = np.round(mlist_0[:,1])
fig, axs = plt.subplots(1,1,figsize=(12,8))
axs.plot(mlist_0[:,0], mlist_0[:,1], color='black')
plt.axis('off')
plt.savefig("test.png", bbox_inches='tight')
plt.show()

I would like to have an outcome like this:

The holes are filled with one colour (black) and the surrounding contour another colour (white), however, I am unsure how to do this.

Upvotes: 2

Views: 1783

Answers (2)

iGian
iGian

Reputation: 11193

If you can, use a Jupyter Notebook with matplotlib.

Starting from the plot I suggest you to use thresholding to get rid of the noise around the line.

im = cv2.imread('Fpeck.jpg', cv2.IMREAD_GRAYSCALE)
threshold = 127
apply_val = 255
ret, th = cv2.threshold(im, threshold, apply_val, cv2.THRESH_BINARY)

If you check the same area of the original and thresholded image you can see the improvement (here is a bit too small, maybe):

enter image description here

Show the images side by side using this code:

original_vs_thresholded = np.concatenate([im[160:200,0:40], th[160:200,0:40]], axis=1)

After that find the contours on the thresholded image using cv2.RETR_TREE as retrieval mode, see https://docs.opencv.org/4.1.0/d9/d8b/tutorial_py_contours_hierarchy.html:

contours, hierarchy = cv2.findContours(th, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

If you print hierarchy you can see the contours and the nesting, it contains a list of index where:

  • column (a) is the index of the next element at the same level
  • column (b) is the index of the previous element at the same level
  • column (c) is the index of the child contour
  • column (d) is the index of the parent contour

This is the hierarchy:

#=>    a  b  c  d
#=>  [-1 -1  1 -1] <- boundary of the image
#=>  [-1 -1  2  0] <- bigger oval, external of the line
#=>  [-1 -1  3  1] <- bigger oval, internal of the line
#=>  [ 5 -1  4  2] <- shape1, external of the line
#=>  [-1 -1 -1  3] <- shape1, internal of the line
#=>  [ 7  3  6  2] <- and so on ...
#=>  [-1 -1 -1  5]
#=>  [ 9  5  8  2]
#=>  [-1 -1 -1  7]
#=>  [11  7 10  2]
#=>  [-1 -1 -1  9]
#=>  [13  9 12  2]
#=>  [-1 -1 -1 11]
#=>  [-1 11 14  2]
#=>  [-1 -1 -1 13]

For example hierarchy[0][5] has values [ 7 3 6 2] and corresponds to contours[5].

Hope this gives you a basic understanding on how to pick the proper contour for building the final result.

For example collecting the indexes of contours where hierarchy[0][0] == -1 or hierarchy[0][3] == 2.


You can plot the sequence of the contour drawing using this code on a notebook:
im_cnt_th = cv2.cvtColor(im, cv2.COLOR_GRAY2BGR)
for n, contour in enumerate(contours):
    color = tuple(int(n) for n in np.random.randint(256, size=3))
    print(color)
    cv2.drawContours(im_cnt_th, [contour], -1, color, cv2.FILLED)
    print(f'index: {n} - hyerarchy: {hierarchy[0][n]}')
    plt.imshow(im_cnt_th[:,:,::-1])
    plt.show()

This is the result you should get (see the overlapping of inner to outer contours):

enter image description here


Finally

For the required result, find the inner shape indexes:

inner_shapes_indexes = [ idx for idx, h in enumerate(hierarchy[0]) if h[3] == 2] # parent contour is indexed 2 in hierarchy

Then, build a black image, plot in white the oval, plot in black the inner shapes:

new_im = np.zeros_like(im)
cv2.drawContours(new_im, [contours[1]], -1, 255, cv2.FILLED)
for idx in inner_shapes_indexes:
    cv2.drawContours(new_im, [contours[idx]], -1, 0, cv2.FILLED)

enter image description here

Upvotes: 2

amras
amras

Reputation: 1619

Here you go, starting with the image from plot.

import numpy as np
import cv2

# Reading the image saved from plot
image = cv2.imread('test.jpg')

# Coversion to grayscale, inversion, edge detection
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.bitwise_not(gray)
edges = cv2.Canny(gray, 50, 200)

# Find the contours. The first two largest contours are for the outer contour
# So, taking the rest of the contours for inner contours
cnts = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[2:]

# Filling the inner contours with black color
for c in cnts:
    cv2.drawContours(image, [c], -1, (0, 0, 0), -1)

# Displaying the result
cv2.imshow("Contour", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

The output from the code:

enter image description here

Upvotes: 5

Related Questions