作为我正在研究的项目的一部分,我需要使用OpenCV和Python在图像中找到一些“blob”的中心点。 我遇到了一些麻烦,真的很感激任何帮助或见解:)
我目前的方法是:获取图像的轮廓,在这些图像上叠加椭圆,使用斑点检测器找到每个图像的中心。 这种效果相当不错,但偶尔我还需要忽略无关的斑点,有时斑点会相互碰触。
这是一个很好的例子:
良好的源图片
提取轮廓后
检测到blob:
当它变得很糟糕时(你可以看到它在三个blob上错误地叠加了一个椭圆,并检测到一个我不想要的):
错误的来源图片
提取轮廓后
检测到blob:
这是我目前使用的代码。我不确定任何其他选择。
def process_and_detect(img_path):
img = cv2.imread(path)
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 50, 150, 0)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
drawn_img = np.zeros(img.shape, np.uint8)
min_area = 50
min_ellipses = []
for cnt in contours:
if cv2.contourArea(cnt) >= min_area:
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(drawn_img,ellipse,(0,255,0),-1)
plot_img(drawn_img, size=12)
# Change thresholds
params = cv2.SimpleBlobDetector_Params()
params.filterByColor = True
params.blobColor = 255
params.filterByCircularity = True
params.minCircularity = 0.75
params.filterByArea = True
params.minArea = 150
# Set up the detector
detector = cv2.SimpleBlobDetector_create(params)
# Detect blobs.
keypoints = detector.detect(drawn_img)
for k in keypoints:
x = round(k.pt[0])
y = round(k.pt[1])
line_length = 20
cv2.line(img, (x-line_length, y), (x+line_length, y), (255, 0, 0), 2)
cv2.line(img, (x, y-line_length), (x, y+line_length), (255, 0, 0), 2)
plot_img(img, size=12)
非常感谢您阅读这篇文章,我衷心希望有人能帮助我,或指出我正确的方向。谢谢!
答案 0 :(得分:6)
目前,您的实施是多余的。来自SimpleBlobDetector()
docs:
该类实现了一种从图像中提取blob的简单算法:
- 将源图像转换为二进制图像,方法是应用阈值从minThreshold(包括)到maxThreshold(不包括)的阈值,其中相邻阈值之间的距离为thresholdStep。
- 通过findContours()从每个二进制图像中提取连接的组件并计算它们的中心。
- 根据坐标从多个二进制图像中心分组。关闭中心形成一个对应于一个blob的组,该组由minDistBetweenBlobs参数控制。
- 从小组中,估计斑点及其半径的最终中心,并返回关键点的位置和大小。
醇>
因此,您已经实施了部分步骤,这可能会产生一些意想不到的行为。您可以尝试使用参数来查看是否可以找出适合您的参数(尝试创建trackbars以使用参数并使用不同的blob检测器参数获取算法的实时结果)。
但是,您已经编写了大部分自己的管道,因此您可以轻松删除blob检测器并实现自己的算法。如果您只是略微降低阈值,您可以轻松获得清晰标记的圆圈,然后斑点检测就像轮廓检测一样简单。如果每个斑点都有一个单独的轮廓,则可以使用moments()
计算轮廓的质心。例如:
def process_and_detect(img_path):
img = cv2.imread(img_path)
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 100, 255, cv2.THRESH_BINARY)
contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[1]
line_length = 20
for c in contours:
if cv2.contourArea(c) >= min_area:
M = cv2.moments(c)
x = int(M['m10']/M['m00'])
y = int(M['m01']/M['m00'])
cv2.line(img, (x-line_length, y), (x+line_length, y), (255, 0, 0), 2)
cv2.line(img, (x, y-line_length), (x, y+line_length), (255, 0, 0), 2)
此管道可用于自动循环阈值,因此您无需猜测和硬编码这些值。由于斑点看起来大致大致相同,因此您可以循环直到所有轮廓具有大致相同的区域。你可以这样做,例如通过查找中位轮廓大小,定义您允许的上下限度的某个百分比,并检查检测到的所有轮廓是否适合这些边界。
这是我的意思的动画gif
。请注意,一旦轮廓分离,gif
就会停止:
然后你可以简单地找到那些分开的轮廓的质心。这是代码:
def process_and_detect(img_path):
img = cv2.imread(img_path)
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
for thresh_val in range(0, 255):
# threshold and detect contours
thresh = cv2.threshold(imgray, thresh_val, 255, cv2.THRESH_BINARY)[1]
contours = cv2.findContours(thresh,
cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[1]
# filter contours by area
min_area = 50
filtered_contours = [c for c in contours
if cv2.contourArea(c) >= min_area]
area_contours = [cv2.contourArea(c) for c in filtered_contours]
# acceptable deviation from median contour area
median_area = np.median(area_contours)
dev = 0.3
lowerb = median_area - dev*median_area
upperb = median_area + dev*median_area
# break when all contours are within deviation from median area
if ((area_contours > lowerb) & (area_contours < upperb)).all():
break
# draw center location of blobs
line_length = 8
cross_color = (255, 0, 0)
for c in filtered_contours:
M = cv2.moments(c)
x = int(M['m10']/M['m00'])
y = int(M['m01']/M['m00'])
cv2.line(img, (x-line_length, y), (x+line_length, y), cross_color, 2)
cv2.line(img, (x, y-line_length), (x, y+line_length), cross_color, 2)
请注意,我在这里使用range(0, 255)
循环显示所有可能的阈值,以提供0, 1, ..., 254
,但实际上您可以开始更高并一次跳过几个值,例如range(50, 200, 5)
得到50, 55, ..., 195
当然要快得多。
答案 1 :(得分:2)