Improve quality of extracted image in OpenCV - python

#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()

Related

Find hand contours only. Python. OpenCV

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

Detect colorful dots in image in python?

I'm trying to detect colorful dots on a white/gray background. The dots are 3 different colors (yellow, purple, blue) of different sizes. Here is the original image:
I converted the image to HSV and found lower and upper bounds for each image then applied contour detection to find those dots. The following code detects most of the dots:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('image1_1.png')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_yellow = np.array([22,25,219])
upper_yellow = np.array([25,75,225])
lower_purple = np.array([141,31,223])
upper_purple = np.array([143,83,225])
lower_blue = np.array([92,32,202])
upper_blue = np.array([96,36,208])
mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)
mask_purple = cv2.inRange(hsv, lower_purple, upper_purple)
mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)
res_blue = cv2.bitwise_and(img,img, mask=mask_blue)
res_purple = cv2.bitwise_and(img,img, mask=mask_purple)
res_yellow = cv2.bitwise_and(img,img, mask=mask_yellow)
gray_blue = cv2.cvtColor(res_blue, cv2.COLOR_BGR2GRAY)
gray_purple = cv2.cvtColor(res_purple, cv2.COLOR_BGR2GRAY)
gray_yellow = cv2.cvtColor(res_yellow, cv2.COLOR_BGR2GRAY)
_,thresh_blue = cv2.threshold(gray_blue,10,255,cv2.THRESH_BINARY)
_,thresh_purple = cv2.threshold(gray_purple,10,255,cv2.THRESH_BINARY)
_,thresh_yellow = cv2.threshold(gray_yellow,10,255,cv2.THRESH_BINARY)
contours_blue, hierarhy1 = cv2.findContours(thresh_blue,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
contours_purple, hierarhy2 = cv2.findContours(thresh_purple,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
contours_yellow, hierarhy3 = cv2.findContours(thresh_yellow,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
result = img.copy()
cv2.drawContours(result, contours_blue, -1, (0, 0, 255), 2)
cv2.drawContours(result, contours_purple, -1, (0, 0, 255), 2)
cv2.drawContours(result, contours_yellow, -1, (0, 0, 255), 2)
cv2.imwrite("_allContours.jpg", result)
Here are the detected contours:
The problem is that some of the colored dots are not detected. I understand by fine-tuning the color ranges (lower and upper) it's possible to detect more dots. But that is very time consuming and not generalizable to similar images. For example the following image looks similar to the first image above and has the same colorful dots but the background is slightly different, once I ran it through above code it was not able to detect even one of the dots. Am I on the right track? Is there a more scalable and reliable solution with less need to tune color parameters in order to solve this problem? Here is the other image I tried:
I would suggest simply using adaptiveThreshold in Python/OpenCV
import cv2
import numpy as np
# read image
img = cv2.imread("dots.png")
# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# do adaptive threshold on gray image
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 25, 6)
# write results to disk
cv2.imwrite("dots_thresh.jpg", thresh)
# display it
cv2.imshow("thresh", thresh)
cv2.waitKey(0)

How to remove background gray drawings from image in OPENCV python

I need to remove the gray drawing from the image background and only need symbols drawn over it.
Here is my code to do that using morphologyEx but it did not remove the entire gray drawing that is in background.
img_path = "images/new_drawing.png"
img = cv2.imread(img_path)
kernel = np.ones((2,2), dtype=np.uint8)
result = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations=1)
cv2.imshow('Without background',result);
cv2.waitKey(0)
cv2.destroyAllWindows()
I tried this also and got expected results in grayscale but unable to convert it to BGR.
Here is my code
img = cv2.imread('images/new_drawing.png')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
med_blur = cv2.medianBlur(gray_img, ksize=3)
_, thresh = cv2.threshold(med_blur, 190, 255, cv2.THRESH_BINARY)
blending = cv2.addWeighted(gray_img, 0.5, thresh, 0.9, gamma=0)
cv2.imshow("blending", blending);
Also i used contours to identify symbols and draw them to white image but problem is that it also identify background drawing that i don't want.
Input image
Expected output image
Also the drawing will be always in gray color as in image.
Please help me out to get better result.
You are almost there...
Instead of using cv2.inRange to "catch" the non-gray pixel I suggest using cv2.inRange for catching all the pixels you want to change to white color:
mask = cv2.inRange(hsv, (0, 0, 100), (255, 5, 255))
The hue range is irrelevant.
The saturation is close to zero (shades of gray).
The brightness excludes the black pixels (you like to keep).
In order to get a nicer solution, I also used the following additional stages:
Build a mask of non-black pixels:
nzmask = cv2.inRange(hsv, (0, 0, 5), (255, 255, 255))
Erode the above mask:
nzmask = cv2.erode(nzmask, np.ones((3,3)))
Apply and operation between mask and nzmask:
mask = mask & nzmask
The above stages keeps the gray pixels around the black text.
Without the above stages, the black text gets thinner.
The last stage is replacing mask pixels with white:
new_img = img.copy()
new_img[np.where(mask)] = 255
Here is the code:
import numpy as np
import cv2
img_path = "new_drawing.png"
img = cv2.imread(img_path)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, (0, 0, 100), (255, 5, 255))
cv2.imshow('mask before and with nzmask', mask);
# Build mask of non black pixels.
nzmask = cv2.inRange(hsv, (0, 0, 5), (255, 255, 255))
# Erode the mask - all pixels around a black pixels should not be masked.
nzmask = cv2.erode(nzmask, np.ones((3,3)))
cv2.imshow('nzmask', nzmask);
mask = mask & nzmask
new_img = img.copy()
new_img[np.where(mask)] = 255
cv2.imshow('mask', mask);
cv2.imshow('new_img', new_img);
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
Here is one way to do that in Python/OpenCV.
Read the input
Convert to HSV and separate channels
Threshold the saturation channel
Threshold the value channel and invert
Combine the two threshold images as a mask
Apply the mask to the input to write white where the mask is black
Save the result
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('symbols.png')
# convert image to hsv colorspace
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
# threshold saturation image
thresh1 = cv2.threshold(s, 92, 255, cv2.THRESH_BINARY)[1]
# threshold value image and invert
thresh2 = cv2.threshold(v, 128, 255, cv2.THRESH_BINARY)[1]
thresh2 = 255 - thresh2
# combine the two threshold images as a mask
mask = cv2.add(thresh1,thresh2)
# use mask to remove lines in background of input
result = img.copy()
result[mask==0] = (255,255,255)
# display IN and OUT images
cv2.imshow('IMAGE', img)
cv2.imshow('SAT', s)
cv2.imshow('VAL', v)
cv2.imshow('THRESH1', thresh1)
cv2.imshow('THRESH2', thresh2)
cv2.imshow('MASK', mask)
cv2.imshow('RESULT', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
# save output image
cv2.imwrite('symbols_thresh1.png', thresh1)
cv2.imwrite('symbols_thresh2.png', thresh2)
cv2.imwrite('symbols_mask.png', mask)
cv2.imwrite('symbols_cleaned.png', result)
Saturation channel thresholded:
Value channel thresholded and inverted:
Mask:
Result:

Calculate the white pixel inside cv2.circle

I'm trying to implement identification of optic nerve glioma identification using python and openCV.
I need to do the following steps in order for me to classify optic nerve glioma successfully.
Find the brightest part of an image and put a circle on it using cv2.circle - Done
Calculate the white part on the image inside cv2.circle - Needs help
Here's my code for identifying the brightest part of the image
gray = cv2.GaussianBlur(gray, (371, 371), 0)
(minVal, maxVal, minLoc, maxLoc) = cv2.minMaxLoc(gray)
image = orig.copy()
cv2.circle(image, maxLoc, 371, (255, 0, 0), 2)
sought = [254,254,254]
amount = 0
for x in range(image.shape[0]):
for y in range(image.shape[1]):
b, g, r = image[x, y]
if (b, g, r) == sought:
amount += 1
print(amount)
image = imutils.resize(image, width=400)
# display the results of our newly improved method
cv2.imshow("Optic Image", image)
cv2.waitKey(0)
The code above returns the following output
What I'm trying to do now is to identify the size of the white region of the image inside the cv2.circle.
Thank you so much!
I am not sure what you consider as "white", but here is one way to do the counting in Python/OpenCV. Simply read the image. Convert to grayscale. Threshold it at some level. Then just count the number of white pixels in the thresholded image.
If I use your output image for my input (after removing your white border):
import cv2
import numpy as np
# read image
img = cv2.imread('optic.png')
# convert to HSV and extract saturation channel
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
# threshold
thresh = cv2.threshold(gray, 175, 255, cv2.THRESH_BINARY)[1]
# count number of white pixels
count = np.sum(np.where(thresh == 255))
print("count =",count)
# write result to disk
cv2.imwrite("optic_thresh.png", thresh)
# display it
cv2.imshow("IMAGE", img)
cv2.imshow("THRESH", thresh)
cv2.waitKey(0)
Thresholded image:
Count of white pixels in threshold:
count = 1025729
I am still not sure what you consider as white and what you consider as the yellow circle. But here is another attempt using Python/OpenCV.
Read the input
Convert the input to the range 0 to 1 as 1D data
Use kmeans clustering to reduce the number of colors and convert back to range 0 to 255 as 2D image
Use inRange color thresholding to isolate the "yellow" area
Clean it up with morphology and get the contour
Get the minimum enclosing circle center and radius and bias the center a little
Draw an unfilled white circle on the input
Draw a white filled circle on a black background as a circle mask for the yellow area
Convert the input to grayscale
Threshold the grayscale image
Apply the mask to the thresholded grayscale image
Count the number of white pixels
Input:
import cv2
import numpy as np
from sklearn import cluster
# read image
img = cv2.imread('optic.png')
h, w, c = img.shape
# convert to range 0 to 1
image = img.copy()/255
# reshape to 1D array
image_1d = image.reshape(h*w, c)
# do kmeans processing
kmeans_cluster = cluster.KMeans(n_clusters=int(5))
kmeans_cluster.fit(image_1d)
cluster_centers = kmeans_cluster.cluster_centers_
cluster_labels = kmeans_cluster.labels_
# need to scale result back to range 0-255
newimage = cluster_centers[cluster_labels].reshape(h, w, c)*255.0
newimage = newimage.astype('uint8')
# threshold brightest region
lowcolor = (150,180,230)
highcolor = (170,200,250)
thresh1 = cv2.inRange(newimage, lowcolor, highcolor)
# apply morphology open and close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7,7))
thresh1 = cv2.morphologyEx(thresh1, cv2.MORPH_OPEN, kernel, iterations=1)
thresh1 = cv2.morphologyEx(thresh1, cv2.MORPH_CLOSE, kernel, iterations=1)
# get contour
cntrs = cv2.findContours(thresh1, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cntrs = cntrs[0] if len(cntrs) == 2 else cntrs[1]
c = cntrs[0]
# get enclosing circle and bias center, if desired, since it is slightly offset (or alternately, increase the radius)
bias = 5
center, radius = cv2.minEnclosingCircle(c)
cx = int(round(center[0]))-bias
cy = int(round(center[1]))+bias
rr = int(round(radius))
# draw filled circle over black and also outline circle over input
mask = np.zeros_like(img)
cv2.circle(mask, (cx,cy), rr, (255, 255, 255), -1)
circle = img.copy()
cv2.circle(circle, (cx,cy), rr, (255, 255, 255), 1)
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
# threshold gray image
thresh2 = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)[1]
# apply mask to thresh2
thresh2 = cv2.bitwise_and(thresh2, mask[:,:,0])
# count number of white pixels
count = np.sum(np.where(thresh2 == 255))
print("count =",count)
# write result to disk
#cv2.imwrite("optic_thresh.png", thresh)
cv2.imwrite("optic_kmeans.png", newimage)
cv2.imwrite("optic_thresh1.png", thresh1)
cv2.imwrite("optic_mask.png", mask)
cv2.imwrite("optic_circle.png", circle)
cv2.imwrite("optic_thresh2.png", thresh2)
# display it
cv2.imshow("IMAGE", img)
cv2.imshow("KMEANS", newimage)
cv2.imshow("THRESH1", thresh1)
cv2.imshow("MASK", mask)
cv2.imshow("CIRCLE", circle)
cv2.imshow("GRAY", gray)
cv2.imshow("THRESH2", thresh2)
cv2.waitKey(0)
kmeans image:
inRange threshold image:
Circle on input:
Circle mask image:
Masked threshold image:
Count Results:
count = 443239

Contour Identification using OpenCV

I have collection of objects in a image.
Check the sample input image here.
I would like to find the contour of each object.
I am following the below approach to identifying the contour using OpenCV2
gray = cv2.cvtColor(input_image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
edged = cv2.Canny(gray, 50, 100)
dilate= cv2.dilate(edged, None, iterations=1)
erode= cv2.erode(dilate, None, iterations=1)
cnts = cv2.findContours(erode, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
This is the contour output which I am getting for the above code : see output image
Is there any better approach for identifying the object in the image?
You have missed a simple step in your code snippet, cv2.findContours() works best on binary images, but you are simply passing the gray scale image to cv2.findContours. I have followed the following steps to segment out the apples from background:
Step 1: Segment out the background which majorly contains gray-scale pixels.
You can use HSV color domain here, where low value of saturation would get the background segmented as:
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV_FULL)
# Filter out low saturation values, which means gray-scale pixels(majorly in background)
bgd_mask = cv2.inRange(img_hsv, np.array([0, 0, 0]), np.array([255, 30, 255]))
Step 2: For pitch black pixels, the saturation value was abrupt so we segmented the extreme black and white pixels:
# Get a mask for pitch black pixel values
black_pixels_mask = cv2.inRange(img_bgr, np.array([0, 0, 0]), np.array([70, 70, 70]))
# Get the mask for extreme white pixels.
white_pixels_mask = cv2.inRange(img_bgr, np.array([230, 230, 230]), np.array([255, 255, 255]))
Step 3: Merge these masks to get a final mask for cv2.findContours:
final_mask = cv2.max(bgd_mask, black_pixels_mask)
final_mask = cv2.min(final_mask, ~white_pixels_mask)
final_mask = ~final_mask
Step 4: Now to fill in the holes, we erode and dilate the image:
final_mask = cv2.erode(final_mask, np.ones((3, 3), dtype=np.uint8))
final_mask = cv2.dilate(final_mask, np.ones((5, 5), dtype=np.uint8))
Step 5: Use cv2.findContours() to get the contours and filter them on area to remove the smaller ones:
# Now you can finally find contours.
im, contours, hierarchy = cv2.findContours(final_mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
final_contours = []
for contour in contours:
area = cv2.contourArea(contour)
if area > 2000:
final_contours.append(contour)
Step 6: Show the final contours
Here is full code snippet:
import cv2
import numpy as np
img_bgr = cv2.imread("/home/anmol/Downloads/tWuTW.jpg")
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV_FULL)
# Filter out low saturation values, which means gray-scale pixels(majorly in background)
bgd_mask = cv2.inRange(img_hsv, np.array([0, 0, 0]), np.array([255, 30, 255]))
# Get a mask for pitch black pixel values
black_pixels_mask = cv2.inRange(img_bgr, np.array([0, 0, 0]), np.array([70, 70, 70]))
# Get the mask for extreme white pixels.
white_pixels_mask = cv2.inRange(img_bgr, np.array([230, 230, 230]), np.array([255, 255, 255]))
final_mask = cv2.max(bgd_mask, black_pixels_mask)
final_mask = cv2.min(final_mask, ~white_pixels_mask)
final_mask = ~final_mask
final_mask = cv2.erode(final_mask, np.ones((3, 3), dtype=np.uint8))
final_mask = cv2.dilate(final_mask, np.ones((5, 5), dtype=np.uint8))
# Now you can finally find contours.
im, contours, hierarchy = cv2.findContours(final_mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
final_contours = []
for contour in contours:
area = cv2.contourArea(contour)
if area > 2000:
final_contours.append(contour)
for i in xrange(len(final_contours)):
img_bgr = cv2.drawContours(img_bgr, final_contours, i, np.array([50, 250, 50]), 4)
debug_img = img_bgr
debug_img = cv2.resize(debug_img, None, fx=0.3, fy=0.3)
cv2.imwrite("./out.png", debug_img)

Categories