I have a picture with 9 water droplets that have different color intensities (i.e. they are all green, but different shades of green). The goal is to:
Identify 9 drops
Find relevant values (size, location, RGB values, etc.)
Plot data
I am using SimpleBlobDetector to identify the dots. This outputs the keypoints, which contains relevant information about each blob.
However, I do not know how to access the RGB (or HSV) values for the specific blob. How do you search only the pixels in the blob to determine min/max/avg color values?
Any advice is greatly appreciated!
Here is my full code. It just prints the x_position, y_position, and area of each blob. I've also attached the file I am using:
# Standard imports
import cv2
import numpy as np
from matplotlib import pyplot as plt
# Read image
filename= "C:\Users\Kevin\Pictures\Far 3.jpg"
img = cv2.imread(filename, 0)
img_color = cv2.imread(filename, cv2.IMREAD_ANYCOLOR)
img_c = cv2.resize(img_color,(800,600))
img1 = cv2.resize(img,(800,600))
ret,im = cv2.threshold(img1,120,255,cv2.THRESH_BINARY)
#######################################################
#######################################################
# Setup SimpleBlobDetector parameters.
params = cv2.SimpleBlobDetector_Params()
# Change thresholds
params.minThreshold = 50
params.maxThreshold = 150
# Filter by Area.
params.filterByArea = True
params.minArea = 150
params.maxArea = 400
# Filter by Circularity
params.filterByCircularity = True
params.minCircularity = 0.2
# Filter by Convexity
params.filterByConvexity = True
params.minConvexity = 0.1
# Filter by Inertia
params.filterByInertia = True
params.minInertiaRatio = 0.01
detector = cv2.SimpleBlobDetector_create(params)
#######################################################
#######################################################
# Detect blobs.
keypoints = detector.detect(im)
# Draw detected blobs as red circles.
# cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures the size of the circle corresponds to the size of blob
im_with_keypoints = cv2.drawKeypoints(img_c, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# Show keypoints
cv2.imshow("Keypoints", im_with_keypoints)
x=[]
y=[]
area=[]
for i in xrange(9):
xx = keypoints[i].pt[0]
yy = keypoints[i].pt[1]
aarea = keypoints[i].size
print "PT.%f -- " %i, "x = %f," %xx, "y = %f," %yy,"area = %f," %aarea, "\n"
#######################################################
#######################################################
cv2.waitKey(0)
For each pixel in img, the B,G,R values can be read as:
B=img[xx,yy,0]
G=img[xx,yy,1]
R=img[xx,yy,2]
You can take the average B,G,R value of all the pixels in a blob and then find the maximum value among the blobs
Related
I have a very simple task which is detecting some very obvious blobs. I am very new to image processing, I am probably making a very simple mistake. I prepared a blob detection function by using this example and I am using this function on an artificial image. My original task is to detect blob in a much complex image but I thought I can start small. Originally I have a gray image with small whitish(less gray) dirts over it. Anyways my problem is I cannot detect any blobs. I think I am failing on adjusting the parameters. Although I tried many different combinations, I couldn't get any result. Here is my simpler artificial image:
Here is my code
import matplotlib.pyplot as plt
import numpy as np
import cv2
def normalizeImage(image):
print("Normalizing image..")
min = np.min(image)
image = image-min #to have only positive values
max=np.max(image)
div=max/255 #calculate the normalize divisor
image_8u = np.uint8(np.round(image / div))
return image_8u
def detectBlobs(dustImage):
print("Detecting blobs...")
pp = cv2.SimpleBlobDetector_Params()
pp.filterByArea = True# Set Area filtering parameters
#minDiameter = 1
#maxDiameter = 5
pp.minArea = 100 #3.14159 * minDiameter * minDiameter
#pp.maxArea = 3.14159 * maxDiameter * maxDiameter
pp.filterByCircularity = True # Set Circularity filtering parameters
pp.minCircularity = 0.9
pp.filterByConvexity = True # Set Convexity filtering parameters
pp.minConvexity = 0.2
pp.filterByInertia = True # Set inertia filtering parameters
pp.minInertiaRatio = 0.1
detector = cv2.SimpleBlobDetector_create(pp) # Create a detector with the parameters
keypoints = detector.detect(dustImage) # Detect blobs
numberOfBlobs = len(keypoints)
if(numberOfBlobs > 0):
print(numberOfBlobs + "blobs detected!")
blank = np.zeros((1, 1))# Draw blobs on our image as red circles
blobs = cv2.drawKeypoints(dustImage, keypoints, blank, (0, 0, 255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
text = "Number of Circular Blobs: " + str(len(keypoints))
cv2.putText(blobs, text, (20, 550), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 100, 255), 3)
# Show blobs
cv2.namedWindow('img', cv2.WINDOW_NORMAL)
cv2.resizeWindow("img", 900 ,900)
cv2.imshow("img", blobs)
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print('No blobs detected')
return numberOfBlobs
if __name__ == '__main__':
img0 = plt.imread('artificial.png')
#plt.imshow(img0)
#plt.show()
img1 = normalizeImage(img0)
img2 = cv2.cvtColor(img1, cv2.CV_8UC1)
detectBlobs(img2)
exit()
I am trying to extract a signature from a document using some code that I found on GIT.
The error is as follows: "ValueError: Image RGB array must be uint8 or floating point; found int32"
No matter what image I use, I get the error. I am a novice at image extraction so any help would be appreciated.
The code used:
def extract_signature(source_image):
"""Extract signature from an input image.
Parameters
----------
source_image : numpy ndarray
The pinut image.
Returns
-------
numpy ndarray
An image with the extracted signatures.
"""
# read the input image
img = source_image
img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)[1] # ensure binary
# connected component analysis by scikit-learn framework
blobs = img > img.mean()
blobs_labels = measure.label(blobs, background=1)
# image_label_overlay = label2rgb(blobs_labels, image=img)
fig, ax = plt.subplots(figsize=(10, 6))
'''
# plot the connected components (for debugging)
ax.imshow(image_label_overlay)
ax.set_axis_off()
plt.tight_layout()
plt.show()
'''
the_biggest_component = 0
total_area = 0
counter = 0
average = 0.0
for region in regionprops(blobs_labels):
if (region.area > 10):
total_area = total_area + region.area
counter = counter + 1
# print region.area # (for debugging)
# take regions with large enough areas
if (region.area >= 250):
if (region.area > the_biggest_component):
the_biggest_component = region.area
average = (total_area/counter)
print("the_biggest_component: " + str(the_biggest_component))
print("average: " + str(average))
# experimental-based ratio calculation, modify it for your cases
# a4_small_size_outliar_constant is used as a threshold value to remove connected outliar connected pixels
# are smaller than a4_small_size_outliar_constant for A4 size scanned documents
a4_small_size_outliar_constant = ((average/constant_parameter_1)*constant_parameter_2)+constant_parameter_3
print("a4_small_size_outliar_constant: " + str(a4_small_size_outliar_constant))
# experimental-based ratio calculation, modify it for your cases
# a4_big_size_outliar_constant is used as a threshold value to remove outliar connected pixels
# are bigger than a4_big_size_outliar_constant for A4 size scanned documents
a4_big_size_outliar_constant = a4_small_size_outliar_constant*constant_parameter_4
print("a4_big_size_outliar_constant: " + str(a4_big_size_outliar_constant))
# remove the connected pixels are smaller than a4_small_size_outliar_constant
pre_version = morphology.remove_small_objects(blobs_labels, a4_small_size_outliar_constant)
# remove the connected pixels are bigger than threshold a4_big_size_outliar_constant
# to get rid of undesired connected pixels such as table headers and etc.
component_sizes = np.bincount(pre_version.ravel())
too_small = component_sizes > (a4_big_size_outliar_constant)
too_small_mask = too_small[pre_version]
pre_version[too_small_mask] = 0
# save the the pre-version which is the image is labelled with colors
# as considering connected components
plt.imsave('pre_version.png', pre_version)
# read the pre-version
img = cv2.imread('pre_version.png', 0)
# ensure binary
img = cv2.threshold(img, 0, 255,
cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
# save the the result
# cv2.imwrite("output.png", img)
return img
If anyone has any suggestions on tips on what to change or improve, that would be greatly appreciated.
I have written the following script with which I aim to detect lines in Gazebo (a simulation environment):
#!/usr/bin/env python
# rospy for the subscriber
import rospy
# ROS Image message
from sensor_msgs.msg import Image
# ROS Image message -> OpenCV2 image converter
from cv_bridge import CvBridge, CvBridgeError
# OpenCV2 for saving an image
import cv2
import matplotlib.pyplot as plt
import numpy as np
def gradient(img):
# grayscale the image
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# gaussian blur of image with a 5x5 kernel
gauss = cv2.GaussianBlur(gray,(5,5),0)
# Return the canny of the image
return cv2.Canny(gauss,20,30)
def region_of_interest(img):
# Height of image (number of rows)
height = img.shape[0]
# Width of the image (number of columns)
width = img.shape[1]
# Create an array of polygons to use for the masking of the canny image
polygons = np.array([
[(200,height), (200,500), (600,500), (600,height)]
])
# Create the mask image's background (black color)
mask_bg = np.zeros_like(img)
# Create the mask image (image with black background an white region of interest)
mask = cv2.fillPoly(mask_bg, polygons, 255)
# Isolate the area of interest using the bitwise operator of the mask and canny image
masked_image = cv2.bitwise_and(img,cv2.fillPoly(mask_bg, polygons, 255))
# Return the updated image
return masked_image
def make_coordinates(img, line_parameters):
# Extract the average slope and intercept of the line
slope, intercept = line_parameters
# Coordinate y(1) of the calculated line
y1 = img.shape[0]
# Coordinate y(2) of the calculated line
y2 = int(y1*0.5)
# Coordinate x(1) of the calculated line
x1 = int((y1-intercept)/slope)
# Coordinate x(2) of the calculated line
x2 = int((y2-intercept)/slope)
# Return the coordinates of the average line
return np.array([x1,y1,x2,y2])
def average_slope_intercep(img,lines):
# Create an empty list containing the coordinates of the detected line
line_fit = []
# Loop through all the detected lines
for line in lines:
# Store the coordinates of the detected lines into an 1D array of 4 elements
x1,y1,x2,y2 = line.reshape(4)
# Create a line y = mx+b based on the coordinates
parameters = np.polyfit((x1,x2),(y1,y2),1)
# Extract the slope m
slope = parameters[0]
# Extract the intercept b
intercept = parameters[1]
# Add elements on the list
line_fit.append((slope,intercept))
# Check slope of line
# if slope < 0:
# continue
# else:
# continue
# Calculate the average of the line fit parameters list
line_fit_average = np.average(line_fit,axis=0)
# Extract the coordinates of the calculated line
main_line = make_coordinates(img,line_fit_average)
return np.array([main_line])
def display_lines(img,lines):
# Create a mask image that will have the drawn lines
line_image = np.zeros_like(img)
# If no lines were detected
if lines is not None:
# Loop through all the lines
for line in lines:
# Store the coordinates of the first and last point of the lines into 1D arrays
x1, y1, x2, y2 = line.reshape(4)
# Draw the lines on the image with blue color and thicknes of 10
cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),10)
# Return the mask image with the drawn lines
return line_image
def image_callback(msg):
# print("Received an image!")
# Instantiate CvBridge
bridge = CvBridge()
try:
# Convert your ROS Image message to OpenCV2
frame = bridge.imgmsg_to_cv2(msg, "bgr8")
except CvBridgeError, e:
print(e)
else:
# Copy of the original frame
frame_copy = np.copy(frame)
# Canny of image
canny_frame = gradient(frame_copy)
# Apply mask in region of interest
cropped_image = region_of_interest(canny_frame)
# Apply Hough Transform on the region of interest
lines = cv2.HoughLinesP(cropped_image,1,np.pi/180,30,np.array([]),minLineLength=10,maxLineGap=2)
# Calculate the average slope of the detected lines
averaged_lines = average_slope_intercep(frame_copy,lines)
# Create a mask image with the drawn lines
line_image = display_lines(frame_copy,averaged_lines)
# Plot lines on the camera feed frame
combo_image = cv2.addWeighted(frame_copy,0.8,line_image,1,1)
#Show manipulated image feed
cv2.imshow("Result feed", frame_copy)
# plt.imshow(canny_frame)
cv2.waitKey(1)
# plt.show()
def main():
rospy.init_node('image_listener')
# Define your image topic
image_topic = "rover/camera1/image_raw"
# Set up your subscriber and define its callback
rospy.Subscriber(image_topic, Image, image_callback)
# Spin until ctrl + c
rospy.spin()
cv2.destroyAllWindows()
if __name__ == '__main__':
main()
The code is integrated in ROS, so please focus your attention at the image_callback function. My issue is that the line that I want to detect is quite noisy and I cannot figure out how to detect it correctly.
To be more specific, from the following frame,
Original Frame
I get this image after gaussian blur and the canny algorithm,
Canny Frame
How could I filter the "noise" I see in the canny frame? I played a lot with the canny and gausian blur parameters but all that I have achieved is removing gradients instead of actually making it less "noisy".
This method might help you to remove noise from the frame.
import cv2
import numpy as np
from skimage.morphology import skeletonize
def get_skeleton_iamge(threshold_image):
skeleton = skeletonize(threshold_image / 255)
skeleton = skeleton.astype(np.uint8)
skeleton *= 255
return skeleton
image = cv2.imread("road.png", 0)
image = cv2.resize(image, (300, 300))
bilateral = cv2.bilateralFilter(image, 15, 100, 100)
cv2.imshow("bilateral_image", bilateral)
canny_image = cv2.Canny(bilateral, 20, 30)
cv2.imshow("canny_image", canny_image)
kernel = np.ones((10, 10))
dilate_image = cv2.dilate(canny_image, kernel, iterations=1)
erode_image = cv2.erode(dilate_image, kernel, iterations=1)
cv2.imshow("erode_image", erode_image)
skeleton_iamge = get_skeleton_iamge(erode_image)
cv2.imshow("skeleton_iamge", skeleton_iamge)
cv2.waitKey(0)
I'm trying to make a general object counting algorithm using python and openCV (open to try other methods) however I can't seem to get a good count on a variety of objects and don't know how to accomodate for that
https://imgur.com/a/yAkRxWH are some example test images.
This is for to speed up inventory counting of smaller objects.
**EDIT
This is my current code (simple blob detector)
# Standard imports
import cv2
import numpy as np;
# Read image
im = cv2.imread("./images/screw_simple.jpg", cv2.IMREAD_GRAYSCALE)
im = cv2.resize(im, (1440, 880))
# Setup SimpleBlobDetector parameters.
params = cv2.SimpleBlobDetector_Params()
# Change thresholds
params.minThreshold = 10 #10
params.maxThreshold = 200 #200
# Filter by Area.
params.filterByArea = True # True
params.minArea = 500 #1500
# Filter by Circularity
params.filterByCircularity = True #True
params.minCircularity = 0.1 #0.1
# Filter by Convexity
params.filterByConvexity = True #True
params.minConvexity = 0.0 #0.87
# Filter by Inertia
params.filterByInertia = True #True
params.minInertiaRatio = 0.0 #0.01
# Create a detector with the parameters
ver = (cv2.__version__).split('.')
if int(ver[0]) < 3:
detector = cv2.SimpleBlobDetector(params)
else:
detector = cv2.SimpleBlobDetector_create(params)
# Detect blobs.
keypoints = detector.detect(im)
# Draw detected blobs as red circles.
# cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures
# the size of the circle corresponds to the size of blob
total_count = 0
for i in keypoints:
total_count = total_count + 1
im_with_keypoints = cv2.drawKeypoints(im, keypoints, np.array([]), (0, 0, 255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# Show blobs
cv2.imshow("Keypoints", im_with_keypoints)
cv2.waitKey(0)
print(total_count)
Here are the results I'm getting: https://imgur.com/a/id6OlIA
How can I improve this algorithm to get better detection for a general use case of objects without having to modify the parameters each time for each object?
You can try with an OpenCV approach, you could use a
SimpleBlobDetector
Obviously this is a test image and the result I got is also not perfect, since there are a lot of hyperparameters to set. The hyperparameters make it pretty flexible, so it is a decent place to start from.
This is what the Detector does (see details here):
Thresholding: Convert the source images to several binary images by thresholding the source image with thresholds starting at minThreshold. These thresholds are incremented by thresholdStep until maxThreshold. So the first threshold is minThreshold, the second is minThreshold + thresholdStep, the third is minThreshold + 2 x thresholdStep, and so on.
Grouping: In each binary image, connected white pixels are grouped together. Let’s call these binary blobs.
Merging: The centers of the binary blobs in the binary images are computed, and blobs located closer than minDistBetweenBlobs are merged.
Center & Radius Calculation: The centers and radii of the new merged blobs are computed and returned.
Find the code bellow the image.
# Standard imports
import cv2
import numpy as np
# Read image
im = cv2.imread("petri.png", cv2.IMREAD_COLOR)
# Setup SimpleBlobDetector parameters.
params = cv2.SimpleBlobDetector_Params()
# Change thresholds
params.minThreshold = 0
params.maxThreshold = 255
# Set edge gradient
params.thresholdStep = 5
# Filter by Area.
params.filterByArea = True
params.minArea = 10
# Set up the detector with default parameters.
detector = cv2.SimpleBlobDetector_create(params)
# Detect blobs.
keypoints = detector.detect(im)
# Draw detected blobs as red circles.
# cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures the size of the circle corresponds to the size of blob
im_with_keypoints = cv2.drawKeypoints(im, keypoints, np.array([]), (0, 0, 255),
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# Show keypoints
cv2.imshow("Keypoints", im_with_keypoints)
cv2.waitKey(0)
For better readability I rather put this in a second answer: You could
use a segmentation approach e.g. watershed algorithm
Any grayscale image can be viewed as a topographic surface where high intensity denotes peaks and hills while low intensity denotes valleys. You start filling every isolated valleys (local minima) with different colored water (labels). As the water rises, depending on the peaks (gradients) nearby, water from different valleys, obviously with different colors will start to merge. To avoid that, you build barriers in the locations where water merges. You continue the work of filling water and building barriers until all the peaks are under water. Then the barriers you created gives you the segmentation result. This is the "philosophy" behind the watershed.
I have some colored rectangles in my image that I successfully recognize by HSV thresholding. The result looks like this:
Now I want to detect the big blob as one point. I tried it with cv2.SimpleBlobDetector() and custom parameters:
import cv2
import numpy as np
mask = cv2.imread('mask.png')
original = cv2.imread('original.png')
params = cv2.SimpleBlobDetector_Params()
# thresholds
params.minThreshold = 10
params.maxThreshold = 200
#params.thresholdStep = 20
# filter by area
params.filterByArea = True
params.minArea = 1
params.maxArea = 10000
# filter by circularity
params.filterByCircularity = False
# filter by convexity
params.filterByConvexity = False
# filter by inertia
params.filterByInertia = False
detector = cv2.SimpleBlobDetector(params)
keypoints = detector.detect(mask)
img_keypoints = cv2.drawKeypoints(original, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imwrite('keypoints.png', img_keypoints)
This is how the result AND the original picture looks like:
I would expect a red point sitting in the center of the green point.
How can I fix this? Help is very appreciated.
EDIT: I forgot to mention: Is it really necessary to generate multiple binary images in cv2.SimpleBlobDetector() since I already have a binary image as input? Is it OK to change the values to the following:
params.minThreshold = 127
params.maxThreshold = 127
to reduce unnecessary CPU usage by generating binary images?
EDIT2 : Please note that I'm using OpenCV 2, not 3
Thank you.
When using cv2.SimpleBlobDetector(), it looks for blobs that are of a darker shade. In your case, the rectangle in mask is in white while the rest of the image is dark. As a result it is unable to find any blobs for the custom parameters set.
I just made a few changes to the existing code:
Read the mask as a grayscale image not a color image:
mask = cv2.imread('mask.png', 0)
Convert the mask to a binary image with the rectangle highlighted in dark:
ret, mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY_INV)
Proceeding from here using you code gave the following result as you expected.
Result: