Reputation: 21
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
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):
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:
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
.
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):
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)
Upvotes: 2
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:
Upvotes: 5