今日继续学习树莓派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:breakframe = 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) & 0xFFif key == ord("q"):breakcamera.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]]) # 第二个轮廓的点集
]
传入参数解释:
image:输入图像,通常是二值图像(即,黑白图像)。这是因为轮廓检测通常在二值图像上执行,其中白色区域表示目标对象,黑色区域表示背景。
- 类型:
numpy.ndarray
- 示例:经过阈值处理或Canny边缘检测等步骤后得到的二值图像。
mode:轮廓检索模式。它定义了轮廓检索的方式,例如是否只检索最外部的轮廓,是否建立轮廓的层次结构等。
- 类型:整数
- 取值:
cv2.RETR_EXTERNAL
:只检索最外层的轮廓。cv2.RETR_LIST
:检索所有的轮廓,但不建立它们之间的层次关系。cv2.RETR_CCOMP
:检索所有的轮廓,并建立两个层次。外层的轮廓包含它们的内层轮廓(例如,如果一个白色对象在另一个白色对象内部,那么外部对象的轮廓和内部对象的轮廓都会被检索到)。cv2.RETR_TREE
:建立一个轮廓的层次结构。method:轮廓近似方法。它定义了轮廓点的近似方式。
- 类型:整数
- 取值:
cv2.CHAIN_APPROX_NONE
:保存轮廓的所有点。cv2.CHAIN_APPROX_SIMPLE
:只保存轮廓的端点(即,曲线的开始和结束点)和拐点。cv2.CHAIN_APPROX_TC89_L1
、cv2.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)
,其中x
和y
是浮点型数值,表示圆心的坐标位置。
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,表示轮廓为空或者没有正确地检测到轮廓。
轮廓的质心(也称为重心)可以通过以下公式计算:
因此代码中的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的轮廓,而第二个圆(质心)是填充的。
双端队列:
双端队列(Deque,全称为“double-ended queue”)是一种具有队列和栈的性质的数据结构。它支持从两端弹出和插入元素的操作,因此得名“双端”。具体来说,双端队列允许我们在其前端(front)和后端(rear)进行添加(append/push)和删除(pop)操作。
在Python中,
collections.deque
是一个双端队列的实现。与列表(list)相比,deque
在从两端添加和删除元素时具有更高的效率,因为列表在列表中间进行插入或删除操作需要移动其他元素,而deque
不需要。在你给出的代码片段中,
pts
可能是一个collections.deque
类型的对象,用于存储一系列质心点。这些质心点可能是从图像中检测到的对象(如移动物体)的中心点。代码解释:
pts.appendleft(center)
:将新检测到的质心center
添加到双端队列pts
的左端(即前端)。循环遍历
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
。通过这种方式,你可以看到随着时间的推移(或随着更多质心被添加到队列中),这些质心点之间的线会逐渐变细,从而可能提供一种视觉上的“轨迹衰减”效果,使得最近的点之间的线更粗,而较早的点之间的线更细。
网上查阅资料贴出:
[树莓派基础]8.树莓派OpenCV颜色追踪讲解_哔哩哔哩_bilibili
文心一言