# 注意
本项目创建时间:2024-7-17
本文标记为OpenCV入门参考项目以及电赛典型范例,由题主长期维护 --2024-10-2
一、图像处理方案
Opencv: 开源计算机视觉处理库,使用嵌入式SOC板卡作为摄像头模块,提供相对可靠的性能。
由于激光点的亮度远高于其他位置,所以采用HSV格式的色彩空间,对图像H,S,V域进行识别。
若要区分红绿激光,那么就对图像的RGB空间进行处理
2. 电工胶带的识别
-
使用原图像->灰度图像->设置阈值后进行二值化的方法,使得黑胶带区域识别为纯白色,其余部分全部为纯黑色区域。关于调参:建议使用创建滑块窗口的方法,进行动态调参,减少调参上浪费的时间。
-
摄像头容易受到外接环境光的干扰,可以通过调节分辨率,设置摄像头的图像获取范围,也有一种更为实用的方法:ROI(感兴趣区),可以通过创建掩膜的方式,只对ROI区域进行处理,这样就排除了外界环境的干扰。当然也可以使用切片的方法,定义一个变量用于读取图像的指定范围,然后再进行图像处理,需要显示参数时,再将一些特征点、辅助线、坐标点、数据显示在想要观看的图像上,使用opencv中的imshow进行可视化显示(注意:imshow是非常耗时的操作,在调试时可以使用,在最终运行时建议注释掉,提升程序执行效率,或者采取图像编解码的方法,使用TCP/UDP协议,ROS通信等方式进行远程图传,降低软件资源的使用)
-
ROI区域也不一定是适宜的图像处理区域,也可能有一些噪声信号的干扰,这可能就要需要用到一些滤波处理的算法,如:高斯平滑滤波(高斯模糊)。
-
胶带容易受激光点的影响,导致二值化图像被==“分割”==,所以需要对胶带的二值化图像先膨胀,后进行图形学闭运算缝合胶带二值化图像漏洞
3. 串口通信的关键问题
- 数据帧:一开始采用字符串发送地方法。结果单片机很容易接收不到一些数据帧
- 通信速率:19200
3. 激光“巡线”的路径规划
-
第一种方法:程序不断对图像进行处理,边缘检测,封闭图形检测,然后激光沿着检测出来的图形边沿进行巡线
-
第二种方法:采用“路径规划的方法”,进行一定时间的图像采集后,确定好激光的运行路径,这样,即避免了激光对矩形框识别的强光干扰,又大大减少了程序运行的资源损耗,只需要执行激光跟随部分的程序,不需要再对图像进行边沿检测。
二、程序设计
视觉部分
1. 摄像头处理部分
1.1 摄像头的类封装
GuideLine 的作用:绘制辅助线
思考:Python中函数对形参的调用,会不会影响到实参?
1 | # 摄像头类创建 |
1.2 摄像头的图像获取
1 | ret, frame = Watch.Video.read() #获取摄像头图像数据 |
1.3 相机资源的释放
思考:Python中不对窗口,或者使用Python语言的硬件资源不释放会怎么样?
1 | Watch.Video.release() |
2. 激光追踪相关
要想实现激光点的巡线,以及红绿激光的相互跟随,首先要做到单个激光点的精确识别与控制移动,这里我们使用了二维舵机云台,精度在能够完成基本要求之内。使用USB 1080P摄像头进行画面捕获。
激光点在图像中的亮度远远大于其他图像像素点,不难想到在HSV空间中对图像进行操作更容易识别到激光点。
2.1 激光点识别的图形预处理
通过调用v2.cvtColor()函数的方法,将图像颜色空间转换到 HSV中,然后通过设定阈值,进行图像的二值化(阈值调节建议采用滑块创建的方法,并且函数需要赋初值)
1 | # 寻找激光点 |
2. 2 激光点的轮廓识别
调用cv2.findContours()函数,对封闭图形进行检测,并且绘制出激光点的图形识别轮廓
函数解释:
mask_laser
:这是输入的二值图像,也就是说,它应该是经过阈值处理或其他形式的图像处理后得到的黑白图像(通常是单通道的,只有 0 和 255 两个像素值)。cv2.RETR_EXTERNAL
:这是轮廓检索模式(Contour Retrieval Mode),指定了轮廓的检索模式。RETR_EXTERNAL
表示只检索最外层的轮廓,忽略内部的轮廓。还有其他的检索模式,比如RETR_LIST
、RETR_TREE
等,可以根据需要选择不同的模式。cv2.CHAIN_APPROX_SIMPLE
:这是轮廓的逼近方法(Contour Approximation Method),指定了轮廓的表示方法。CHAIN_APPROX_SIMPLE
表示压缩水平、垂直和对- 第一个返回值
contours_laser
是一个列表,其中每个元素是一个 numpy 数组,表示一个检测到的轮廓
cv2.minAreaRect()
函数解释:
contour
:这是一个轮廓,通常是通过findContours
函数找到的其中一个轮廓对象,它是一个包含轮廓点的 numpy 数组。cv2.minAreaRect(contour)
:这个函数接收一个轮廓作为输入,并返回一个RotatedRect
对象,它表示包围轮廓的最小旋转矩形框。
具体来说,返回的 RotatedRect
对象包含以下信息:
center
:矩形框的中心点坐标(cx, cy)
。size
:矩形框的宽度和高度(width, height)
。angle
:矩形框相对于水平方向的旋转角度(逆时针为正)
获取矩形框角点:
box = cv2.boxPoints(rect)
:cv2.boxPoints
函数接收一个RotatedRect
对象作为输入,并返回该旋转矩形框的四个顶点坐标。- 这些顶点坐标是浮点型数据,表示为一个包含四个点的 numpy 数组。
box = np.int0(box)
:- 这一步将上一步得到的四个顶点坐标
box
转换为整数类型的 numpy 数组。 np.int0()
函数会将浮点数数组四舍五入为最接近的整数,并返回一个整数类型的 numpy 数组。
- 这一步将上一步得到的四个顶点坐标
1 | # 寻找轮廓 |
2.3激光点的颜色区分
imge.shape[]
image.shape
:这是一个 numpy 数组属性,用于获取图像的尺寸和通道数信息。image.shape[:2]
:这是对shape
属性进行切片操作,[:2]
表示取前两个元素,即图像的高度和宽度。
具体来说,如果 image
是一个图像的 numpy 数组,例如形状为 (height, width, channels)
,那么 image.shape[:2]
将返回一个包含图像高度和宽度的元组 (height, width)
。
这种操作通常用于获取图像的空间尺寸信息,以便在处理图像时进行尺寸相关的计算或操作
方圆坐标确定:
- 获取输入坐标:
x, y = coords
:这行代码将变量coords
解构为x
和y
,分别表示中心点的横纵坐标。
- 确定左上角坐标:
x_start = max(0, x - radius)
:计算方形区域的左上角横坐标。x - radius
表示以中心点x
为基础,向左偏移半径radius
的距离。max(0, ...)
确保左上角横坐标不会小于 0,即不会超出图像左边界。y_start = max(0, y - radius)
:计算方形区域的左上角纵坐标。同理,y - radius
表示以中心点y
为基础,向上偏移半径radius
的距离。max(0, ...)
确保左上角纵坐标不会小于 0,即不会超出图像上边界。
- 确定右下角坐标:
x_end = min(width - 1, x + radius)
:计算方形区域的右下角横坐标。x + radius
表示以中心点x
为基础,向右偏移半径radius
的距离。min(width - 1, ...)
确保右下角横坐标不会超过图像的右边界。y_end = min(height - 1, y + radius)
:计算方形区域的右下角纵坐标。同理,y + radius
表示以中心点y
为基础,向下偏移半径radius
的距离。min(height - 1, ...)
确保右下角纵坐标不会超过图像的下边界。
get_pixel_sum
这段代码用于从给定的区域中提取红色(R)和绿色(G)颜色通道的值。让我来解释一下这段代码的含义:
roi[:, :, 2]
:roi
应该是图像的一个区域,即感兴趣的区域(Region of Interest,ROI)。:
是一个表示所有行或所有列的切片操作。[ : , : , 2]
表示选取所有行的所有列的第三个颜色通道。在 BGR 颜色空间中,第三个通道对应的是红色通道,所以这行代码获取了 ROI 中所有的红色通道值。
roi[:, :, 1]
:- 与上面的代码类似,这行代码选取了所有行的所有列的第二个颜色通道。在 BGR 颜色空间中,第二个通道对应的是绿色通道,所以这行代码获取了 ROI 中所有的绿色通道值。
在大多数图像处理库中(如 OpenCV),图像默认是以 BGR 顺序存储的,而不是 RGB。这意味着在获取颜色通道时需要使用 [ : , : , 2]
来获取红色通道,[ : , : , 1]
来获取绿色通道,[ : , : , 0]
来获取蓝色通道。
1 | # 激光点RGB值获取 |
cv2.circle(frame, laser_coords, 4, (0, 0, 255), -1)
绘制实心圆
cv2.putText()
- 参数解释:
frame
:这是目标图像帧,即要在其上添加文本的图像。"RED"
:要写入的文本内容,这里是字符串 “RED”。(laser_coords[0] - 10, laser_coords[1] - 10)
:文本放置的起始位置,由laser_coords
提供,向左上方偏移了(10, 10)
的像素值。cv2.FONT_HERSHEY_SIMPLEX
:字体类型,这里使用了简单的字体类型。0.5
:字体大小因子,控制文本大小相对于基础字体的比例。(0, 0, 255)
:文本的颜色,这里是红色,对应 BGR 颜色空间中的(0, 0, 255)
。2
:文本的线宽,即文本轮廓的粗细。
- 作用说明:
- 这行代码的主要作用是在图像帧的指定位置绘制红色的文本 “RED”。通常用于在图像或视频中标记特定的对象、位置或状态信息。
- 注意事项:
- 如果
laser_coords
提供的坐标(x, y)
位于图像的边缘,确保(x - 10, y - 10)
不会超出图像边界,以避免出现越界错误。
- 如果
1 | # 是否获取到激光点矩形框的中心坐标,防止读取值为空 |
2.4差值计算与串口通信
PS:上位机与下位机通信,建议采用十六进制:帧头+数据类型+数据内容 的单帧数据包格式,这样才能保证下位机能够同步反应上位机发送的特征值,减小通信的复杂度,同时可靠性和速率较高,不建议采用发送字符串类型的通信。
1 | if mode == 'A': |
3.胶带矩形框识别
3.1 ROI区域的创建
PS: 使用ROI区域后,又想还原到原图像上对图像进行处理,就需要对坐标进行还原操作,保证图形每个像素点的索引和原图像对应
1 | class ROIPixelProcessor: |
3.2 图像的预处理
通过BGR转灰度,自设定阈值进行二值化地方法,将胶带部分转换为白色单值,其他部分全为黑色单值,并且通过滤波、腐蚀、闭运算地方法对图形进行处理,降低噪点干扰,提高图形识别完整性。
1 | if time.time()-start_time <= 5: |
3.3 矩形框轮廓的识别
同样地,使用封闭图形检测算法,检测矩形框轮廓,这里使用 ROI处理,要特别注意像素点索引(坐标)的复原的操作。
即:
1 | # 将轮廓绘制在原始图像上 |
近似曲线
注意:这里approx_contour得到的是最终近似矩形的四个角点的坐标!
1 | epsilon = 0.01 * cv2.arcLength(max_contour, True) |
cv2.arcLength
用于计算轮廓的周长或弧长。epsilon
是一个近似精度参数,通常是轮廓周长的某个百分比,这里设定为总周长的1%。cv2.approxPolyDP
用来对轮廓进行多边形逼近,通过减少顶点的数量来近似表示轮廓。approx_contour
是近似后的多边形轮廓。
找到左右轮廓的边界点(列表)
leftmost[0]就是左轮廓第一个点
1 | leftmost = min(path, key=lambda x: x[0]) # 左轮廓边界坐标点 |
path
是一组坐标点的集合,通常表示为一个列表或数组。min(path, key=lambda x: x[0])
和max(path, key=lambda x: x[0])
分别用于找到path
中横坐标x
最小和最大的点,即左轮廓和右轮廓的边界点。
参数说明:
lambda x: x[0]
是一个匿名函数,用于指定以每个点的横坐标x
作为比较的关键字。因此,min
函数找到具有最小横坐标的点,而max
函数找到具有最大横坐标的点。
计算轮廓的中心点
1 | center_point_x = int(sum([point[0] for point in path]) / len(path)) |
path
是一组轮廓的坐标点集合,通常表示为一个列表或数组。sum([point[0] for point in path])
对path
中所有点的横坐标进行求和。sum([point[1] for point in path])
对path
中所有点的纵坐标进行求和。len(path)
是path
中点的数量,即轮廓的长度或大小。
参数说明:
center_point_x
和center_point_y
分别是计算得到的轮廓中心点的横坐标和纵坐标。center_point
是由center_point_x
和center_point_y
组成的元组,表示轮廓的中心点坐标(x, y)
。
作用:
- 这段代码的作用是计算轮廓
path
的几何中心点,通过对所有点的坐标进行平均值计算得出
1 | #cv2.imshow('dilation',erosion) |
3-4 矩形框的路径规划-插值算法
如果让激光点沿着轮廓一个一个坐标点去走,这样计算量会相当大,不如将矩形轮廓通过近似得到四个角点,再通过插值法,设置相邻点的插值参数,得到12个路径点,这样就既保证了巡线的稳定性,也大大减少了巡线的计算量。同时,通过调用time库来计时,程序启动时定时5s后,这段时间提供给我们确认摄像机状态,移动ROI区域,完成路径规划,实现“动态建图”的效果。
1 | #插值算法 |