树莓派4B_OpenCv学习笔记12:OpenCv颜色追踪_画出轨迹

今日继续学习树莓派4B 4G:(Raspberry Pi,简称RPi或RasPi)

 本人所用树莓派4B 装载的系统与版本如下:

 版本可用命令 (lsb_release -a) 查询:

 Opencv 版本是4.5.1:

今日尝试使用倒叙的方式来学习Open'CV颜色追踪,尝试一种新的笔记写法吧......

代码是根据创乐博(MAKEROBO)的视频代码学习的 ,我只是查阅资料学习与解释

贴出的解释部分是由百度文心一言AI生成的...

文章提供测试代码讲解,整体代码贴出、测试效果图

目录

测试视频效果展示:

完整实例代码贴出:

代码小结:

创建掩膜/图像的膨胀腐蚀:

开运算 与 闭运算: 

掩膜(mask)上寻找各个轮廓(cnts)函数:

众多轮廓中(cnts)找到最大轮廓(C)函数:

计算轮廓(C)最小包围圆:

计算轮廓(C)的质心(center):

OpenCv画圆函数:

双端队列:

网上查阅资料贴出:


测试视频效果展示:

OpenCv颜色追踪

 

完整实例代码贴出:

这个代码例程实现了使用最小圆来圈出识别追踪到的颜色物体,并画出轨迹

颜色可以在代码中初始化的部分更改:(注意先将BGR转换为HSV色彩空间)

之后会详细解释其中的一些关键函数:

完整代码与解释如下:

# -*- coding: utf-8 -*-

# 导入必要的库
from collections import deque  # 导入双端队列,用于存储物体的中心点  
import numpy as np             # 导入numpy库,用于数学和数组操作  
import argparse                # 导入argparse库,用于处理命令行参数 
import imutils
import cv2                     # 导入OpenCV库,用于视频和图像处理 

ap = argparse.ArgumentParser() # 创建命令行参数解析器 
# 添加命令行参数:--video,可选的视频文件路径  
ap.add_argument("-v", "--video",help="path to the (optional) video file")
# 添加命令行参数:--buffer,双端队列的最大长度,默认为64  
ap.add_argument("-b", "--buffer", type=int, default=64,help="max buffer size")
# 解析命令行参数,并将结果存储在args字典中  
args = vars(ap.parse_args())

# 定义HSV颜色空间中的颜色范围,用于追踪颜色物体
colorLower = (18, 100, 100)
colorUpper = (38, 255, 255)

# 初始化双端队列,用于存储物体的中心点,最大长度为args["buffer"] 
pts = deque(maxlen=args["buffer"])
 
# 如果没有提供视频文件,则使用默认的摄像头
if not args.get("video", False):
	camera = cv2.VideoCapture(0)
# 否则,使用提供的视频文件路径
else:
	camera = cv2.VideoCapture(args["video"])

# 无限循环,读取和处理视频帧 
while True:
	(grabbed, frame) = camera.read() # 读取一帧图像 
 
  # 如果是视频文件,并且没有成功读取帧(即视频结束),则退出循环
	if args.get("video") and not grabbed:
		break
 
 
	frame = imutils.resize(frame, width=600) # 调整帧的大小为600像素宽,保持其原始宽高比 
	hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)# 将帧从BGR颜色空间转换为HSV颜色空间 
 
 
	mask = cv2.inRange(hsv, colorLower, colorUpper)# 创建一个只包含指定颜色范围内的像素的掩码 
  # 对掩码进行腐蚀和膨胀操作,以减少噪声并平滑形状(开运算)
	mask = cv2.erode(mask, None, iterations=2)
	mask = cv2.dilate(mask, None, iterations=2)
	
  # 在掩码上查找轮廓
	cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
	center = None  # 初始化中心点变量  
 
  # 如果找到了轮廓  
	if len(cnts) > 0:
		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"]))#
		# 如果最小包围圆的半径大于10像素,绘制圆和质心 
		if radius > 10:
			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)
	
	# 遍历双端队列中的点(除了第一个),绘制它们之间的线  
	for i in range(1, len(pts)):
		if pts[i - 1] is None or pts[i] is None:
			continue
 
    # 根据点在队列中的位置动态调整线的粗细
		thickness = int(np.sqrt(args["buffer"] / float(i + 1)) * 2.5)
		cv2.line(frame, pts[i - 1], pts[i], (0, 0, 255), thickness)
 
	cv2.imshow("Frame", frame)
	key = cv2.waitKey(1) & 0xFF
 
	if key == ord("q"):
		break
 

