[Day 5 of 17]视频中球追踪

Ball Tracking with OpenCV

https://pyimagesearch.com/2015/09/14/ball-tracking-with-opencv/?utm_source=Drip&utm_medium=Email&utm_campaign=CVandDLCrashCourse&utm_content=email5

goal:

  1. Detect the presence of the ball
  2. Track the ball as it moves around in the video frames, drawing its previous position as it moves.
# import the necessary packages
from collections import deque       
# 使用deque,一种类似list的数据结构,具有超快的append和pop功能
# 用来维护追踪的球轨迹列表
from imutils.video import VideoStream
import numpy as np
import argparse
import cv2
import imutils
import time

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-v", "--video",
	help="path to the (optional) video file")
# 可选命令行参数,提供则指向视频文件;否则访问摄像头
ap.add_argument("-b", "--buffer", type=int, default=64,
	help="max buffer size")
# 指定:deque的最大长度,维护了我们正在跟踪的球的previous(x,y)坐标的列表
# 当队列已满时,新的元素将从队头删除,以保持队列的最大长度不变。
# 当向队尾插入新元素时,队列会自动调整以满足最大长度的限制。
args = vars(ap.parse_args())



# define the lower and upper boundaries of the "green"
# ball in the HSV color space, then initialize the
# list of tracked points
greenLower = (29, 86, 6)
greenUpper = (64, 255, 255)
# 在HSV颜色空间中定义“绿色”球的上下边界
pts = deque(maxlen=args["buffer"]) 
# 然后初始化跟踪点列表,即最大缓冲区大小(默认为64)初始化我们的pts deque

# if a video path was not supplied, grab the reference
# to the webcam
if not args.get("video", False):
	vs = VideoStream(src=0).start()
	# VideoStream(src=0) 创建了一个视频流对象 vs,并使用参数 src=0 指定了视频源的索引。
    # start()方法会启动视频流,开始读取视频帧并提供实时的图像数据
# video空:访问摄像头

# otherwise, grab a reference to the video file
else:
	vs = cv2.VideoCapture(args["video"])
	# cv2.VideoCapture(args["video"])创建了一个视频捕获对象 `vs`
# video不空:指向视频文件

# allow the camera or video file to warm up
time.sleep(2.0)


# keep looping
while True:
	# grab the current frame
	frame = vs.read()       # frame 将包含成功读取到的图像帧数据

	# handle the frame from VideoCapture or VideoStream
	frame = frame[1] if args.get("video", False) else frame
	# 从args获得video值,如果不存在返回False;存在即frame = frame[1](实际的图像帧数据)
    # 如果参数 "video" 的值不存在, frame不变

	# if we are viewing a video and we did not grab a frame,
	# then we have reached the end of the video
	if frame is None:
		break

	# resize the frame, blur it, and convert it to the HSV
	# color space
	frame = imutils.resize(frame, width=600)    # 调整帧大小,能提高处理速度
	blurred = cv2.GaussianBlur(frame, (11, 11), 0)  # 高斯模糊,减少高频,专注结构对象
	hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV)  # 转换为HSV颜色空间

	# construct a mask for the color "green", then perform
	# a series of dilations and erosions to remove any small
	# blobs left in the mask
    # 制作一个颜色为“绿色”的mask,然后进行一系列的扩张和侵蚀,以去除mask中残留的小斑点
	mask = cv2.inRange(hsv, greenLower, greenUpper)
	# 过滤出指定颜色范围内的像素
    # 在hsv中每个像素位置判断,返回一个与 hsv 相同大小的mask数组
    # mask中的像素值为 0(不在)或 255(在),表示像素是否在指定颜色范围内
	mask = cv2.erode(mask, None, iterations=2)
	# 腐蚀,默认腐蚀内核,迭代次数为2
    # 腐蚀  用于缩小或消除图像中物体的边界
    # 通过将内核与图像进行逐像素的比较,如果内核完全包含在图像中的某个区域,则该区域的像素值为 1,否则为 0。
    # 作用:减小mask中前景(绿色区域)的大小或连接性。
    # 常用于去除图像中的噪声、分离接触的物体或者缩小物体的尺寸等应用场景
	mask = cv2.dilate(mask, None, iterations=2)
	# 膨胀  用于扩大图像中物体的边界。
    # 通过将内核与图像进行逐像素的比较,如果内核与图像中的某个区域有重叠,则该区域的像素值为 1,否则为 0。
    # 扩大掩膜中前景(绿色区域)的大小或连接性
    # 常用于填充图像中的空洞、连接断开的物体或者增大物体的尺寸

