首先,在流动任何细胞之前,我会通过累积帧来获取平均背景图像。这将允许您稍后使用背景减法来更轻松地找到您关心的单元格。我使用一个看起来像这样的函数来执行此操作,其中images 是您要平均的帧系列。
# returns a frame that is the average of all the provided frames
def average(images):
average = np.zeros(images[0].shape, np.float32)
for image in images:
cv2.accumulate(image, average)
average = average / len(images)
average = np.uint8(average)
return average
一旦你有一个好的背景(使用适当数量的帧,如果你在减法后看到噪音,增加用于平均的帧数),你可以使用一系列背景减法、腐蚀和膨胀(如果需要)和轮廓查找来定位细胞。
这个函数有所有这些部分,你可以用它作为例子。它将减去背景,对其进行阈值化,然后侵蚀和扩张以消除任何剩余的噪声,并寻找剩下的轮廓,理想情况下是您关心的细胞。您可能必须更改腐蚀和膨胀大小(它们以像素为单位)才能获得良好的图片。
# detect a moving ball with countours and background subtraction
def detectBall(frame, background, roi):
# background subtraction, thresholding, erosion and dialation
motion = cv2.absdiff(background, frame[roi[0][1]:roi[1][1], roi[0][0]:roi[1][0]])
_, thresh1 = cv2.threshold(motion, 10, 255, cv2.THRESH_BINARY)
gray = cv2.cvtColor(thresh1, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
thresh = cv2.threshold(blurred, 30, 255, cv2.THRESH_BINARY)[1]
erosion_size = 10
dilate_size = 8
thresh = cv2.erode(thresh, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (erosion_size, erosion_size)))
thresh = cv2.dilate(thresh, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (dilate_size, dilate_size)))
# find countours
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
center = None
for c in cnts:
# compute the center of the contour
M = cv2.moments(c)
cX = int((M["m10"] / (M["m00"] + 1e-7)))
cY = int((M["m01"] / (M["m00"] + 1e-7)))
# do some color filtering on each contour to eliminate extra contours
hsv = cv2.cvtColor(motion, cv2.COLOR_BGR2HSV)
(_, _, h) = hsv[cY, cX]
if 80 < h < 140:
# calculate the center and draw the detected ball
c = c.astype("float")
c = c.astype("int")
area = int(cv2.contourArea(c))
diam = int(math.sqrt(area / math.pi))
center = (cX + roi[0][0], cY + roi[0][1])
cv2.circle(frame, center, 1, RED, 5)
cv2.circle(frame, center, diam, GREEN, 2)
cv2.putText(frame, str(center), (center[0] + 10, center[1] + 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, BLUE, 2) # draw center text
return center, frame
这将为您提供与背景不同的任何对象的位置,并且在我的系统上运行得非常快。
这对您来说可能已经足够了,但是如果在确定您想要尝试跟踪特定单元格之后,您可以使用模板匹配并围绕已知单元格位置的中心进行切片,以查看它们移动到的位置。如果细胞看起来都非常相似,这可能会也可能不会很好,但您可以尝试一下。
为此,我使用如下函数:
def findMatchingPoints(old_frame, new_frame, pts, templateSize=5, searchWindowSize=20):
output_points = []
strengths = []
for pt in pts:
# make template and window images
l_template = int(templateSize / 2) # calculate half template size
l_window = int(searchWindowSize /2) # calculate half window size
x = int(pt[0][0]) # get x coordinate
y = int(pt[0][1]) # get y coordinate
template = old_frame[y-l_template:y+l_template, x-l_template:x+l_template] # templeate comes from old
window = new_frame[y-l_window:y+l_window, x-l_window:x+l_window] # look in the new
# skip poorly formed windows or templates
if 0 in window.shape or 0 in template.shape:
strengths.append(float(10))
output_points.append([[np.float32(0), np.float32(0)]])
continue
# Apply template matching and save results
res = cv2.matchTemplate(window, template, cv2.TM_SQDIFF_NORMED)
strength, _, top_left, _ = cv2.minMaxLoc(res) # returns (min_val, max_val, min_loc, max_loc), for SSD we want min location, is top left of template
center = [[np.float32(top_left[0]+x-l_window), np.float32(top_left[1]+y-l_window)]]
strengths.append(strength)
output_points.append(center)
# create a boolean mask to keep good points
output_points = np.asarray(output_points)
_, mask = cv2.findFundamentalMat(pts, output_points, cv2.FM_RANSAC)
return output_points, strengths, mask
# see which matches are good
def filter_matches(p0, p1, st, good):
good_old, good_new = [], []
for old, new, s, g in zip(p0, p1, st, good):
s = int(s*100)
if s < 1 and g == 1:
good_new.append(new)
good_old.append(old)
return good_old, good_new
希望这会有所帮助。这里的关键功能/想法是背景平均和减法、轮廓查找和模板匹配。