在移动摄像机中检测移动物体(监视安装在无人机上的一个区域)
Detecting moving object in moving camera(monitoring one area mounted on a drone)
def run(self):
while True:
_ret, frame = self.cam.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
vis = frame.copy()
if len(self.tracks) > 0:
img0, img1 = self.prev_gray, frame_gray
p0 = np.float32([tr[-1] for tr in self.tracks]).reshape(-1, 1, 2)
p1, _st, _err = cv2.calcOpticalFlowPyrLK(img0, img1, p0, None, **lk_params)
p0r, _st, _err = cv2.calcOpticalFlowPyrLK(img1, img0, p1, None, **lk_params)
d = abs(p0-p0r).reshape(-1, 2).max(-1)
good = d < 1
new_tracks = []
for i in range(len(p1)):
A.append(math.sqrt((p1[i][0][0])**2 + (p1[i][0][1])**2))
counts,bins,bars = plt.hist(A)
for tr, (x, y), good_flag in zip(self.tracks, p1.reshape(-1, 2), good):
if not good_flag:
continue
tr.append((x, y))
if len(tr) > self.track_len:
del tr[0]
new_tracks.append(tr)
cv2.circle(vis, (x, y), 2, (0, 255, 0), -1)
self.tracks = new_tracks
cv2.polylines(vis, [np.int32(tr) for tr in self.tracks], False, (0, 255, 0))
draw_str(vis, (20, 20), 'track count: %d' % len(self.tracks))
if self.frame_idx % self.detect_interval == 0:
mask = np.zeros_like(frame_gray)
mask[:] = 255
for x, y in [np.int32(tr[-1]) for tr in self.tracks]:
cv2.circle(mask, (x, y), 5, 0, -1)
p = cv2.goodFeaturesToTrack(frame_gray, mask = mask, **feature_params)
if p is not None:
for x, y in np.float32(p).reshape(-1, 2):
self.tracks.append([(x, y)])
self.frame_idx += 1
self.prev_gray = frame_gray
cv2.imshow('lk_track', vis)
ch = cv2.waitKey(1)
if ch == 27:
break
我正在使用来自 opencv 示例的 lk_track.py 来尝试检测移动物体。我正在尝试使用光流矢量幅度的直方图找到相机运动,然后计算应与相机运动成正比的相似值的平均值。我已经计算出向量的大小并将其保存在列表 A 中。有人可以建议如何从中找到最高的相似值并仅计算这些值的平均值吗?
我创建了一个玩具问题来模拟通过光流对图像进行二值化的方法。这是对问题的极大简化视图,但很好地给出了总体思路。我会将问题分成几块并为它们提供功能。如果你直接处理视频,当然需要很多额外的代码,我只是硬编码了很多你需要转换成参数的值。
第一个函数仅用于生成图像序列。图像在一个场景中移动,其中一个对象在序列内移动。图像序列只是在场景中简单地平移,物体在序列中看起来是静止的,但这当然意味着物体实际上是在与相机相反的方向上移动。
import numpy as np
import cv2
def gen_seq():
"""Generate motion sequence with an object"""
scene = cv2.GaussianBlur(np.uint8(255*np.random.rand(400, 500)), (21, 21), 3)
h, w = 400, 400
step = 4
obj_mask = np.zeros((h, w), np.bool)
obj_h, obj_w = 50, 50
obj_x, obj_y = 175, 175
obj_mask[obj_y:obj_y+obj_h, obj_x:obj_x+obj_w] = True
obj_data = np.uint8(255*np.random.rand(obj_h, obj_w)).ravel()
imgs = []
for i in range(0, 1+w//step, step):
img = scene[:, i:i+w].copy()
img[obj_mask] = obj_data
imgs.append(img)
return imgs
# generate image sequence
imgs = gen_seq()
# display images
for img in imgs:
cv2.imshow('Image', img)
k = cv2.waitKey(100) & 0xFF
if k == ord('q'):
break
cv2.destroyWindow('Image')
这是可视化的基本图像序列。我只是使用了一个随机场景,翻译通过,并在中心添加了一个随机对象。
太棒了!现在我们需要计算每一帧之间的流量。我在这里使用了密集流,但对于实际图像来说,稀疏流会更稳健。
def find_flows(imgs):
"""Finds the dense optical flows"""
optflow_params = [0.5, 3, 15, 3, 5, 1.2, 0]
prev = imgs[0]
flows = []
for img in imgs[1:]:
flow = cv2.calcOpticalFlowFarneback(prev, img, None, *optflow_params)
flows.append(flow)
prev = img
return flows
# find optical flows between images
flows = find_flows(imgs)
# display flows
h, w = imgs[0].shape[:2]
hsv = np.zeros((h, w, 3), dtype=np.uint8)
hsv[..., 1] = 255
for flow in flows:
mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])
hsv[..., 0] = ang*180/np.pi/2
hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
cv2.imshow('Flow', rgb)
k = cv2.waitKey(100) & 0xFF
if k == ord('q'):
break
cv2.destroyWindow('Flow')
在这里,我根据它的角度和幅度对流进行了着色。角度将决定颜色,大小将决定颜色的intensity/brightness。这与 OpenCV tutorial on dense optical flow 使用的视图相同。
然后,我们需要将此流二值化,以便我们根据像素的移动方式获得两组不同的像素。在稀疏情况下,除了你会得到两组不同的 features 之外,结果是一样的。
def label_flows(flows):
"""Binarizes the flows by direction and magnitude"""
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
flags = cv2.KMEANS_RANDOM_CENTERS
h, w = flows[0].shape[:2]
labeled_flows = []
for flow in flows:
flow = flow.reshape(h*w, -1)
comp, labels, centers = cv2.kmeans(flow, 2, None, criteria, 10, flags)
n = np.sum(labels == 1)
camera_motion_label = np.argmax([labels.size-n, n])
labeled = np.uint8(255*(labels.reshape(h, w) == camera_motion_label))
labeled_flows.append(labeled)
return labeled_flows
# binarize the flows
labeled_flows = label_flows(flows)
# display binarized flows
for labeled_flow in labeled_flows:
cv2.imshow('Labeled Flow', labeled_flow)
k = cv2.waitKey(100) & 0xFF
if k == ord('q'):
break
cv2.destroyWindow('Labeled Flow')
这里令人讨厌的是标签将随机设置,即每一帧的标签都不同。如果您将二值图像可视化,它会随机地在黑白之间翻转。我只使用二进制标签 0 和 1,所以我所做的被认为是分配给更多像素的标签是 "camera motion label" 然后我设置 that 标签在生成的图像中为白色,而另一个标签为黑色,这样相机运动标签在每一帧中始终相同。这可能需要更加复杂才能处理视频源。
但我们这里有一个二值化流,其中颜色仅显示两组不同的流矢量。
现在如果我们想在这个流中找到目标,我们可以反转图像并找到二值图像的连通分量。反转将使相机运动成为背景标签(0)。然后每个黑色斑点都会变成白色并被标记,我们可以找到与最大成分相关的斑点,在这种情况下,这将是目标。这将在目标周围提供一个蒙版,我们可以在原始图像上绘制该蒙版的轮廓以查看被检测到的目标。在找到连接的组件之前,我还将切断图像的边界,以便忽略密集流的边缘效应。
def find_target_in_labeled_flow(labeled_flow):
labeled_flow = cv2.bitwise_not(labeled_flow)
bw = 10
h, w = labeled_flow.shape[:2]
border_cut = labeled_flow[bw:h-bw, bw:w-bw]
conncomp, stats = cv2.connectedComponentsWithStats(border_cut, connectivity=8)[1:3]
target_label = np.argmax(stats[1:, cv2.CC_STAT_AREA]) + 1
img = np.zeros_like(labeled_flow)
img[bw:h-bw, bw:w-bw] = 255*(conncomp == target_label)
return img
for labeled_flow, img in zip(labeled_flows, imgs[:-1]):
target_mask = find_target_in_labeled_flow(labeled_flow)
display_img = cv2.merge([img, img, img])
contours = cv2.findContours(target_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[1]
display_img = cv2.drawContours(display_img, contours, -1, (0, 255, 0), 2)
cv2.imshow('Detected Target', display_img)
k = cv2.waitKey(100) & 0xFF
if k == ord('q'):
break
当然,这可以进行一些清理,对于稀疏流,您不会完全这样做。您可以在跟踪点周围定义一个感兴趣区域。
现在,还有很多工作要做。你有一个二值化流......你可以假设最频繁出现的标签是相机运动(就像我一样)安全。但是,您必须确保其他标签是您有兴趣跟踪的对象。你必须在流之间跟踪它,这样如果它停止移动,你就会知道它在相机移动时的位置。当您执行 k-means 步骤时,您需要确保 k-means 的中心相距 "far enough"这样你就知道物体是否在移动。
基本步骤是,从视频的起始帧开始:
- 如果两个中心是"close",那么你可以假设你的物体不在场景中或者在场景中没有移动。
- 一旦中心足够分开,您就会找到要跟踪的对象。跟踪对象的位置。
- 在跟踪对象期间,验证该位置是否在预测附近。您可以使用前一帧的光流速度矢量来预测每个 pixel/feature 在新帧中的位置,因此请确保您的预测与您的跟踪结果一致。
- 如果物体停止移动,k的中心应该很近。跟踪对象位置周围的光流矢量,并跟随它们预测对象恢复移动后的位置,并再次使用此预测验证检测到的位置。
我以前从未使用过这些方法,所以我不确定它们有多稳健。 HOOF 或 "Histogram of oriented optical flow" 的典型方法比这先进得多(请参阅开创性论文 here)。这个想法不仅仅是二值化,而是使用来自每个帧的直方图作为概率分布,并且可以使用时间序列分析中的工具来分析这种概率分布随时间变化的方式,我认为这为这种方法提供了一个更强大的框架.
def run(self):
while True:
_ret, frame = self.cam.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
vis = frame.copy()
if len(self.tracks) > 0:
img0, img1 = self.prev_gray, frame_gray
p0 = np.float32([tr[-1] for tr in self.tracks]).reshape(-1, 1, 2)
p1, _st, _err = cv2.calcOpticalFlowPyrLK(img0, img1, p0, None, **lk_params)
p0r, _st, _err = cv2.calcOpticalFlowPyrLK(img1, img0, p1, None, **lk_params)
d = abs(p0-p0r).reshape(-1, 2).max(-1)
good = d < 1
new_tracks = []
for i in range(len(p1)):
A.append(math.sqrt((p1[i][0][0])**2 + (p1[i][0][1])**2))
counts,bins,bars = plt.hist(A)
for tr, (x, y), good_flag in zip(self.tracks, p1.reshape(-1, 2), good):
if not good_flag:
continue
tr.append((x, y))
if len(tr) > self.track_len:
del tr[0]
new_tracks.append(tr)
cv2.circle(vis, (x, y), 2, (0, 255, 0), -1)
self.tracks = new_tracks
cv2.polylines(vis, [np.int32(tr) for tr in self.tracks], False, (0, 255, 0))
draw_str(vis, (20, 20), 'track count: %d' % len(self.tracks))
if self.frame_idx % self.detect_interval == 0:
mask = np.zeros_like(frame_gray)
mask[:] = 255
for x, y in [np.int32(tr[-1]) for tr in self.tracks]:
cv2.circle(mask, (x, y), 5, 0, -1)
p = cv2.goodFeaturesToTrack(frame_gray, mask = mask, **feature_params)
if p is not None:
for x, y in np.float32(p).reshape(-1, 2):
self.tracks.append([(x, y)])
self.frame_idx += 1
self.prev_gray = frame_gray
cv2.imshow('lk_track', vis)
ch = cv2.waitKey(1)
if ch == 27:
break
我正在使用来自 opencv 示例的 lk_track.py 来尝试检测移动物体。我正在尝试使用光流矢量幅度的直方图找到相机运动,然后计算应与相机运动成正比的相似值的平均值。我已经计算出向量的大小并将其保存在列表 A 中。有人可以建议如何从中找到最高的相似值并仅计算这些值的平均值吗?
我创建了一个玩具问题来模拟通过光流对图像进行二值化的方法。这是对问题的极大简化视图,但很好地给出了总体思路。我会将问题分成几块并为它们提供功能。如果你直接处理视频,当然需要很多额外的代码,我只是硬编码了很多你需要转换成参数的值。
第一个函数仅用于生成图像序列。图像在一个场景中移动,其中一个对象在序列内移动。图像序列只是在场景中简单地平移,物体在序列中看起来是静止的,但这当然意味着物体实际上是在与相机相反的方向上移动。
import numpy as np
import cv2
def gen_seq():
"""Generate motion sequence with an object"""
scene = cv2.GaussianBlur(np.uint8(255*np.random.rand(400, 500)), (21, 21), 3)
h, w = 400, 400
step = 4
obj_mask = np.zeros((h, w), np.bool)
obj_h, obj_w = 50, 50
obj_x, obj_y = 175, 175
obj_mask[obj_y:obj_y+obj_h, obj_x:obj_x+obj_w] = True
obj_data = np.uint8(255*np.random.rand(obj_h, obj_w)).ravel()
imgs = []
for i in range(0, 1+w//step, step):
img = scene[:, i:i+w].copy()
img[obj_mask] = obj_data
imgs.append(img)
return imgs
# generate image sequence
imgs = gen_seq()
# display images
for img in imgs:
cv2.imshow('Image', img)
k = cv2.waitKey(100) & 0xFF
if k == ord('q'):
break
cv2.destroyWindow('Image')
这是可视化的基本图像序列。我只是使用了一个随机场景,翻译通过,并在中心添加了一个随机对象。
太棒了!现在我们需要计算每一帧之间的流量。我在这里使用了密集流,但对于实际图像来说,稀疏流会更稳健。
def find_flows(imgs):
"""Finds the dense optical flows"""
optflow_params = [0.5, 3, 15, 3, 5, 1.2, 0]
prev = imgs[0]
flows = []
for img in imgs[1:]:
flow = cv2.calcOpticalFlowFarneback(prev, img, None, *optflow_params)
flows.append(flow)
prev = img
return flows
# find optical flows between images
flows = find_flows(imgs)
# display flows
h, w = imgs[0].shape[:2]
hsv = np.zeros((h, w, 3), dtype=np.uint8)
hsv[..., 1] = 255
for flow in flows:
mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])
hsv[..., 0] = ang*180/np.pi/2
hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
cv2.imshow('Flow', rgb)
k = cv2.waitKey(100) & 0xFF
if k == ord('q'):
break
cv2.destroyWindow('Flow')
在这里,我根据它的角度和幅度对流进行了着色。角度将决定颜色,大小将决定颜色的intensity/brightness。这与 OpenCV tutorial on dense optical flow 使用的视图相同。
然后,我们需要将此流二值化,以便我们根据像素的移动方式获得两组不同的像素。在稀疏情况下,除了你会得到两组不同的 features 之外,结果是一样的。
def label_flows(flows):
"""Binarizes the flows by direction and magnitude"""
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
flags = cv2.KMEANS_RANDOM_CENTERS
h, w = flows[0].shape[:2]
labeled_flows = []
for flow in flows:
flow = flow.reshape(h*w, -1)
comp, labels, centers = cv2.kmeans(flow, 2, None, criteria, 10, flags)
n = np.sum(labels == 1)
camera_motion_label = np.argmax([labels.size-n, n])
labeled = np.uint8(255*(labels.reshape(h, w) == camera_motion_label))
labeled_flows.append(labeled)
return labeled_flows
# binarize the flows
labeled_flows = label_flows(flows)
# display binarized flows
for labeled_flow in labeled_flows:
cv2.imshow('Labeled Flow', labeled_flow)
k = cv2.waitKey(100) & 0xFF
if k == ord('q'):
break
cv2.destroyWindow('Labeled Flow')
这里令人讨厌的是标签将随机设置,即每一帧的标签都不同。如果您将二值图像可视化,它会随机地在黑白之间翻转。我只使用二进制标签 0 和 1,所以我所做的被认为是分配给更多像素的标签是 "camera motion label" 然后我设置 that 标签在生成的图像中为白色,而另一个标签为黑色,这样相机运动标签在每一帧中始终相同。这可能需要更加复杂才能处理视频源。
但我们这里有一个二值化流,其中颜色仅显示两组不同的流矢量。
现在如果我们想在这个流中找到目标,我们可以反转图像并找到二值图像的连通分量。反转将使相机运动成为背景标签(0)。然后每个黑色斑点都会变成白色并被标记,我们可以找到与最大成分相关的斑点,在这种情况下,这将是目标。这将在目标周围提供一个蒙版,我们可以在原始图像上绘制该蒙版的轮廓以查看被检测到的目标。在找到连接的组件之前,我还将切断图像的边界,以便忽略密集流的边缘效应。
def find_target_in_labeled_flow(labeled_flow):
labeled_flow = cv2.bitwise_not(labeled_flow)
bw = 10
h, w = labeled_flow.shape[:2]
border_cut = labeled_flow[bw:h-bw, bw:w-bw]
conncomp, stats = cv2.connectedComponentsWithStats(border_cut, connectivity=8)[1:3]
target_label = np.argmax(stats[1:, cv2.CC_STAT_AREA]) + 1
img = np.zeros_like(labeled_flow)
img[bw:h-bw, bw:w-bw] = 255*(conncomp == target_label)
return img
for labeled_flow, img in zip(labeled_flows, imgs[:-1]):
target_mask = find_target_in_labeled_flow(labeled_flow)
display_img = cv2.merge([img, img, img])
contours = cv2.findContours(target_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[1]
display_img = cv2.drawContours(display_img, contours, -1, (0, 255, 0), 2)
cv2.imshow('Detected Target', display_img)
k = cv2.waitKey(100) & 0xFF
if k == ord('q'):
break
当然,这可以进行一些清理,对于稀疏流,您不会完全这样做。您可以在跟踪点周围定义一个感兴趣区域。
现在,还有很多工作要做。你有一个二值化流......你可以假设最频繁出现的标签是相机运动(就像我一样)安全。但是,您必须确保其他标签是您有兴趣跟踪的对象。你必须在流之间跟踪它,这样如果它停止移动,你就会知道它在相机移动时的位置。当您执行 k-means 步骤时,您需要确保 k-means 的中心相距 "far enough"这样你就知道物体是否在移动。
基本步骤是,从视频的起始帧开始:
- 如果两个中心是"close",那么你可以假设你的物体不在场景中或者在场景中没有移动。
- 一旦中心足够分开,您就会找到要跟踪的对象。跟踪对象的位置。
- 在跟踪对象期间,验证该位置是否在预测附近。您可以使用前一帧的光流速度矢量来预测每个 pixel/feature 在新帧中的位置,因此请确保您的预测与您的跟踪结果一致。
- 如果物体停止移动,k的中心应该很近。跟踪对象位置周围的光流矢量,并跟随它们预测对象恢复移动后的位置,并再次使用此预测验证检测到的位置。
我以前从未使用过这些方法,所以我不确定它们有多稳健。 HOOF 或 "Histogram of oriented optical flow" 的典型方法比这先进得多(请参阅开创性论文 here)。这个想法不仅仅是二值化,而是使用来自每个帧的直方图作为概率分布,并且可以使用时间序列分析中的工具来分析这种概率分布随时间变化的方式,我认为这为这种方法提供了一个更强大的框架.