# 计算绿色球的轮廓(即轮廓)并将其绘制在我们的框架上
    # find contours in the mask and initialize the current
	# (x, y) center of the ball
	cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
		cv2.CHAIN_APPROX_SIMPLE)
	# mask中找到轮廓
    # cv2.RETR_EXTERNAL 是轮廓检索模式,表示只检测最外层的轮廓
    # cv2.CHAIN_APPROX_SIMPLE 是轮廓近似方法,表示使用简单的近似方法来压缩轮廓的表示
    # cv2.findContours() 函数将对输入进行轮廓检测,返回检测到的轮廓列表
    # 每个轮廓是一个表示闭合边界的点集合
	cnts = imutils.grab_contours(cnts)      # 版本兼容
	
	center = None   # 初始化球的当前中心

	# only proceed if at least one contour was found
	if len(cnts) > 0:
		# find the largest contour in the mask, then use
		# it to compute the minimum enclosing circle and
		# centroid
		c = max(cnts, key=cv2.contourArea)  # 找到mask中最大轮廓
		# cv2.contourArea 计算轮廓的面积
        # 在轮廓列表 cnts 中找到满足指定条件的最大值,将轮廓的面积作为比较的关键字,

		((x, y), radius) = cv2.minEnclosingCircle(c)    #计算包围轮廓c的最小外接圆
		# 返回圆心坐标和半径
        # 找到一个最小的圆,使得轮廓 c 完全位于圆内
		M = cv2.moments(c)
		# 计算轮廓 c 的矩
		center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))   # 质心
		# 在cv中,轮廓的重心是指轮廓区域的质心或中心点,通常用于表示对象的位置
        # 重心计算:通过轮廓的一阶矩(m10和m01)和零阶矩(m00)
        # 重心的 x 坐标 = m10 / m00
        # 重心的 y 坐标 = m01 / m00
        # M["m10"] 表示轮廓的一阶矩的 x 分量,M["m01"] 表示轮廓的一阶矩的 y 分量,M["m00"] 表示轮廓的零阶矩(面积)

		# only proceed if the radius meets a minimum size
		if radius > 10:     # 半径满足最小尺寸时才进行,确保最小包围圆的半径足够大
			# draw the circle and centroid on the frame,
			# then update the list of tracked points
            # 画两个圆:一个围绕球本身,另一个指示球的质心
			cv2.circle(frame, (int(x), int(y)), int(radius),
				(0, 255, 255), 2)
			
			cv2.circle(frame, center, 5, (0, 0, 255), -1)

	# update the points queue
	pts.appendleft(center)      # 将质心appear到更新跟踪点列表
	

# 最后一步:绘制球的轨迹,或者简单地绘制检测到球的过去N(x,y)坐标

    # loop over the set of tracked points
    # 在一组跟踪点上循环
	for i in range(1, len(pts)):
		# if either of the tracked points are None, ignore them
		if pts[i - 1] is None or pts[i] is None:
			continue
        # 如果当前点或前一点为“无”(表示在该给定帧中未成功检测到球),则忽略当前索引,继续在点上循环
        # 检查队列中前一个和当前位置的重心坐标是否都存在。
        # 如果其中任意一个重心坐标不存在(即为 None),则说明无法进行连线或绘制操作,需要继续下一个迭代。

		# otherwise, compute the thickness of the line and
		# draw the connecting lines
		thickness = int(np.sqrt(args["buffer"] / float(i + 1)) * 2.5)
		# 计算轨迹厚度
        # 根据输入参数和迭代次数得出的一个线宽值
        # 根据当前的迭代次数和队列的最大长度,动态调整线宽的大小。
        # 随着迭代次数的增加,线宽逐渐变细,使得绘制的线条呈现出一种渐变效果,能够更好地表示轨迹的运动特征。
		cv2.line(frame, pts[i - 1], pts[i], (0, 0, 255), thickness)
		# 框架上绘制轨迹
		

	# show the frame to our screen
	cv2.imshow("Frame", frame)  # 显示帧
	key = cv2.waitKey(1) & 0xFF

	# if the 'q' key is pressed, stop the loop
	if key == ord("q"):
		break

# if we are not using a video file, stop the camera video stream
if not args.get("video", False):
	vs.stop()

# otherwise, release the camera
else:
	vs.release()

# close all windows
cv2.destroyAllWindows()

注:

  1. deque是一种双端队列(double-ended queue)数据结构,它是由collections模块提供的一种容器类型。通过from collections import deque导入。deque可以在队列的两端进行高效的插入和删除操作。适合在队头和队尾频繁插入和删除元素的场景。它还可以限制队列的最大长度,以防止无限增长。当队列已满时,新的元素将从队头删除,以保持队列的最大长度不变。当向队尾插入新元素时,队列会自动调整以满足最大长度的限制。
  2. VideoStream 是一个用于视频捕获和处理的类,它提供了一个简单的接口来读取实时视频流或已经录制的视频文件。将 src=0 传递给 VideoStream,我们指定要使用默认的摄像头作为视频源。摄像头索引通常从0开始递增,所以 src=0 表示使用第一个可用的摄像头。start() 方法会启动视频流,开始读取视频帧并提供实时的图像数据。后续使用 vs 对象来访问视频流,并进行图像处理或分析。vs = VideoStream(src=0).start()
  3. vs = cv2.VideoCapture(args["video"]),cv2.VideoCapture(args["video"]) 创建了一个视频捕获对象 vs,并使用参数 args["video"] 指定了要打开的视频文件路径。
  4. vs.read() 是用于从视频流或视频捕获对象中读取一帧图像,read()方法读取视频流的下一帧图像
  5. 对图像先腐蚀后膨胀–即开运算(Opening)。腐蚀消除图像中的小细节、噪声,突出物体边界,使得物体边界向内收缩同时保持物体整体形状;膨胀填充物体空洞、连接物体断开部分,增大物体尺寸,边界向外扩张,保持整体形状。结合的开运算效果:消除小细节和噪声,分离接触的物体,平滑边界,去除空洞,有助于改善图像质量和凸显目标物体。
  6. 矩:矩(Moments)是一种描述图像特征和形状的数学工具。矩可以用于计算图像的重心、面积、形状特征等。矩是对图像像素的空间分布和强度分布进行数学建模的结果。它们提供了关于图像的统计信息,可以用于计算图像的各种特征。在计算机视觉中,常用的矩包括零阶矩(表示图像的总体强度)、一阶矩(表示图像的重心)、二阶矩(表示图像的方向和形状)、中心矩(表示图像的归一化形状)等。总而言之,矩是用于描述图像特征和形状的数学工具,通过对图像的像素进行加权求和,可以计算出图像的各种统计特征和形状属性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值