Is there a way I can remove other components in the image except the beans using OpenCV python? - image-processing

I have been trying to remove every other component from the image except the beans. I have tried using edging and contour I couldn't get it right.
enter image description here

If the "beans" are the dark region, then here is one way in Python OpenCV.
- Read the input
- Threshold on the blue color
- Apply morphology close to clean it up a little
- Invert
- Find the largest contour
- Draw a white filled contour on a black background as a mask
- Use the mask to make everything in the input black except the "beans"
- Save the results
Input:
import cv2
import numpy as np
# load image
img = cv2.imread("beans.jpg")
lower =(80,70,30)
upper = (220,220,180)
# create the mask and use it to change the colors
thresh = cv2.inRange(img, lower, upper)
# apply morphology
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# invert
morph = 255 - morph
# find largest contour
contours = cv2.findContours(morph, 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 filled contour on black background as mask
mask = np.zeros_like(thresh)
cv2.drawContours(mask, [big_contour], 0, 255, -1)
# apply mask to img
result = img.copy()
result[mask==0] = (0,0,0)
# write result to disk
cv2.imwrite("beans_thresh2.png", thresh)
cv2.imwrite("beans_morph2.png", morph)
cv2.imwrite("beans_mask2.png", mask)
cv2.imwrite("beans_result2.png", result)
# display it
cv2.imshow("thresh", thresh)
cv2.imshow("morph", morph)
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold image:
Morphology cleaned image:
Mask Image:
Result Image:

Related

How to calculate diameter at multiple points of an object using image pixels?

I am trying to get diameters on different points of a cylinder over a certain length using computer vision to replace the use of optical micrometer.
Image of a cylinder:
How can I calculate the diameter of this object (cylinder) on multiple points (blue lines) along its length as shown in the image using OpenCV python?
An OpenCV solution. The main idea is to:
Detect edges
Find the contours of the edges
Fill in the contour areas
Go through each column in the image and count the nonzero pixels
1., 2. and 3. could possibly be simplified by a single thresholding step depending on your use case
import numpy as np
import cv2
src = cv2.imread('/path/to/src.jpg')
mask = np.zeros(src.shape, dtype=np.uint8)
w, h, c = src.shape
# edge detection
threshold = 100
gray = cv2.Canny(src, threshold, threshold * 2)
cv2.imshow('', gray)
cv2.waitKey(0)
# find contours
cnts = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
threshold_area = 0.5
# fill area withing contours with white color
for c in cnts:
area = cv2.contourArea(c)
if area > threshold_area:
cv2.drawContours(mask, [c], -1, (255, 255, 255), -1)
cv2.imshow('', mask)
cv2.waitKey(0)
# get non zero values (height) of each column
column_pixels = [cv2.countNonZero(mask[:, i]) for i in range(0, w)]
print(column_pixels)
Src image:
Canny result:
After filling in contours with white color:
countNonZero is applied on this last image for each column

Blurred Circle detection

I am new to opencv and want to detect the center point of these circles. I tried with Hough Circles with thresholding but it doesn't seem to generate good results all the time.
This image is easy to get using contours and threshloding:
It is harder to do this one:
The thresholding and Hough circle doesn't work with this image:
Adding more images for help
Can you suggest any method that will be reliable for all the images?
Since the circle is the only bright thing in the image, we can get the center by looking for the centroid of the white blob. We'll auto-threshold with otsu's and use findContours to get the centroid of the mask.
import cv2
import numpy as np
# load image
img = cv2.imread("circ1.png");
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY);
# threshold
gray = cv2.GaussianBlur(gray, (5,5), 0);
_, mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU);
# contour
_, contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
# center
M = cv2.moments(contours[0]);
cx = int(M['m10']/M['m00']);
cy = int(M['m01']/M['m00']);
center = (int(cx), int(cy));
# draw
img = cv2.circle(img, center, 4, (0,0,200), -1);
# show
cv2.imshow("marked", img);
cv2.imshow("mask", mask);
cv2.waitKey(0);

How to detect edges for pattern on space?

