检测移动摄像机中的移动物体(监控安装在无人机上的一个区域)
Posted
技术标签:
【中文标题】检测移动摄像机中的移动物体(监控安装在无人机上的一个区域)【英文标题】:Detecting moving object in moving camera(monitoring one area mounted on a drone) 【发布时间】:2017-12-05 18:31:54 【问题描述】: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 中。有人可以建议如何从中找到最高相似值并仅计算这些值的平均值吗?
【问题讨论】:
从初步搜索(我以前从未见过,但听起来很有趣)看来标准方法不仅仅是数量级; HOOF 又名 定向 光流的直方图。看起来this 是开创性论文,尽管它提出了您不需要的额外时间序列分析。您是否假设只有一个对象存在?我认为这里的 k 表示很好用。 所以对象可能存在也可能不存在?嗯,这不是一个简单的问题。仅查看直方图时,您可以轻松地使用np.histogram()
,但这不会让您进行任何分析。 k-means 有助于识别直方图中 k 峰的中心/平均值,但您需要知道是否存在 k 峰。也许您仍然可以设置 k=2,然后如果中心/平均值非常接近,您就知道该对象还没有在场景中。
@AlexanderReynolds 是的,无人机将监视监视区域,并且对象可能随时进入,我需要检测和通知,还需要裁剪正在移动的图像的一部分以进行进一步处理,例如识别物体。我将使用 k-means 方法,看看它是如何进行的。我在想是否可以根据方向分离和裁剪移动对象的部分,因为视频中的所有其他跟踪点都将基于相机运动。对此有何建议?
是的,这两件事基本上是齐头并进的;这个想法是将定向光流向量分成两组。如果这些组的中心彼此靠近,则该对象要么不在场景中,要么是静止的。如果物体在移动,那么您应该有两组不同的光流矢量,并且您可以找到对应于应该是物体的较小组的像素位置。我看到的主要困难部分是边界像素的流动,IDK 会产生多大的影响。我现在正在研究这个问题的玩具版本。
@AlexanderReynolds 太好了。我正在使用 Lucas-Kanade 光流算法仅通过回溯跟踪稀疏点,以确保跟踪的点是帧上的好点,因此我猜不跟踪边界像素。如果我错了,请纠正我。您能否确认如何将光流向量分成两组。我的意思是它们将采用 (u,v) 格式,所以我应该使用这两个向量的大小还是分别处理水平和垂直分量?祝玩具版好运
【参考方案1】:
我创建了一个玩具问题来模拟通过光流对图像进行二值化的方法。这是对问题的一个大大简化的视图,但很好地给出了总体思路。我会将问题分成几块并为它们提供功能。如果您直接处理视频,当然需要很多额外的代码,而我只是硬编码了很多需要转换为参数的值。
第一个函数只是用于生成图像序列。图像在场景中移动,其中一个对象在序列内移动。图像序列只是简单地在场景中平移,并且对象在序列中看起来是静止的,但这意味着对象实际上是在向摄像机的相反方向移动。
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')
在这里,我根据流的角度和大小对流进行了着色。角度将决定颜色,大小将决定颜色的强度/亮度。这与OpenCV tutorial on dense optical flow 使用的视图相同。
然后,我们需要将此流二值化,以便根据它们的移动方式获得两组不同的像素。在稀疏的情况下,除了你会得到两组不同的特征之外,这也是一样的。
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,所以我所做的就是将分配给更多像素的标签视为“相机运动标签”,然后我将 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 的中心相距“足够远”,以便您知道对象是移动与否。
基本步骤是,从视频的起始帧开始:
-
如果这两个中心“接近”,那么您可以假设您的对象不在场景中或不在场景中移动。
一旦中心被充分分开,您就会找到要跟踪的对象。跟踪对象的位置。
在跟踪对象期间,验证该位置是否在预测附近。您可以使用前一帧的光流速度向量来预测新帧中每个像素/特征的位置,因此请确保您的预测与您的跟踪结果一致。
如果对象停止移动,则 k-means 的中心应该靠近。跟踪对象位置周围的光流矢量,并跟踪它们以在对象恢复移动后再次预测对象的位置,并通过此预测再次验证检测到的位置。
我以前从未使用过这些方法,所以我不确定它们有多强大。 HOOF 或“定向光流直方图”的典型方法比这要先进得多(参见开创性论文here)。这个想法不仅仅是二值化,而是使用来自每个帧的直方图作为概率分布,并且可以使用来自时间序列分析的工具来分析这种概率分布随时间变化的方式,我假设这为这种方法提供了一个更强大的框架.
【讨论】:
@KiranBayari 太好了,我在底部添加了更多讨论。我会看看你链接的论文,看看他们是怎么做的(我只是对这个话题感兴趣)。如果您链接的论文中有我想要实施的内容,我会更新更多信息。 那太好了。我将开始研究它,如果我遇到任何我无法弄清楚的问题,我会回到这里。再次感谢所有建议和详细解释。 不幸的是:error: (-215:Assertion failed) npoints > 0 in function 'drawContours'
添加ctr = np.array(contours).reshape((-1,1,2)).astype(np.int32) display_img = cv2.drawContours(display_img, ctr, -1, (0, 255, 0), 2)
修复了上述错误,但现在没有检测到。以上是关于检测移动摄像机中的移动物体(监控安装在无人机上的一个区域)的主要内容,如果未能解决你的问题,请参考以下文章