I have foreground mask, like this
If I use findContours method I receive this result
I want to find only main contours on hand without trash. How can I do it?
thresh = 100
# get threshold image
ret, thresh_img = cv.threshold(fgMask, thresh, 255, cv.THRESH_BINARY)
# find contours
contours, hierarchy = cv.findContours(thresh_img, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# create an empty image for contours
img_contours = np.zeros(img.shape)
# draw the contours on the empty image
cv.drawContours(img_contours, contours, -1, (0, 255, 0), 3)
cv.imwrite("img.png", fgMask)
cv.imwrite("img1.png", img_contours)
Maybe you can try blurring image before threshold to reduce the noise.
However, your foreground mask is quite bad actually for this purpose. Since I cannot see what you have done before, it is generally better to use HSV color space if you want to achieve human skin. Because
The color of human skin is created by a combination of blood (red) and
melanin (yellow, brown). Skin colors lie between these two extreme
hues and are somewhat saturated.
HSV: (Hue-Saturation-Value) defined in a way that is similar to how
humans perceive color.
img_path = "hand.jpg"
img = cv.imread(img_path)
# define the upper and lower boundaries of the HSV pixel intensities
# to be considered 'skin'
hsvim = cv.cvtColor(img, cv.COLOR_BGR2HSV)
lower = np.array([0, 48, 80], dtype="uint8")
upper = np.array([20, 255, 255], dtype="uint8")
skinMask= cv.inRange(hsvim, lower, upper)
# blur the mask to help remove noise
skinMask= cv.blur(skinMask, (2, 2))
# get threshold image
ret, thresh = cv.threshold(skinMask, 100, 255, cv.THRESH_BINARY)
cv.imshow("thresh", thresh)
# draw the contours on the empty image
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
contours = max(contours, key=lambda x: cv.contourArea(x))
cv.drawContours(img, [contours], -1, (255, 255, 0), 2)
cv.imshow("contours", img)
cv.waitKey()
should resulted better like
Code Reference
Related
I have the following image: mask
I'm trying separate each white piece into its own image. My approach is to find the contours, iterate over them and fill each one with white color then save the new image.
So far, I've found the contours after using Canny Edge Detection: contours
But I can't seem to fill them all on the inside, since the edges are not fully connected:contours filled
Is there a way to fill in the contours without using dilation/erosion? I intend to preserve the image as it is, not altering it more than needed.
I've used the following code.
import cv2
import numpy as np
def get_blank_image(image):
return np.zeros((image.shape[0], image.shape[1], 3), np.uint8)
# Let's load a simple image with 3 black squares
image = cv2.imread('img.png')
cv2.waitKey(0)
blank_image = get_blank_image(image)
# cv2.imshow('blank', blank_image)
# Grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Find Canny edges
edged = cv2.Canny(gray, 30, 50)
cv2.waitKey(0)
# Finding Contours
# Use a copy of the image e.g. edged.copy()
# since findContours alters the image
contours, hierarchy = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
good_contours = []
for con in contours:
area = cv2.contourArea(con)
if area > 10:
good_contours.append(con)
cv2.drawContours(blank_image, [con], 0, (255, 255, 255), thickness=cv2.FILLED)
# cv2.fillPoly(blank_image, pts=[con], color=(255, 255, 255))
# cv2.imshow('blank', blank_image)
# cv2.waitKey(0)
# good_contours.remove(con)
# cv2.imshow('Canny Edges After Contouring', edged)
cv2.waitKey(0)
cv2.imshow('blank', blank_image)
print("Number of Contours found = " + str(len(good_contours)))
# Draw all contours
# -1 signifies drawing all contours
cv2.drawContours(image, good_contours, -1, (0, 255, 0), 3)
cv2.imshow('Contours', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
The bubbles are the target of contour detection
I would like to detect the contour of the bubble in the photo. However, no matter what kind of contour detection I used, my program would not detect the whole contour lines. I tried with erosion, blur and morphologyEx methods to make the contour continuous, but it seems the bubble shape has changed completely.
May I ask is there other method that can help me correctly draw the bubble contour similar as original photo?
My code is as following:
cv2.imshow(self.window_name, clone)
canvas = cv2.imshow('canvas', canvas)
# waiting for the user to press any key
imageThresh = cv2.adaptiveThreshold(cloneG, 200, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 3, 11)
bubble_only = cv2.bitwise_and(imageThresh, imageThresh, mask = VA_mask)
thresh = cv2.adaptiveThreshold(bubble_only, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 3, 9)
kernel1 = np.ones((2,2), np.uint8)
# Perform morphological hit-or-miss for
erosion = cv2.morphologyEx(thresh, cv2.MORPH_ERODE, kernel1, iterations = 2)
# Inverted this image and blurred it with a kernel size of 4
ret, thresh1 = cv2.threshold(erosion, 127, 255, 1)
blur = cv2.blur(thresh1, (4,4))
# Again perform another threshold on this image to get the central portion of the edge
ret, thresh2 = cv2.threshold(blur, 145, 255, 0)
# Perform morphological dilation to thin the edge. Use an ellipse structuring element of kernel size of 2
kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1,1))
final = cv2.morphologyEx(thresh2, cv2.MORPH_ERODE, kernel2, iterations = 2)
# find the contour then fill the contour
mask = np.zeros(clone.shape[:2], dtype = 'uint8')
contours, hier = cv2.findContours(final, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
bubble_shade = cv2.drawContours(mask, contours, -1, color = (255,255,255), thickness=cv2.FILLED)
area = []
for contour in contours:
cnt_Area = cv2.contourArea(contour)
area.append(cnt_Area)
print(sorted(area))
cv2.imwrite('bubble_shade.jpg', bubble_shade)
cv2.waitKey()
cv2.destroyAllWindows()
return clone
Detected bubble contour
#Segmenting the red pointer
img = cv2.imread('flatmap.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_red = np.array([140, 110, 0])
upper_red = np.array([255, 255 , 255])
# Threshold with inRange() get only specific colors
mask_red = cv2.inRange(hsv, lower_red, upper_red)
# Perform bitwise operation with the masks and original image
red_pointer = cv2.bitwise_and(img,img, mask= mask_red)
# Display results
cv2.imshow('Red pointer', red_pointer)
cv2.imwrite('redpointer.jpg', red_pointer)
cv2.waitKey(0)
cv2.destroyAllWindows()
I have a map and need to extract the red arrow. The code works but the arrow has black patches in it. How would I go about altering the code to improve the output of the arrow so it's a solid shape?
You could use:
dilate to fill up the internal noise in the shape
external contour finding to get the outline of the triangle
convex hull to further smooth it out
import cv2
import numpy as np
img = cv2.imread('dCkpC.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_red = np.array([140, 60, 0])
upper_red = np.array([255, 255, 255])
mask_red = cv2.inRange(hsv, lower_red, upper_red)
element = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
mask_red = cv2.dilate(mask_red, element)
contours, _ = cv2.findContours(mask_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
hull_list = [cv2.convexHull(contour) for contour in contours]
drawing = np.zeros_like(img)
for hull in hull_list:
cv2.fillConvexPoly(img, hull, (255, 0, 0))
cv2.imshow('Image', img)
cv2.imwrite('out.jpg', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
out.jpg ends up looking like
where the triangle has been filled in with blue.
I've looked at the channels in HSL/HSV space.
The arrows are the only stuff in the picture that has any saturation. That would be one required (but insufficient) aspect to get a lock on the desired arrow. I've picked those pixels and they appear to have a bit more than 50% saturation, so I'll use a lower bound of 25% (64).
That red arrow's hue dithers around 0 degrees (red)... that means some of its pixels are on the negative side of 0, i.e. something like 359 degrees.
You need to use two inRange calls to collect all hues from 0 up, and all hues from 359 down. Since OpenCV encodes hues in 2-degree steps, that'll be a value of 180 and down. I'll select 0 +- 20 degrees (0 .. 10 and 170 .. 180).
In summary:
hsv_im = cv.cvtColor(im, cv.COLOR_BGR2HSV)
mask1 = cv.inRange(hsv_im, np.array([0, 64, 0]), np.array([10, 255, 255]))
mask2 = cv.inRange(hsv_im, np.array([170, 64, 0]), np.array([180, 255, 255]))
mask = mask1 | mask2
cv.imshow("mask", mask)
cv.waitKey()
I am only a couple weeks into learning coding with Python and OpenCV, but StackOverflow has helped me numerous times. However I cant seem to figure this issue out so decided to ask my first question.
I am trying to take an image
Find the largest contour by area
Remove the background outside the contour
Effectively removing the background from the largest "object" in the
picture.
I am struggling with the last part. I know I need to create a mask somehow then place the mask over the original image.
How do I create the correct type of mask? And how do I place the mask on top of the original image?
This is my code:
import cv2
import numpy as np
# Load image
image = cv2.imread('Resources/X.png')
# Grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Find Canny edges
edged = cv2.Canny(gray, 30, 200)
# Finding Contours
contours, hierarchy = cv2.findContours(edged,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cv2.imshow('Canny Edges After Contouring', edged)
print("Number of Contours found = " + str(len(contours)))
cv2.waitKey(0)
# Largest contour
c = max(contours, key=cv2.contourArea)
# Not sure what to do from here. Attempt below:
mask = np.zeros(image.shape, np.uint8) # What is this actually doing? what does np.unit8 mean?
cv2.drawContours(mask, c, -1, (255, 255, 255), 1) # I am drawing the correct outline/contour
cv2.imshow('Mask', mask)
cv2.waitKey(0)
Any help would be appreciated.
Thanks
Chris
EDIT:
I managed to do it but not exactly sure what I am doing :-(
How would I get a different color background? I presume I have to fill the blank_mask with another color?
Also not sure what the bitwise function is actually doing.
blank_mask = np.zeros(image.shape, dtype=np.uint8)
cv2.fillPoly(blank_mask, [c], (255,255,255))
blank_mask = cv2.cvtColor(blank_mask, cv2.COLOR_BGR2GRAY)
result = cv2.bitwise_and(original,original,mask=blank_mask)
cv2.imshow('Result', result)
Here is one way to change the background on your image using Python/OpenCV.
Read the input and get its dimensions
Threshold on black and invert to get white on black background
Get the largest contour from the inverted threshold image
Draw the largest contour as white filled on a black background as a mask
Create an inverted mask
Create a new colored background image
Apply the mask to the image
Apply the inverted mask to the background color image
Add the two images
Save the result
import cv2
import numpy as np
# Read image
img = cv2.imread('shapes.png')
hh, ww = img.shape[:2]
# threshold on black
# Define lower and uppper limits of what we call "white-ish"
lower = np.array([0, 0, 0])
upper = np.array([0, 0, 0])
# Create mask to only select black
thresh = cv2.inRange(img, lower, upper)
# invert mask so shapes are white on black background
thresh_inv = 255 - thresh
# get the largest contour
contours = cv2.findContours(thresh_inv, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# draw white contour on black background as mask
mask = np.zeros((hh,ww), dtype=np.uint8)
cv2.drawContours(mask, [big_contour], 0, (255,255,255), cv2.FILLED)
# invert mask so shapes are white on black background
mask_inv = 255 - mask
# create new (blue) background
bckgnd = np.full_like(img, (255,0,0))
# apply mask to image
image_masked = cv2.bitwise_and(img, img, mask=mask)
# apply inverse mask to background
bckgnd_masked = cv2.bitwise_and(bckgnd, bckgnd, mask=mask_inv)
# add together
result = cv2.add(image_masked, bckgnd_masked)
# save results
cv2.imwrite('shapes_inverted_mask.jpg', mask_inv)
cv2.imwrite('shapes_masked.jpg', image_masked)
cv2.imwrite('shapes_bckgrnd_masked.jpg', bckgnd_masked )
cv2.imwrite('shapes_result.jpg', result)
cv2.imshow('mask', mask)
cv2.imshow('image_masked', image_masked)
cv2.imshow('bckgrnd_masked', bckgnd_masked)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask image from largest contour:
Image masked:
Background masked:
Result:
I'm attempting to remove all but the largest contours while retaining the child contours within the largest contour. Effectively I want to go from here: Input Image to: Output Image I don't know how to go about this though (I'm not a programmer by any stretch and had help putting together the below). The code I do have doesn't retain the child contours. Output of Below.
import cv2 as cv
import numpy as np
img = cv.imread('small_contour.jpg')
image_contours = np.zeros((img.shape[1], img.shape[0], 1), np.uint8)
image_binary = np.zeros((img.shape[1], img.shape[0], 1), np.uint8)
for channel in range(img.shape[2]):
ret, image_thresh = cv.threshold(img[:, :, channel], 127, 255, cv.THRESH_BINARY)
contours = cv.findContours(image_thresh, 1, 1)[0]
cv.drawContours(image_contours, contours, -1, (255,255,255), 3)
contours = cv.findContours(image_contours, cv.RETR_LIST,
cv.CHAIN_APPROX_SIMPLE)[0]
cv.drawContours(image_binary, [max(contours, key = cv.contourArea)],
-1, (255, 255, 255), -1)
cv.imwrite('fill_contour.jpg', image_binary)
cv.imshow('Small Contour', image_binary)
cv.waitKey(0)
What your code does in this line cv.drawContours(image_binary, [max(contours, key = cv.contourArea)], -1, (255, 255, 255), -1) is to set to color (255, 255, 255) the pixels of the image_binary corresponding to the boundary and inner pixels of the bigger contour found by cv.findContours.
Creating the image_binary you are also swapping width with height and this is an error.
You don't need to keep the children: you should use cv.RETR_TREE for that, then find the index of the father and look for all contours with that father doing it recursively for the nested contours (children of children ...). See https://docs.opencv.org/4.1.0/d9/d8b/tutorial_py_contours_hierarchy.html But this is not useful for your goal.
You can use the result you currently have as a mask for the original image, but the code need some fixes, starting from the linked image named VS0L9.jpg.
Note that I'm using cv2, just change to cv.
Load the image
im = cv2.imread('VS0L9.jpg', cv2.IMREAD_GRAYSCALE)
Find the contours (I used RETR_TREE)
contours, hierarchy = cv2.findContours(im, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
Find the bigger contour:
bigger = max(contours, key=lambda item: cv2.contourArea(item))
Initialize the mask (what you tried with image_binary):
the_mask = np.zeros_like(im)
Draw the fill the bigger contour on the mask image, the region of interest is set to (255, 255, 255):
cv2.drawContours(the_mask, [bigger], -1, (255, 255, 255), cv2.FILLED)
At this point the mask looks like the output of your code, but with proper width and height.
Use the mask with the original image to show only the area of interest:
res = cv2.bitwise_and(im, im, mask = the_mask)
Or, alternatively:
res = im.copy()
res[the_mask == 0] = 0
Now res is the desired result.
You can use morphological reconstruction, with erode image as marker.
import cv2
img = cv2.imread('VS0L9.jpg', cv2.IMREAD_GRAYSCALE)
thresh = cv2.threshold(img, 40, 255, cv2.THRESH_BINARY)[1]
kernel=cv2.getStructuringElement(cv2.MORPH_RECT, (17,17))
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
marker = cv2.erode(thresh,kernel,iterations = 5)
while True:
tmp=marker.copy()
marker=cv2.dilate(marker, kernel2)
marker=cv2.min(thresh, marker)
difference = cv2.subtract(marker, tmp)
if cv2.countNonZero(difference) == 0:
break
cv2.imwrite('out.png', marker)
cv2.imshow('result', marker )