使用Python,OpenCV追踪对象的轨迹,来确定其移动方向
Posted 程序媛一枚~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Python,OpenCV追踪对象的轨迹,来确定其移动方向相关的知识,希望对你有一定的参考价值。
这篇博客是上一篇博客: 使用Python,OpenCV转换颜色空间,追踪对象的轨迹的扩展。将使用Python,OpenCV追踪对象的轨迹,来确定其移动方向;
虽然球跟踪展示了目标检测和跟踪的基础知识,但无法计算球的实际移动方向。通过在两个单独的帧中简单地计算对象(x,y)坐标之间的增量,就能够正确地跟踪对象的运动,甚至报告其移动的方向。
也可以通过简单地分别取dX和dY的反正切来报告实际的运动角度,从而使这个对象运动跟踪器更加精确。
1. 效果图
gif效果图如下:
部分截图如下:
注意:有点像镜面成像,刚好跟看到的方向相反
向右侧东移动
可以看到球被正确检测到,并正在向“东”移动,北”方向是通过检查dX和dY值(显示在帧的左下角)来确定的。自| dY |>20以来,能够确定y坐标发生了重大变化。dX正,dY负,因此确定是向右。
向西南方向移动
2. 源码
# 使用Python,OpenCV追踪对象的轨迹以确定其移动方向
# USAGE
# 使用网络摄像头流
# python object_movement.py
# 使用视频文件
# python object_movement.py --video imgs/ball_movement.mp4
# 导入必要的包
import argparse
import math
import time
from collections import deque # 使用Python内置的deque数据类型来高效地存储对象检测和跟踪的过去N个点
import cv2
import imutils # 使用IMUTIL(已经收集了OpenCV和Python方便的函数)
import numpy as np
from imutils.video import VideoStream
# 获取角度
def get_angle(delta_x, delta_y):
angle = 0
if delta_x == 0 or delta_x == 0.0:
b = math.pi / 2.0
angle = b / math.pi * 180
elif delta_y == 0 or delta_y == 0.0:
angle = 0.0
elif delta_y < 0:
angle -= 180
else:
b = math.atan(delta_y / delta_x)
angle = b / math.pi * 180
if delta_y > 0 and delta_x < 0:
angle = angle + 180
if delta_y < 0 and delta_x < 0:
angle = angle - 180
return angle
# 绘制轮廓ID号
def draw_contour(image, c, i):
# cv2.drawContours(image, [c], 0, (0, 255, 255), 2)
# 计算轮廓区域的中心,并绘制⭕代表中心
M = cv2.moments(c)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
# 在图像上绘制轮廓数
cv2.putText(image, "".format(i + 1), (cX - 5, cY - 5), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (0, 255, 0), 1)
(x, y), radius = cv2.minEnclosingCircle(c)
center = (int(x), int(y))
radius = int(radius)
cv2.circle(image, center, radius, (0, 255, 255), 1)
# 返回绘制了轮廓数的图像
return image
# 构建命令行参数及解析
# --video:如果省略了--video开关,则将(尝试)使用网络摄像头。
# -buffer,它控制点的deque的最大大小。deque越大,跟踪对象的(x,y)坐标就越多,基本上提供了对象在视频流中的位置的更大“历史”。默认为32表示将只为之前的32帧维护对象(x,y)坐标的缓冲区。
ap = argparse.ArgumentParser()
ap.add_argument("-v", "--video",
help="path to the (optional) video file")
ap.add_argument("-b", "--buffer", type=int, default=8,
help="max buffer size")
args = vars(ap.parse_args())
# 定义绿色的HSV空间的上下限值
greenLower = (29, 86, 6)
greenUpper = (64, 255, 255)
greenLower = (0, 0, 57)
greenUpper = (179, 207, 255)
# 初始化list以存储追踪的中心点,帧计数器,坐标值,及方向
pts = deque(maxlen=args["buffer"])
counter = 0
(dX, dY) = (0, 0)
direction = ""
# 如果未提供视频文件,则获取摄像头
# imutils.video VideoStream类以线程方式处理相机帧。处理视频文件帧时使用cv2.VideoCapture捕获做得最好
if not args.get("video", False):
vs = VideoStream(src=0).start()
# 预热2s
time.sleep(2.0)
# 否则,获取视频文件指针
else:
vs = cv2.VideoCapture(args["video"])
num = 0
# 遍历帧
while True:
# 获取当前帧
frame = vs.read()
# 处理VideoCapture or VideoStream中的帧
frame = frame[1] if args.get("video", False) else frame
# 如果处理的是视频文件,未获取到帧则表明到达文件末尾,跳出循环
if frame is None:
break
# 创建HSV图像,并根据最低、最高阈值进行阈值化
# 缩放帧,应用高斯模糊来平滑图像并减少高频噪声,对帧进行预处理,最后将帧转换为HSV颜色空间
# frame = imutils.resize(frame, width=400)
blurred = cv2.GaussianBlur(frame, (11, 11), 0)
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, greenLower, greenUpper)
# 构建一个绿色mask
# 执行一系列腐蚀、膨胀以去除面具上留下的任何小的斑点
mask = cv2.erode(mask, None, iterations=2)
mask = cv2.dilate(mask, None, iterations=2)
# cv2.imshow("mask", mask)
(T, thresh) = cv2.threshold(mask, 0, 255, cv2.THRESH_BINARY_INV)
# cv2.imshow("thresh", thresh)
output = cv2.bitwise_and(frame, frame, mask=thresh)
# cv2.imshow("output", output)
# 查找轮廓,初始化球的中心坐标
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
center = None
# print(len(cnts))
# 至少一个轮廓找到时继续处理
if len(cnts) > 0:
# # 根据面积过滤掉面积过大、过小的不合法轮廓
# cnts = [cnt for cnt in cnts if cv2.contourArea(cnt) > 2000]
# for (i, c) in enumerate(cnts):
# print(cv2.contourArea(c))
# draw_contour(frame, c, i)
# # 展示排序后的输出图像
# cv2.imshow("res", frame)
# cv2.waitKey(0)
# 寻找mask中面积最大的轮廓,并计算最小外接圆和质心(质心只是对象的中心(x,y)坐标。)
c = max(cnts, key=cv2.contourArea)
((x, y), radius) = cv2.minEnclosingCircle(c)
M = cv2.moments(c)
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
# 当半径大于5时继续处理
if radius > 3:
# 在帧上绘制圆的中心,然后更新追踪点队列
cv2.circle(frame, (int(x), int(y)), int(radius),
(0, 255, 255), 2)
cv2.circle(frame, center, 5, (0, 0, 255), -1)
pts.appendleft(center)
# 实际跟踪对象的移动,然后使用该对象的移动来计算对象的移动方向,只使用对象的(x,y)-坐标:
# 遍历追踪点队列点
for i in np.arange(1, len(pts)):
# 如果追踪点为None,则跳过
if pts[i - 1] is None or pts[i] is None:
continue
# 检查缓冲队列是否已累计足够的点
if counter >= 5 and i == 1 and pts[-5] is not None:
# 计算x,y坐标的变化,并重新初始化方向文本变量
dX = pts[-5][0] - pts[i][0]
dY = pts[-5][1] - pts[i][1]
(dirX, dirY) = ("", "")
# 计算当前帧和前一帧之间对象的方向。然而,使用当前帧和前一帧有点不稳定。除非对象移动得非常快,否则(x,y)坐标之间的增量将非常小。
# 如果使用这个值来报告方向,那么结果将非常嘈杂,这意味着即使轨迹上微小的变化也会被视为方向变化(这些变化可能非常小,以至于肉眼几乎看不见(或者至少是微不足道的))
# 相反,更有可能对较大的对象移动感兴趣,并报告对象移动的方向,因此计算当前帧坐标与队列中较后帧坐标之间的差值。执行此操作有助于减少噪音和方向更改的错误报告。
# 通过降低阈值,可以使方向检测代码更加敏感。在这种情况下,20个像素的差异可以获得良好的结果。但是如果要检测微小的移动,只需减小该值即可。另一方面,如果只想报告大型对象的移动,只需增加该阈值即可。
# 确保x方向有有效移动
if np.abs(dX) > 20:
dirX = "East" if np.sign(dX) == 1 else "West"
# 确保y方向有有效移动
if np.abs(dY) > 20:
dirY = "North" if np.sign(dY) == 1 else "South"
# 当x,y方向均有移动时
if dirX != "" and dirY != "":
direction = "-".format(dirY, dirX)
# 仅有一个方向移动时
else:
direction = dirX if dirX != "" else dirY
print(dX, dY, str(get_angle(dX, dY)), direction)
# 否则,计算物体轨迹线的宽度,并绘制连接线
thickness = int(np.sqrt(args["buffer"] / float(i + 1)) * 2.5)
cv2.line(frame, pts[i - 1], pts[i], (0, 0, 255), thickness)
# 在帧上,展示移动的方向和dx,dy增量
cv2.putText(frame, direction, (5, 80), cv2.FONT_HERSHEY_SIMPLEX,
0.65, (0, 0, 255), 3)
cv2.putText(frame, "dx: , dy: , angle: ".format(dX, dY, str(get_angle(delta_x=dX, delta_y=dY))),
(5, frame.shape[0] - 5), cv2.FONT_HERSHEY_SIMPLEX,
0.35, (0, 0, 255), 1)
num = num + 1
cv2.imwrite("images/" + str(num) + ".jpg", frame)
time.sleep(0.020)
# 展示帧,并累计帧计数器
cv2.imshow("Frame", frame)
key = cv2.waitKey(1) & 0xFF
counter += 1
# 按下‘q’键退出
if key == ord("q"):
break
# 释放摄像头
if not args.get("video", False):
vs.stop()
# 否则,释放文件
else:
vs.release()
# 关闭所有窗口
cv2.destroyAllWindows()
参考
以上是关于使用Python,OpenCV追踪对象的轨迹,来确定其移动方向的主要内容,如果未能解决你的问题,请参考以下文章
OpenCV-Python视频分析(移动物体检测,物体追踪)