cv2.HoughCircles出现不可靠的结果

9
我是一名有用的助手,可以为您翻译文本。

我有一个视频,其中有5个油滴,我正在尝试使用cv2.HoughCircles来找到它们。

这是我的代码:

import cv, cv2
import numpy as np

foreground1 = cv2.imread("foreground1.jpg")
vid = cv2.VideoCapture("NB14.avi")

cv2.namedWindow("video")
cv2.namedWindow("canny")
cv2.namedWindow("blur")

while True:
    ret, frame = vid.read()
    subtract1 = cv2.subtract( foreground1, frame)
    framegrey1 = cv2.cvtColor(subtract1, cv.CV_RGB2GRAY)
    blur = cv2.GaussianBlur(framegrey1, (0,0), 2)
    circles =  cv2.HoughCircles(blur, cv2.cv.CV_HOUGH_GRADIENT, 2, 10, np.array([]), 40, 80, 5, 100)
    if circles is not None:
            for c in circles[0]:
                    cv2.circle(frame, (c[0],c[1]), c[2], (0,255,0),2)
    edges = cv2.Canny( blur, 40, 80 )
    cv2.imshow("video", frame)
    cv2.imshow("canny", edges)
    cv2.imshow("blur", blur)
    key = cv2.waitKey(30)

我认为canny边缘检测看起来非常好,而霍夫变换的结果非常不稳定,每个帧都会提供不同的结果。
例子:

frame1

frame2

frame3

我一直在调整参数,但实话说,我不知道如何获得更稳定的结果。

我贴出的图片中,左边的图片是实际帧,除了绿色圆圈外没有任何处理。这就是从相机传输的帧。我想要的是在每一帧中找到水滴,因为我需要跟踪它们。我还在尝试使用Otsu直方图。 - Dr Sokoban
是的,那正是我想表达的意思。我不想要绿色的圆圈。 - mmgp
有任何地方可以上传视频吗? - Dr Sokoban
只需保存其中的一些帧并将它们包含在内即可。否则,还有几个免费上传的地方可供选择,选择您喜欢的即可。 - mmgp
请在这里查看视频:http://dl.dropbox.com/u/17284290/NB14.avi - Dr Sokoban
显示剩余2条评论
1个回答

11

一开始我以为你的油滴不会重叠,但是它们确实有。因此,使用Hough方法可能是一个好方法,但是当我将其与RANSAC结合使用时,我有更好的经验。我建议尝试一下这个方法,但在这里我将提供一些不同的东西。

首先,由于我没有“foreground1.jpg”图像,所以我无法执行你所做的背景减除(因此结果可以轻松改进)。我也不关心画圆圈,但你可以这样做,我只是画出我认为是圆形的对象的边框。

因此,让我们先假设没有重叠。然后在图像中找到边缘(很容易),通过Otsu二值化边缘检测器的响应,填充孔隙,并最终测量圆度即可。如果有重叠,我们可以使用Watershed变换结合距离变换来分离油滴。问题是你得不到真正圆形的物体,而我并不太关心这个,但你可以调整一下。

在以下代码中,我还不得不使用scipy来标记连接组件(对于构建Watershed标记非常重要),因为OpenCV在这方面比较欠缺。代码并不是非常简短,但应该很容易理解。此外,在Watershed分割后,由于只有你想要的对象保留下来,所以不需要进行圆度检查。最后,基于到物体中心的大致距离进行了一些简单的跟踪。

import sys
import cv2
import math
import numpy
from scipy.ndimage import label

pi_4 = 4*math.pi

def segment_on_dt(img):
    border = img - cv2.erode(img, None)

    dt = cv2.distanceTransform(255 - img, 2, 3)
    dt = ((dt - dt.min()) / (dt.max() - dt.min()) * 255).astype(numpy.uint8)
    _, dt = cv2.threshold(dt, 100, 255, cv2.THRESH_BINARY)

    lbl, ncc = label(dt)
    lbl[border == 255] = ncc + 1

    lbl = lbl.astype(numpy.int32)
    cv2.watershed(cv2.cvtColor(img, cv2.COLOR_GRAY2RGB), lbl)
    lbl[lbl < 1] = 0
    lbl[lbl > ncc] = 0

    lbl = lbl.astype(numpy.uint8)
    lbl = cv2.erode(lbl, None)
    lbl[lbl != 0] = 255
    return lbl


