0x00 往期博文
《HelloOriginBot::使用Python编写Joy手柄功能包》
[精选]《Originbot::find_object_2d书籍卡片识别》
0x01 实验环境
操作环境及软硬件配置如下:
- OriginBot机器人(视觉版/导航版)
- PC:Ubuntu (≥20.04) + ROS2 (≥Foxy)
- 巡线场景:黑色路径线,背景有明显反差(黑白)
知识点:
- 视觉巡线:使用自适应局部二值化方法处理图像找出黑线。
- 计算出黑线质心,根据质心控制机器人循迹。
0x02. adaptiveThreshold 自适应二值化
dst = cv.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst])
将自适应阈值应用于一个图像数据
Applies an adaptive threshold to an array.
该函数通过以下公式将灰度图转换为二值图:
The function transforms a grayscale image to a binary image according to the formulae:
- THRESH_BINARY
- THRESH_BINARY_INV
其中T(x,y)是针对每个像素单独计算的阈值(参见adaptiveMethod参数)。
where T(x,y) is a threshold calculated individually for each pixel (see adaptiveMethod parameter).
参数:
-
src: 8比特单通道的源图像,灰度图。
Source 8-bit single-channel image. -
dst: 处理结果,尺寸和类型和源图像一致。
Destination image of the same size and the same type as src. -
maxValue: 分配给满足条件的像素的非零值。
Non-zero value assigned to the pixels for which the condition is satisfied. -
adaptiveMethod: 要使用的自适应阈值算法,请参阅
AdaptiveThresholdTypes
。BORDER_REPLICATE | BORDER_ISOLATE
用于处理边界。
Adaptive thresholding algorithm to use, seeAdaptiveThresholdTypes
. TheBORDER_REPLICATE | BORDER_ISOLATED
is used to process boundaries.- cv.ADAPTIVE_THRESH_MEAN_C | 均值
- cv.ADAPTIVE_THRESH_GAUSSIAN_C | 高斯
-
thresholdType: 阈值类型必须是
THRESH_BINARY
或THRESH_MINARY_INV
,请参阅ThresholdTypes
或看一下上面的公式。
Thresholding type that must be eitherTHRESH_BINARY
orTHRESH_BINARY_INV
, seeThresholdTypes
. -
blockSize: 用于计算像素阈值的像素邻域的大小:3、5、7等奇数。
Size of a pixel neighborhood that is used to calculate a threshold value for the pixel: 3, 5, 7, and so on. -
C: 从平均值或加权平均值中减去常数 C(详见下文)。通常,它是正的,但也可能是零或负的。
Constant subtracted from the mean or weighted mean (see the details below). Normally, it is positive but may be zero or negative as well.
0x03 为什么使用 adaptiveThreshold?
参考
- https://www.cnblogs.com/ybqjymy/p/12822097.html
- https://www.cnblogs.com/GaloisY/p/11037350.html
- https://blog.csdn.net/bugang4663/article/details/109589177
cv.AdaptiveThreshold 既可以做边缘提取,也可以实现二值化,这是由邻域blockSize参数所确定的。
如果邻域参数非常小(比如3×3),那么很显然阈值的“自适应程度”就非常高,这在结果图像中就表现为边缘检测的效果。
如果邻域参数较大(比如31×31),那么阈值的“自适应程度”就比较低,这在结果图像中就表现为二值化的效果。
一般情况下,滤波器宽度应该大于被识别物体的宽度。否则block_size太小,计算出的均值将无法代表背景。
对此灰度图使用自适应阈值函数进行局部二值化处理,该算法可以利用白底黑线的颜色对比度,更精确地找出黑线。
注意:该算法在白底黑线的图纸上实验最佳。
0x04 图像识别效果对比
原始图像
HSV、LAB 色彩空间调参
全局二值化穷举 cv2.threshold
这两种方式,效果均不理想,易受外接环境(光线)干扰。
自适应二值化代码实现
# Convert BGR to GRAY
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# retval, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 全局二值化
ada_dst = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 101, 77) # 局部二值化
_2show = cv2.cvtColor(ada_dst, cv2.COLOR_BGR2RGB)
plt.imshow(_2show)
可见‘自适应’二值化效果最佳;
0x05 图像分隔
核心思路:
- 机器人欲寻的线必定在其脚下,且机器人低头走路。
- 随切割图像于高1/2处取下半部分,以消除上半部分可能存在的外部干扰。
- 对下半部分,再做三次图像切割,通过权重计算出当前线段质心位置,根据该值控制机器人转向或前进。
5.1 定义局部变量
line_centroid_x # 质心 x 坐标, 最终结果
centroid_x_sum = 0 # 质心 x 权重和
weight_sum = 0 # sum(ROI[:, 0:4])
# line_centroid_x = centroid_x_sum / weight_sum
# region of interest 感兴区域
# y0, y1, x0, x1, 权重
roi = [(300, 360, 0, 640, 0.1), # (y1:y1), (x1:x2), 权重
(360, 420, 0, 640, 0.3), # 每层高度 60
(420, 480, 0, 640, 0.6)] # 将图像分割成三个部分
n = 0 # 用于遍历ROI, 计数
5.2 切割可视化
for r in roi:
img_block = ada_dst[r[0]:r[1], r[2]:r[3]]
# Jupyter 显示图像代码
_2jpg = cv2.imencode(".jpg", img_block)[1].tobytes()
display(widgets.Image(value=_2jpg, format='jpg', width=320))
5.3 标记路线
发现轮廓,处理噪声
r in roi:
img_block = ada_dst[r[0]:r[1], r[2]:r[3]]
# 找出所有外轮廓
contours, _ = cv2.findContours(img_block, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# 该块未发现轮廓
if len(contours) == 0:
continue
# 对轮廓对象按面积进行降序, 面积最大的为序号为0
c_sorted = sorted(contours, key=lambda x:
math.fabs(
cv2.contourArea(x)
), reverse=True)
# 如果面积小于 20, 认为是噪声, 下一个块处理
if math.fabs(cv2.contourArea(c_sorted[0])) <= 20:
continue
# 否则识别到黑线, 这个程序目前只能处理黑线, 其他颜色的线不能用灰度色彩空间处理;
# 在原图像上绘制轮廓, offset 为 roi 左上角坐标, 用于偏移
cv2.drawContours(img_debug, c_sorted, 0, (0, 0, 255), 2, offset=(r[2], r[0]))
# 取轮廓最小外接矩形
rect = cv2.minAreaRect(c_sorted[0])
# 取最小外接矩形的四个顶点
box = np.int0(cv2.boxPoints(rect))
# 获取矩形的对角点, 再根据对角线求出中心点, 1,3 2,4 对角
pt1_x, pt1_y = box[0, 0], box[0, 1]
pt3_x, pt3_y = box[2, 0], box[2, 1]
center_x, center_y = (pt1_x + pt3_x) / 2, (pt1_y + pt3_y) / 2 # 中心点
cv2.circle(img_debug, (int(center_x), int(center_y) + r[0]), 3, (0, 0, 255), -1) # 画出中心点,图中红点, -1填充
# +r[0]是block ROI y起始位置;
# 按权重不同对上、中、下三个中心点进行求和
centroid_x_sum += center_x * r[4] # 执行了三次,中心坐标 × 权重
weight_sum += r[4] # 0.1 + 0.3 + 0.6 # 三个区域, 的权重之和, 越靠后, 权重越大
# 权重值为0未找到黑线, 不为0, 某个块识别到了黑线; 或者说面积大于 20(根据实际情况修改)
if weight_sum != 0:
# 求得加权平均中心点
line_centroid_x = int(centroid_x_sum / weight_sum) # 根据加权平均, 计算出的中心点,图中黄点
cv2.circle(img_debug, (line_centroid_x, 360), 7, (0, 255, 255), -1) # 画出中心, 这里y坐标是固定的; 巡线不关心y坐标;
else:
line_centroid_x = -1 # 错误值, 未寻找到黑线
# Jupyter 显示图像
plt.imshow(img_debug[:,:,::-1])
5.5 计算质心
上图中
- 红点 为 每一段黑线 的 中心点
- 黄点 为 加权平均后 的 中心点, 可认为是整条黑线的
质心
5.6 什么是加权平均?
例子:学校学期末成绩,期中考试占30%,期末考试占50%,作业占20%,假如某人期中考试得了84,期末92,作业分91。
如果是算数平均,那么就是
(84+92+91)/3=89;
加权后的,那么加权处理后就是
84x30%+92x50%+91x20%=89.4
在本例中,线段位置靠上距离机器人较远,权重值较小;反之,线段位置靠下距离机器人较近, 权重值较大。
距离与权重值之间呈反比例关系。
0x06 运动控制
注: 机器人获取的图像像素默认为(640, 480)。
根据当前线段质心位置,设置阈值对转向或前进控制。
设 x 为当前线段质心位置,t为转向阈值可调。320为图像横坐标中心位置,即常量x_center。那么,
当 abs(x - 320) > t 时机器人偏离黑线,需左、右转向。
x - 320 > t 时,机器人右转
x - 320 < -t 时,机器人左转
否则
机器人直线行走
参考:
评论(0)
您还未登录,请登录后发表或查看评论