Detect circles with specific colors using opencv - python

I have to detect yellow circles in an image using OpenCV and python, as shown in the first image:
Once I detect the yellow circle, I have to highlight it, like so:
I am new to OpenCV, so I was looking for some guidance or help. All help is appreciated

Here's a potential approach:
Convert image to HSV
Find upper/lower color boundaries and create a mask
Find contours and filter using the number of vertices
We convert image to HSV and then determine lower and upper boundaries to create a mask using cv2.inRange(). This step isolates the yellow objects
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([0, 208, 94], dtype="uint8")
upper = np.array([179, 255, 232], dtype="uint8")
mask = cv2.inRange(image, lower, upper)
Next to determine the shape, we find contours and filter using the number of vertices. We use cv2.arcLength() and cv2.approxPolyDP() to obtain a list of vertices and approximate contours. We can check the number of entries in this list to determine the shape of an object. For instance, if the contour has three vertices, it must be a triangle. Similarly, if it has four vertices, it must be a square. So for this image, we can make the assumption the shape is a circle if it has greater than a certain number of vertices. Here's the result
import numpy as np
import cv2
image = cv2.imread('1.png')
original = image.copy()
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([0, 208, 94], dtype="uint8")
upper = np.array([179, 255, 232], dtype="uint8")
mask = cv2.inRange(image, lower, upper)
# Find contours
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Extract contours depending on OpenCV version
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# Iterate through contours and filter by the number of vertices
for c in cnts:
perimeter = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.04 * perimeter, True)
if len(approx) > 5:
cv2.drawContours(original, [c], -1, (36, 255, 12), -1)
cv2.imshow('mask', mask)
cv2.imshow('original', original)
cv2.imwrite('mask.png', mask)
cv2.imwrite('original.png', original)
cv2.waitKey()

Related

How to find Contour based on specific color outline or border?

I'm trying to detect count of contour based on yellow color outline showing below in AutoCAD drawing.
import numpy as np
import cv2
image = cv2.imread('C:/Users/htc/Desktop/capture.png')
original = image.copy()
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([0, 208, 94], dtype="uint8")
upper = np.array([179, 255, 232], dtype="uint8")
mask = cv2.inRange(image, lower, upper)
# Find contours
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Extract contours depending on OpenCV version
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# Iterate through contours and filter by the number of vertices
for c in cnts:
perimeter = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.04 * perimeter, True)
if len(approx) > 5:
cv2.drawContours(original, [c], -1, (36, 255, 12), -1)
cv2.imshow('mask', mask)
cv2.imshow('original', original)
cv2.waitKey()
The output i'm expecting to detect these two outline circles(contours) but not achieved. Any one help me in this where i'm doing wrong.
Output
The yellow color has different hue value from the rest. You can use first channel (hue), with value range is 10:15
lower = np.array([10, 0, 0], dtype="uint8")
upper = np.array([15, 255, 255], dtype="uint8")
The hue channel indicates the "color" of the image see this. You can imshow the hue channel
hue = image[:,:,0]
cv2.imshow('hue', hue)
It's almost black. Let's "rescale" its value:
cv2.imshow('hue', (hue-np.min(hue))/(np.max(hue)-np.min(hue))*255)
After using MS Paint to find the range of "white zone", I see it is 170-255, and np.max(hue) = 15, np.min(hue) = 0, so the original range is 10-15.
That's how I got the numbers in lower and upper. Since you only care about the color, other channel is set to 0,0 for lower, and 255,255 for upper
Your upper boundary cannot detect the color of the circles.
Try setting it to something like this:
upper = np.array([179, 255, 255], dtype="uint8")
Feel free to play around with boundaries more to select your color.

Removing Background Around Contour

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:

Count number of cells in the image