def find_circles(frame):
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
    frame_gray = cv2.GaussianBlur(frame_gray, (5, 5), 2)

    edges = frame_gray - cv2.erode(frame_gray, None)
    _, bin_edge = cv2.threshold(edges, 0, 255, cv2.THRESH_OTSU)
    height, width = bin_edge.shape
    mask = numpy.zeros((height+2, width+2), dtype=numpy.uint8)
    cv2.floodFill(bin_edge, mask, (0, 0), 255)

    components = segment_on_dt(bin_edge)

    circles, obj_center = [], []
    contours, _ = cv2.findContours(components,
            cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

    for c in contours:
        c = c.astype(numpy.int64) # XXX OpenCV bug.
        area = cv2.contourArea(c)
        if 100 < area < 3000:
            arclen = cv2.arcLength(c, True)
            circularity = (pi_4 * area) / (arclen * arclen)
            if circularity > 0.5: # XXX Yes, pretty low threshold.
                circles.append(c)
                box = cv2.boundingRect(c)
                obj_center.append((box[0] + (box[2] / 2), box[1] + (box[3] / 2)))

    return circles, obj_center

def track_center(objcenter, newdata):
    for i in xrange(len(objcenter)):
        ostr, oc = objcenter[i]
        best = min((abs(c[0]-oc[0])**2+abs(c[1]-oc[1])**2, j)
                for j, c in enumerate(newdata))
        j = best[1]
        if i == j:
            objcenter[i] = (ostr, new_center[j])
        else:
            print "Swapping %s <-> %s" % ((i, objcenter[i]), (j, objcenter[j]))
            objcenter[i], objcenter[j] = objcenter[j], objcenter[i]


video = cv2.VideoCapture(sys.argv[1])

obj_center = None
while True:
    ret, frame = video.read()
    if not ret:
        break

    circles, new_center = find_circles(frame)
    if obj_center is None:
        obj_center = [(str(i + 1), c) for i, c in enumerate(new_center)]
    else:
        track_center(obj_center, new_center)

    for i in xrange(len(circles)):
        cv2.drawContours(frame, circles, i, (0, 255, 0))
        cstr, ccenter = obj_center[i]
        cv2.putText(frame, cstr, ccenter, cv2.FONT_HERSHEY_COMPLEX, 0.5,
                (255, 255, 255), 1, cv2.CV_AA)

    cv2.imshow("result", frame)
    cv2.waitKey(10)
    if len(circles[0]) < 5:
        print "lost something"

这适用于您整个视频,以下是两个示例:

在此输入图像描述 在此输入图像描述


顺便说一下,我不是在寻找圆圈,我在寻找液滴。所以它们是否完美的圆形并不重要。 - Dr Sokoban
@DrSokoban RANSAC在检测之前取样,能够很好地处理异常值。而Hough则考虑每个点,因此异常值可能会掩盖本应找到的好圆。 - mmgp
能否详细解释一下?我猜你想使用Ransac算法来寻找圆,但我不明白你是在图像空间还是霍夫空间进行操作。另外,有没有办法可以获取累积矩阵呢? - Dr Sokoban
1
当您使用None作为结构元素时,OpenCV会假定一个3x3的平坦SE。预计这种腐蚀会使液滴稍微变大,因为它们是暗色液滴。我将其用作简单形式的边缘检测,但您可以用任何其他同样好(或更好)的方法替换它。关于RANSAC的另一点:原则上,所有RANSAC中发生的事情都在图像空间中,正如您所说,它不依赖于类似霍夫实现的累加器矩阵。有关更多详细信息,我想您将不得不找到一些关于它的论文或书籍,在评论中没有太多的空间。 - mmgp
好的,我要检查一些文件,但是你的解决方案看起来很完美。有什么想法可以进行跟踪吗?到目前为止,我正在考虑随着时间的推移比较x、y位置,并选择最近的一个来分配帧之间的关系。 - Dr Sokoban
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接