I'm building a new machine and have a problem with edge detection. I have a piece of paper put on one cylinder as shown in the image below. How to detect edges of this paper?
I attempted to build a led background but this paper is very large. Therefore, my machine don't have enough spaces to run.
You can use below code as a reference. Here, I am basically using cv2.inRange function to segment light green color from the image(not dark green, otherwise edge of one of the axis will also be detected) and finally applying Canny edge detection on the grayscaled version of the segmented image, i.e., cv2.Canny.
import cv2
import numpy as np
img = cv2.imread('cylinder.png')
# convert to HSV color space
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Threshold the HSV image to get green colors by defining range of green color in HSV
mask = cv2.inRange(img_hsv, (36,0,0), (55,255,255))
# Bitwise-AND mask and original image
res = cv2.bitwise_and(img, img, mask = mask)
# coverting image with green colored region of interest from HSV to RGB
img_hsv2bgr = cv2.cvtColor(res, cv2.COLOR_HSV2BGR)
# coverting image from RGB to GRAYSCALE
img_gray = cv2.cvtColor(img_hsv2bgr, cv2.COLOR_BGR2GRAY)
# canny edge detection
edges = cv2.Canny(img_gray, 100, 200)
cv2.imshow('Edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
output:
EDIT: After making some modification to above code, like segmenting yellow color portion along with light green color and applying Gaussian blurring before passing into the cv2.Canny function gives even better output than above.
Code:
# Threshold the HSV image to get both green and yellow colors by defining range of color in HSV
mask_green = cv2.inRange(img_hsv, (36,0,0), (55,255,255))
mask_yellow = cv2.inRange(img_hsv, (21, 39, 64), (38, 255, 255))
mask = cv2.bitwise_or(mask_green, mask_yellow)
# Bitwise-AND mask and original image
res = cv2.bitwise_and(img, img, mask = mask)
# coverting image with green colored region of interest from HSV to RGB
frame_hsv2bgr = cv2.cvtColor(res, cv2.COLOR_HSV2BGR)
# coverting image from RGB to GRAYSCALE
frame_gray = cv2.cvtColor(frame_hsv2bgr, cv2.COLOR_BGR2GRAY)
gaussian_blurred = cv2.GaussianBlur(frame_gray,(5, 3), 0)
# canny edge detection
edges = cv2.Canny(gaussian_blurred, 100, 200)
output:

openCV problem with detecting contours of shapes fully

I am doing this university project where i try to detect UI elements on screenshots of Android applications using openCV. I am not expecting a 100 percent accuracy for this detection of UI elements.
This is my code below. I convert the image to gray scale, apply Gaussian blur and then use adaptive threshold to convert the image to binary. After which i use the find contours method.
ap = argparse.ArgumentParser()
ap.add_argument("-i","--image", help = "path to an image", required =
True)
args = vars(ap.parse_args())
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray",gray)
cv2.waitKey(0)
blurred = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.adaptiveThreshold(blurred, 255,
cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 4)
cv2.imshow("thresh",thresh)
cv2.waitKey(0)
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cv2.drawContours(image, cnts, -1, (0,255,0), 1)
cv2.imshow("contours", image)
cv2.waitKey(0)
for c in cnts:
area = cv2.contourArea(c)
print(area)
if area > 50:
M = cv2.moments(c)
cX = int(M['m10'] / M['m00'])
cY = int(M['m01'] / M['m00'])
#cv2.drawContours(image, [c], -1, (0,255,0), 2) # draw contours on image
(x,y,w,h) = cv2.boundingRect(c) # for each contour get a
bounding rectangle
mask = np.zeros(image.shape[:2], dtype = "uint8") # find
shape of the image dimensions and set up a mask
mask[y: y + h, x: x + w] = 255 # convert region of
interest into white
to_display = cv2.bitwise_and(image,image, mask = mask) # carry
out bitwise and
#cv2.putText(image, 'center', (c))
cv2.imshow("Image", to_display)
cv2.waitKey(0)
this is the screenshot that i am running my code on.
The leftmost screenshot represents the image after applying a threshold to it.
The middle image represents the image i get after drawing the contours.
The last image shows when i am examining each individual contour. The contour covers the line but does not encapsulate the rectangle.
I have a few questions.
1) Is it possible to sieve out the contours for the white rectangles. What alteration do i have to make to my code to be able to achieve this?
2) I am trying to sieve out the unimportant contours eg. the words and I was thinking if i could use the getArea() function to help me with it. The idea is that i would set a minimum contour size to filter out the smaller contours that account for the words.
This is another image that i have tried to identify the "objects" in this screenshots.
I face the same issue here where i cant identify the white rectangles. I am only identifying the borders of the rectangle.
Would appreciate any form of help as I am still new to openCv
Original images before processing:
There is no need to blur. In fact I makes it harder. Simple thresholding works best with hard transitions. The second image is easiest. There are white items on a grayish background. By selecting only very white values the items are selected.
Result:
Code:
# load image
img = cv2.imread("app.png")
# convert to gray
img2 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# crate a mask that hold only white values (above 250)
ret,thresh1 = cv2.threshold(img2,250,255,cv2.THRESH_BINARY)
# find contours in mask
im2, contours, hierarchy = cv2.findContours(thresh1, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# select large contours (menu items only)
for cnt in contours:
print(cv2.contourArea(cnt))
if cv2.contourArea(cnt) > 5000:
# draw a rectangle around the items
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0),3)
#cv2.drawContours(img, [cnt], 0, (0,255,0), 3) #also works, but has issues with letters at the last item
#show image
cv2.imshow("img", img)
#cv2.imshow("mask", thresh) # shows mask
cv2.waitKey(0)
cv2.destroyAllWindows()
The first image is more complex, because it is divided in by a very thin red line. Selecting colors is easier in HSV colorspace. Next red values are used to create a mask, some noise is removed and then contours are detected.
Result:
# load image
img = cv2.imread("app2.png")
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# set lower and upper color limits
lower_val = np.array([0,0,0])
upper_val = np.array([20,50,255])
# Threshold the HSV image
mask = cv2.inRange(hsv, lower_val, upper_val)
# remove noise
kernel = np.ones((1,2),np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
kernel = np.ones((1,5),np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# find contours in mask
im2, contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# select large contours (menu items only)
for cnt in contours:
print(cv2.contourArea(cnt))
if cv2.contourArea(cnt) > 1000:
# draw a rectangle around the items
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0),3)
#show image
cv2.imshow("img", img)
cv2.imshow("mask", mask)
cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV - Extracting lines on a graph