I need code for counting the number of cells in the image and only the cells that are in pink color should be counted .I have used thresholding and watershed method.
import cv2
from skimage.feature import peak_local_max
from skimage.morphology import watershed
from scipy import ndimage
import numpy as np
import imutils
image = cv2.imread("cellorigin.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,
cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv2.imshow("Thresh", thresh)
D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, indices=False, min_distance=20,
labels=thresh)
cv2.imshow("D image", D)
markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]
labels = watershed(-D, markers, mask=thresh)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1))
for label in np.unique(labels):
# if the label is zero, we are examining the 'background'
# so simply ignore it
if label == 0:
continue
# otherwise, allocate memory for the label region and draw
# it on the mask
mask = np.zeros(gray.shape, dtype="uint8")
mask[labels == label] = 255
# detect contours in the mask and grab the largest one
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
c = max(cnts, key=cv2.contourArea)
# draw a circle enclosing the object
((x, y), r) = cv2.minEnclosingCircle(c)
cv2.circle(image, (int(x), int(y)), int(r), (0, 255, 0), 2)
cv2.putText(image, "#{}".format(label), (int(x) - 10, int(y)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.imshow("input",image
cv2.waitKey(0)
I am not able to segment the pink cells properly.At some places two pink cells are attached together those also should be separated.
output:
Since the cells seem to be visibility different from the nucleus (dark purple) and the background (light pink), color thresholding should work here. The idea is to convert the image to HSV format then use a lower and upper color threshold to isolate the cells. This will give us a binary mask which we can use to count the number of cells.
We begin by converting the image to HSV format then use a lower/upper color threshold to create a binary mask. From here we perform morphological operations to smooth the image and remove small bits of noise.
Now that we have the mask, we find contours with the cv2.RETR_EXTERNAL parameter to ensure that we only take the outer contours. We define several area thresholds to filter out the cells
minimum_area = 200
average_cell_area = 650
connected_cell_area = 1000
The minimum_area threshold ensures that we do not count tiny sections of a cell. Since some of the cells are connected, some contours may have multiple connected cells represented as a single contour so to estimate the cells better, we define an average_cell_area parameter which estimates the area of a single cell. The connected_cell_area parameter detects connected cells where use math.ceil() on a connected cell contour to estimate the number of cells in that contour. To count the number of cells, we iterate through the contours and sum up the contours based on their area. Here's the detected cells highlighted in green
Cells: 75
Code
import cv2
import numpy as np
import math
image = cv2.imread("1.jpg")
original = image.copy()
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
hsv_lower = np.array([156,60,0])
hsv_upper = np.array([179,115,255])
mask = cv2.inRange(hsv, hsv_lower, hsv_upper)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel, iterations=2)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
minimum_area = 200
average_cell_area = 650
connected_cell_area = 1000
cells = 0
for c in cnts:
area = cv2.contourArea(c)
if area > minimum_area:
cv2.drawContours(original, [c], -1, (36,255,12), 2)
if area > connected_cell_area:
cells += math.ceil(area / average_cell_area)
else:
cells += 1
print('Cells: {}'.format(cells))
cv2.imshow('close', close)
cv2.imshow('original', original)
cv2.waitKey()

How to mask everything in an image except for a specific color inside

I have an image and I need to make everything black except for the color green and what ever color is inside there. How do I make all black except for green and what ever color is inside green?
I have converted the color image from RGB to BGR and converted BGR to HSV.
I have created lower and upper for green
Masked the image according to the boundaries but when I show the image everything else is black except for green. The color inside the green rectangle doesn't show up.
[]
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
lower = np.array([69,206,177], dtype = "uint8")
upper = np.array([69,206,177], dtype = "uint8")
green_mask = cv2.inRange(hsv,lower,upper)
green= cv2.bitwise_and(hsv,hsv,mask=green_mask )
cv2.imshow("Show colors in green ",green)
cv2.waitKey(0)
cv2.destroyAllWindows()
Here's a simple approach:
Convert image to grayscale
Color threshold to isolate green
Find contours and fill in mask
Bitwise-and to get result
After converting to grayscale we color threshold to get a mask of only green pixels within the minimum/maximum range
Next we find contours and fill in the mask to keep everything inside using cv2.fillPoly()
Now we cv2.bitwise_and() to get our result
import numpy as np
import cv2
image = cv2.imread('1.png')
original = image.copy()
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([35, 0, 0], dtype="uint8")
upper = np.array([131, 255, 185], dtype="uint8")
mask = cv2.inRange(image, lower, upper)
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.fillPoly(mask, cnts, (255,255,255))
result = cv2.bitwise_and(original,original,mask=mask)
cv2.imshow('mask', mask)
cv2.imshow('result', result)
cv2.waitKey()

How to invert the background of square headers from black to white in an image using python?

I am trying to make the background of the square headers (The black bar that contains TERMS PONUMBER PROJECT) white and the text within black.
I have tried using the findContours method to find the contours and then crop and invert them so that I get them in the black text and white background form. But the problem is I am not having any idea on how to proceed ahead or is there any better approach to this
image =cv2.imread("default.jpg")
gray=cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
th, thresh = cv2.threshold(gray,1, 255, cv2.THRESH_BINARY_INV)
kernel = cv2.getStructuringElemnt(cv2.MORPH_ELLIPSE,(7,7))
morp_image = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
contours = cv2.findContours(morp_image,
cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
cnts = sorted(contours,key=cv2.contourArea)[-1]
The code above does find each such contour on an individual basis like if I change the [-1] in the last line of the code to [-2], it will find the next contour but I want to find all such areas in the image in a single go and make the background of such areas white while changing the text to black.
Thanks
Here's a simple approach
Convert image to grayscale and Gaussian blur
Otsu's threshold to obtain binary image
Find contours
Filter using the number of corners and contour area
Extract ROI, invert ROI, and replace into original image
The idea is that if the contour has 4 corners, it must be a square/rectangle. In addition, we filter using a minimum contour area to ignore noise. If the contour passes our filter then we have a desired ROI to invert. The detected ROIs
Now we extract each ROI using Numpy slicing. Here's each ROI before and after inverting
Now we simply replace each inverted ROI back into the original image to get our result
import cv2
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.015 * peri, True)
area = cv2.contourArea(c)
if len(approx) == 4 and area > 1000:
x,y,w,h = cv2.boundingRect(c)
ROI = 255 - image[y:y+h,x:x+w]
image[y:y+h, x:x+w] = ROI
cv2.imshow('image', image)
cv2.imwrite('image.png', image)
cv2.waitKey()

Categories