camera.release()
cv2.destroyAllWindows()

 

代码小结:

在仔细研究代码后,个人将其分为以下步骤:

1、定义命令行解释器(这是用于选择视频源是文件还是摄像头)

2、定义HSV色彩空间范围(确定识别的目标颜色)

接下来是while True:循环中:

1、调整帧大小

2、掩膜 通过cv2.inRange()函数,指定一个HSV颜色空间中的颜色范围,

      从而确定并提取图像中特定颜色范围内的所有像素区域 

      之后对掩膜进行“开运算”  ——  腐蚀后立即膨胀

3、寻找掩膜的轮廓(可能不止一个,因为符合颜色的像素矩阵区域不止一个),

      并根据所有的轮廓,找到最大的那个Numpy矩阵 C

      再通过C计算出包裹它所需的最小包围圆

      计算C轮廓的质心,记录质心坐标到 center 变量

 4、如果最小包围圆半径大于10,就绘制圆与质心

 5、将质心添加到双端队列左端

       遍历双端队列中记录的点,绘制它们之间的线

       根据点在队列中的位置,动态调整线的粗细  

创建掩膜/图像的膨胀腐蚀:

相关图像处理之前文章中提到过,这里不多解释,但讲一下 开运算 与 闭运算:

文章地址贴出:

树莓派4B_OpenCv学习笔记6:OpenCv识别已知颜色_运用掩膜-CSDN博客 

树莓派4B_OpenCv学习笔记9:图片的腐蚀与膨胀-CSDN博客

 

开运算 与 闭运算: 

 在调用cv2.erode()之后立即调用cv2.dilate()(或相反的顺序)是一种常见的技术,称为开运算(opening)或闭运算(closing)。

开运算通常用于消除小的对象(在噪声中)

而闭运算则用于连接小的断裂或填充小的孔洞

代码中,先腐蚀后膨胀的组合会稍微平滑对象的形状并减少噪声。

 

掩膜(mask)上寻找各个轮廓(cnts)函数:

cv2.findContours 函数用于检测图像中的轮廓

实例代码中是这样使用的:

cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]

cnts  接受到的数据类型是一个列表(list),其中每个元素都是一个NumPy数组(numpy.ndarray)。这些NumPy数组代表检测到的轮廓,每个轮廓由一系列的点(边界上的像素坐标)组成,这些点以(x, y)的形式表示。

# 假设 cnts 是一个包含两个轮廓的列表:

(实际可能不止一俩个轮廓、图片中掩膜包含颜色范围不止一个,根据像素点来的)

cnts = [

             array([[x1_1, y1_1], [x1_2, y1_2], ..., [x1_n, y1_n]]), # 第一个轮廓的点集      

             array([[x2_1, y2_1], [x2_2, y2_2], ..., [x2_m, y2_m]]) # 第二个轮廓的点集

            ]