I would like to create a program that is able to extract lines from a graph.
For example, if a graph like this is inputted, I would just want the red line to be outputted.
Below I have tried to do this using a hough line transformation, however, I do not get very promising results.
import cv2
import numpy as np
graph_img = cv2.imread("/Users/2020shatgiskessell/Desktop/Graph1.png")
gray = cv2.cvtColor(graph_img, cv2.COLOR_BGR2GRAY)
kernel_size = 5
#grayscale image
blur_gray = cv2.GaussianBlur(gray,(kernel_size, kernel_size),0)
#Canny edge detecion
edges = cv2.Canny(blur_gray, 50, 150)
#Hough Lines Transformation
#distance resoltion of hough grid (pixels)
rho = 1
#angular resolution of hough grid (radians)
theta = np.pi/180
#minimum number of votes
threshold = 15
#play around with these
min_line_length = 25
max_line_gap = 20
#make new image
line_image = np.copy(graph_img)
#returns array of lines
lines = cv2.HoughLinesP(edges, rho, theta, threshold, np.array([]),
min_line_length, max_line_gap)
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),2)
lines_edges = cv2.addWeighted(graph_img, 0.8, line_image, 1, 0)
cv2.imshow("denoised image",edges)
if cv2.waitKey(0) & 0xff == 27:
cv2.destroyAllWindows()
This produces the output image below, which does not accurately recognize the graph line. How might I go about doing this?
Note: For now, I am not concerned about the graph titles or any other text.
I would also like the code to work for other graph images aswell, such as:
etc.
If the graph does not have many noises around it (like your example) I would suggest to threshold your image with Otsu threshold instead of looking for edges . Then you simply search the contours, select the biggest one (graph) and draw it on a blank mask. After that you can perform a bitwise operation on image with the mask and you will get a black image with the graph. If you like the white background better, then simply change all black pixels to white. Steps are written in the example. Hope it helps a bit. Cheers!
Example:
import numpy as np
import cv2
# Read the image and create a blank mask
img = cv2.imread('graph.png')
h,w = img.shape[:2]
mask = np.zeros((h,w), np.uint8)
# Transform to gray colorspace and threshold the image
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# Search for contours and select the biggest one and draw it on mask
_, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
cv2.drawContours(mask, [cnt], 0, 255, -1)
# Perform a bitwise operation
res = cv2.bitwise_and(img, img, mask=mask)
# Convert black pixels back to white
black = np.where(res==0)
res[black[0], black[1], :] = [255, 255, 255]
# Display the image
cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
EDIT:
For noisier pictures you could try this code. Note that different graphs have different noises and may not work on every graph image since the denoisiation process would be specific in every case. For different noises you can use different ways to denoise it, for example histogram equalization, eroding, blurring etc. This code works well for all 3 graphs. Steps are written in comments. Hope it helps. Cheers!
import numpy as np
import cv2
# Read the image and create a blank mask
img = cv2.imread('graph.png')
h,w = img.shape[:2]
mask = np.zeros((h,w), np.uint8)
# Transform to gray colorspace and threshold the image
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# Perform opening on the thresholded image (erosion followed by dilation)
kernel = np.ones((2,2),np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
# Search for contours and select the biggest one and draw it on mask
_, contours, hierarchy = cv2.findContours(opening,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
cv2.drawContours(mask, [cnt], 0, 255, -1)
# Perform a bitwise operation
res = cv2.bitwise_and(img, img, mask=mask)
# Threshold the image again
gray = cv2.cvtColor(res,cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# Find all non white pixels
non_zero = cv2.findNonZero(thresh)
# Transform all other pixels in non_white to white
for i in range(0, len(non_zero)):
first_x = non_zero[i][0][0]
first_y = non_zero[i][0][1]
first = res[first_y, first_x]
res[first_y, first_x] = 255
# Display the image
cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:

Resources