Reputation: 76
I've been researching this for a few hours, and while I know the answer is out there, I just can't understand it. I'm working on a hobby project and this is slightly over my head. What I'm trying to do is detect a red dot in an image and return the x/y coordinate of the center of the dot. For testing purposes I created a jpg image that is all white except for one red dot. I set up color constraints such that anything between 0,0,150 - 90,90,255 (BGR) will be considered a match for red. From my understanding inRange will return something like an array containing 0 or 255 for each pixel. What I can't figure out how to do is to find the center of the largest blob and return the x,y coordinate. Any help is appreciated, and the more ELI5 the better! Thanks much for your time.
I've been following along with a tutorial I found which seems a good start for the program, but here's where i'm stuck.
# import the necessary packages
import numpy as np
import argparse
import cv2
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", help = "path to the image")
args = vars(ap.parse_args())
# load the image
image = cv2.imread(args["image"])
# define the list of boundaries
boundaries = [
([0, 0, 150], [90, 90, 255]),
]
# loop over the boundaries
for (lower, upper) in boundaries:
# create NumPy arrays from the boundaries
lower = np.array(lower, dtype = "uint8")
upper = np.array(upper, dtype = "uint8")
# find the colors within the specified boundaries
mask = cv2.inRange(image, lower, upper)
#find contour
contours = cv2.findContours(mask, 1, 2)
cnt = contours[0]
M = cv2.moments(cnt)
#save x,y center
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
print cx
print cy
Currently code returns error: M = cv2.moments(cnt) TypeError: array is not a numpy array, neither a scalar
I'm sure this makes perfect sense to you, but as I'm hacking this together I am lost.
Upvotes: 0
Views: 5089
Reputation: 668
I had a similar error to this and found a solution.
First of all I found that cnt = contours[1]
worked over cnt = contours[0]
since cv version has changed to 3.0
Next this would return us a list still so we can do c[0]
to access the first contour and the following would work
contours = cv2.findContours(mask, 0, 2)
c = contours[0]
M = cv2.moments(c)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
hopefully this helps!
Upvotes: 0
Reputation: 339200
You can use the numpy.ndimage
capabilities for your purpose. Starting at the point where you have used cv2.inRange
to extract the interesting regions in the image, you can use ndimage.label()
to separate those regions and then find their respective center of mass and sum. The label with the largest sum will be the one we're looking for and its center of mass can be printed.
Mind that the notation of the output is in image coordinates, i.e. vertical coordinate comes first.
import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
import cv2
# load the image
image = cv2.imread("image2993.png")
# set boundaries for colors (BGR)
lowerb = np.array([0, 0, 150], np.uint8)
upperb = np.array([90, 90, 255], np.uint8)
frame = cv2.inRange(image, lowerb, upperb)
blobs = frame > 100
labels, nlabels = ndimage.label(blobs)
# find the center of mass of each label
t = ndimage.center_of_mass(frame, labels, np.arange(nlabels) + 1 )
# calc sum of each label, this gives the number of pixels belonging to the blob
s =ndimage.sum(blobs, labels, np.arange(nlabels) + 1 )
# print the center of mass of the largest blob
print t[s.argmax()] # notation of output (y,x)
Upvotes: 1
Reputation: 76
Well, I spent a few more hours on this and came up with a temporary novel solution. It still doesn't address finding the largest blob, but it works for now. Uses numpy to take the average of the contour list. I suppose there's probably a way to compare the list sizes within the array then choose the largest, or something similar, although I can see already this is not the ideal way to do it.
# import the necessary packages
import numpy as np
import argparse
import cv2
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", help = "path to the image")
args = vars(ap.parse_args())
# load the image
image = cv2.imread(args["image"])
# define the list of boundaries
boundaries = [
([0, 0, 150], [90, 90, 255])
]
# loop over the boundaries
for (lower, upper) in boundaries:
# create NumPy arrays from the boundaries
lower = np.array(lower, dtype = "uint8")
upper = np.array(upper, dtype = "uint8")
# find the colors within the specified boundaries
mask = cv2.inRange(image, lower, upper)
#find contour
contours = cv2.findContours(mask, 0, 2)
avg = np.mean(contours[0],axis=1)
x = int(round(avg[0,0,0]))
y = int(round(avg[0,0,1]))
print x,y
Maybe someone else will find this helpful in some way. Run with filename.py -i c:\pathto\file.jpg
Upvotes: 0