传入参数解释:

  1. image:输入图像,通常是二值图像(即,黑白图像)。这是因为轮廓检测通常在二值图像上执行,其中白色区域表示目标对象,黑色区域表示背景。

    • 类型:numpy.ndarray
    • 示例:经过阈值处理或Canny边缘检测等步骤后得到的二值图像。
  2. mode:轮廓检索模式。它定义了轮廓检索的方式,例如是否只检索最外部的轮廓,是否建立轮廓的层次结构等。

    • 类型:整数
    • 取值:
      • cv2.RETR_EXTERNAL:只检索最外层的轮廓。
      • cv2.RETR_LIST:检索所有的轮廓,但不建立它们之间的层次关系。
      • cv2.RETR_CCOMP:检索所有的轮廓,并建立两个层次。外层的轮廓包含它们的内层轮廓(例如,如果一个白色对象在另一个白色对象内部,那么外部对象的轮廓和内部对象的轮廓都会被检索到)。
      • cv2.RETR_TREE:建立一个轮廓的层次结构。
  3. method:轮廓近似方法。它定义了轮廓点的近似方式。

    • 类型:整数
    • 取值:
      • cv2.CHAIN_APPROX_NONE:保存轮廓的所有点。
      • cv2.CHAIN_APPROX_SIMPLE:只保存轮廓的端点(即,曲线的开始和结束点)和拐点。
      • cv2.CHAIN_APPROX_TC89_L1cv2.CHAIN_APPROX_TC89_KCOS:这些是其他轮廓近似方法,但通常cv2.CHAIN_APPROX_SIMPLE就足够了。

众多轮廓中(cnts)找到最大轮廓(C)函数:

c = max(cnts, key=cv2.contourArea)

cv2.contourArea 是一个用于计算轮廓面积的函数,它接受一个轮廓(通常是一个点的数组)作为输入,并返回该轮廓的面积。 

key 参数接受一个函数,该函数被用于从 cnts 列表中的每个元素(即每个轮廓)提取一个用于比较的值。在这个例子中,cv2.contourArea 被用作这个提取函数。

c 接收到的数据类型是一个 NumPy 数组(numpy.ndarray),这个数组表示了检测到的最大轮廓的点集。具体来说,这个数组通常是一个二维数组(或称为矩阵),其中每一行代表轮廓上的一个点,每个点由两个或三个整数坐标组成(对于二维图像通常是 (x, y),对于三维图像可能是 (x, y, z),但通常轮廓处理是在二维图像上进行的)。

轮廓通常是由一系列的点组成的,并且 OpenCV 的 findContours 函数返回的是点集的列表(每个列表代表一个轮廓),因此 c 的确切形状可能会稍有不同,但它总是包含多个表示轮廓上点的二维数组。此外,每个点的坐标通常是以单个数组 [x, y] 的形式出现的,但在轮廓数组中,这些点可能又被封装在一个额外的数组中(如上所示),或者可能是“扁平化”的(即没有额外的数组封装)。

计算轮廓(C)最小包围圆:

((x, y), radius) = cv2.minEnclosingCircle(c)

函数会计算轮廓 c 的最小包围圆,并返回圆心和半径。

圆心的坐标是一个元组 (x, y),其中 xy 是浮点型数值,表示圆心的坐标位置。

radius 也是浮点型数值,表示圆的半径。

计算轮廓(C)的质心(center):

 M = cv2.moments(c)

 cv2.moments(c) 函数会计算轮廓 c 的图像矩。

图像矩可以帮助计算有关轮廓的一些属性,比如面积、质心等。

M接受到的是一个字典(dict)类型的数据,这个字典包含了轮廓c的多个图像矩。这些图像矩是通过对轮廓上的点进行积分运算得到的,可以用来描述轮廓的形状和特性。

M字典中的键(key)是字符串类型,代表不同类型的图像矩,而对应的值(value)是浮点数类型(如float),表示该图像矩的具体数值。OpenCV中计算得到的图像矩主要包括以下几种:

  • 'm00': 零阶矩,表示轮廓内的像素总数(即轮廓的面积)。
  • 'm10', 'm01': 一阶矩,与轮廓的质心位置有关。
  • 'm20', 'm02', 'm11': 二阶矩,与轮廓的旋转和缩放特性有关。
  • 更高阶的矩:OpenCV也支持计算更高阶的图像矩,但通常在实际应用中,低阶矩(尤其是零阶矩和一阶矩)已经足够描述轮廓的基本特性。

M字典的具体形式如下:

例如M['m00']会返回轮廓的面积,M['m10']M['m01']可以用来计算轮廓的质心位置。

注意:在计算质心位置时,需要确保M['m00'](即轮廓面积)不为0,以避免除以零的错误。如果M['m00']为0,表示轮廓为空或者没有正确地检测到轮廓。

文心大模型3.5生成

轮廓的质心(也称为重心)可以通过以下公式计算:

因此代码中的center如下计算:

center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))

OpenCv画圆函数:

cv2.circle(img, center_coordinates, radius, color, thickness)

  • img:这是你要在其上绘制圆的图像。在这个例子中,它是frame,即你可能正在处理的视频帧或图像。
  • center_coordinates:这是圆的中心坐标,由两个值(x, y)组成,分别代表横坐标和纵坐标。在这个例子中,(int(x), int(y))是第一个圆的中心坐标,而center(它应该是一个元组,例如(cx, cy))是第二个圆的中心坐标(即质心)。
  • radius:这是圆的半径,以像素为单位。在这个例子中,int(radius)是第一个圆的半径,而第二个圆(质心)的半径是由thickness参数控制的(因为我们传递了-1作为thickness,所以它会绘制一个填充的圆)。
  • color:这是圆的颜色。它是一个BGR元组,即蓝色、绿色和红色的组合。在这个例子中,(0, 255, 255)代表青色(因为没有红色,蓝色和绿色都是最大值),而(0, 0, 255)代表蓝色。
  • thickness:这是圆的线条厚度。如果它是正数,那么它就代表线条的粗细(以像素为单位)。如果它是负数(如-1),那么圆就会被填充。在这个例子中,第一个圆有一个线条厚度为2的轮廓,而第二个圆(质心)是填充的。
  • 文心大模型3.5生成

双端队列:

双端队列(Deque,全称为“double-ended queue”)是一种具有队列和栈的性质的数据结构。它支持从两端弹出和插入元素的操作,因此得名“双端”。具体来说,双端队列允许我们在其前端(front)和后端(rear)进行添加(append/push)和删除(pop)操作。

在Python中,collections.deque 是一个双端队列的实现。与列表(list)相比,deque 在从两端添加和删除元素时具有更高的效率,因为列表在列表中间进行插入或删除操作需要移动其他元素,而 deque 不需要。

在你给出的代码片段中,pts 可能是一个 collections.deque 类型的对象,用于存储一系列质心点。这些质心点可能是从图像中检测到的对象(如移动物体)的中心点。

代码解释:

  1. pts.appendleft(center):将新检测到的质心 center 添加到双端队列 pts 的左端(即前端)。

  2. 循环遍历 pts 中的点(除了第一个),绘制它们之间的线:

    • for i in range(1, len(pts)): 遍历从索引1开始到最后一个元素的点。
    • if pts[i - 1] is not None and pts[i] is not None: 确保两个相邻的点都不是 None。这可能是因为某种原因(如检测失败)某些点可能未被设置。
    • thickness = int(np.sqrt(args["buffer"] / float(i + 1)) * 2.5):这里动态计算线的粗细。其中 args["buffer"] 可能是一个预设的缓冲值,用于控制线的粗细变化。随着 i 的增加(即距离队列开始的位置越远),线的粗细会逐渐减小。
    • cv2.line(frame, pts[i - 1], pts[i], (0, 0, 255), thickness):在图像 frame 上绘制从 pts[i - 1]pts[i] 的红色线,线的粗细为前面计算得到的 thickness

通过这种方式,你可以看到随着时间的推移(或随着更多质心被添加到队列中),这些质心点之间的线会逐渐变细,从而可能提供一种视觉上的“轨迹衰减”效果,使得最近的点之间的线更粗,而较早的点之间的线更细。

文心大模型3.5生成

网上查阅资料贴出:

[树莓派基础]8.树莓派OpenCV颜色追踪讲解_哔哩哔哩_bilibili

文心一言

 

  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NULL指向我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值