Во-первых, перед обтеканием любых ячеек я бы получил усредненное фоновое изображение, накапливая кадры. Это позволит вам позже использовать вычитание фона, чтобы легче находить нужные вам клетки. Я использую функцию, которая выглядит следующим образом, где 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
Надеюсь, это поможет. Ключевыми функциями / идеями здесь являются усреднение и вычитание фона, поиск контуров и сопоставление с шаблоном.