【工程训练/水中机器人】水下管道智能巡检【视觉部分】总结

本文为第七届大学生工程训练综合能力竞赛中【水下管道智能巡检赛项】和第十四届国际水中机器人大赛【水下管道智能巡检组】视觉部分总结。


前言

我在队伍中主要负责机器人视觉功能的实现,所以在本篇参赛总结中只会涉及视觉功能实现的思路和过程。


提示:下文中所提出的实现方法不一定是最优解,如果有其他更好的建议和想法,我衷心欢迎大家与我交流,一起学习进步!

一、规则解读

完整规则太多了,这里仅引入一些与视觉部分相关的内容。

水下机器人能够对水下管道上的吸附物进行检测报警及移除清理等,竞赛过程中水下机器人必须全程自主运行。
赛场尺寸(长×宽×高)为 3000×2000×600(mm)长方形水池,示意图如下:
在这里插入图片描述
水下管道铺设在水池内,分浅水区、渐变区和深水区。管道上共设置若干(大于 5、小于 20)个吸附物,分布在管道各处。
熟悉规则后,总结视觉任务如下:

  • 管道巡线
  • 吸附物检测与识别

二、实现思路

1.管道巡线

硬件采用openmv,寻迹对象为水下白色管道,思路与我的另一篇博文:https://blog.csdn.net/qq_39784672/article/details/121239435?spm=1001.2014.3001.5501中寻迹思路相仿,这里就不赘述了。

2.吸附物检测与识别

吸附物在管道上布置方式如下图所示:
在这里插入图片描述
吸附物为实心黑色物体,其截面为正方形、圆形两种,边长或直径尺寸限制在 30~50mm 范围,厚度不大于 30mm。吸附物截面图如下:在这里插入图片描述

上面为第十四届国际水中机器人大赛的比赛规则,工训的规则和这个相差不大。工训中要求检测并识别出吸附物形状;而水中则只需检测出即可,算是降低了难度。

吸附物检测与识别我们采用了两种方案:

(1) openmv手工特征

我们尝试过许多特征,比如色块框像素占比、圆形和矩形查找等。

(2) k210目标检测模型

通过制作吸附物的数据集训练yolo目标检测模型,最终部署到k210上进行预测输出吸附物类型和位置,完成检测和识别任务。
相关实现方法参加我的另一篇博文:【实战】K210训练与部署YOLO目标检测模型

openmv代码

最终比赛代码如下:

import sensor, image, time, pyb
import math
from pyb import UART, Timer

# 返回参数初始化
tube_offset_angle = 0                               # 管道偏移角
tube_offset_distance = 0                            # 管道偏移距离
obstacle_shape = 0                                  # 障碍物形状,取值规则:0:没有障碍物;1:圆形障碍物;2:矩形障碍物
# 调试参数
tube_threshold = (30, 100, -128, 127, -128, 127)     # 管道LAB阈值
obstacle_threshold = (0, 18, -7, 26, -3, 12)    # 障碍物LAB阈值
timer_freg = 25                                     # 定时器触发间隔(Hz)

# 定时器回调函数
def timeFunc(timer):
    time_flag = 1

# 判断障碍块位置
# return: 0:left 1:center 2:right 3:error
def getPosition(o_b, t_b):
    # 计算与管道中心的偏移
    x_offset = (o_b[0] + o_b[2]/2) - (t_b[0] + t_b[2]/2)
#    print(x_offset)
    if abs(x_offset) < ((75 / scale)/5):
        p_flag = 1
    elif x_offset < 0:
        p_flag = 0
    elif x_offset > 0:
        p_flag = 2
    else:
        p_flag = 3
#    print(p_flag)
    return p_flag

def find_max(blobs):
    max_size=0
    for blob in blobs:
        # if blob.pixels()>=250 and blob.pixels()<=7000:
            if blob.pixels() > max_size:
                max_blob=blob
                max_size = blob.pixels()
                return max_blob

sensor.reset()                          # 初始化摄像头
sensor.set_pixformat(sensor.RGB565)     # 格式为 RGB565
sensor.set_framesize(sensor.QQVGA)      # 使用 QQVGA 速度快一些,160*120
sensor.skip_frames(n = 800)             # 跳过800s,使新设置生效,并自动调节白平衡
sensor.set_auto_gain(False)             # 关闭自动自动增益
sensor.set_auto_whitebal(False)
uart = UART(3, 115200)                  # 初始化串口通信参数
clock = time.clock()                    # 追踪帧率
time_flag = 0                           # 定时标志位
timer = Timer(2)                        # 使用定时器2创建一个定时器对象
timer.init(freq=timer_freg)             # 设置触发间隔
timer.callback(timeFunc)                # 定义定时器回调函数
scale = 0                               # 图像比例尺(mm/pixel)

while (True):
    clock.tick()
    img = sensor.snapshot()
    # 识别管道
    tube_blobs = img.find_blobs([tube_threshold], pixels_threshold = 2000)
    if tube_blobs:
        tube_flag = 1
        t_b = find_max(tube_blobs)
        tube_offset_angle = t_b[7]
        tube_offset_distance = t_b[5]
        scale = 75 / t_b[2]
        # print(scale)
        img.draw_rectangle(t_b[0:4])
        # 绘制管道方向线
		# img.draw_line((t_b[5], t_b[6], t_b[5] + int(50 * math.cos(t_b[7])), t_b[6] + int(50 * math.sin(t_b[7]))),color = (255,255,255))
        # 识别障碍物
        obstacle_blobs = img.find_blobs([obstacle_threshold], roi = (t_b[0:4]), pixels_threshold = 100)
        if obstacle_blobs:
            o_b = find_max(obstacle_blobs)
            img.draw_rectangle(o_b[0:4])
            if abs((o_b[1] + o_b[3]/2)-60) < 30*scale:
                # 判断位置
				# img.draw_line(int(o_b[0] + o_b[2]/2),0,int(o_b[0] + o_b[2]/2),120,color = (255,0,0))
				# img.draw_line(int(t_b[0] + t_b[2]/2),0,int(t_b[0] + t_b[2]/2),120,color = (255,0,0))
                p = getPosition(o_b, t_b)
                # 障碍块在中间
                if p == 1:
                    circles = img.find_circles(roi = t_b.rect(),threshold = 3000, x_margin = 10, y_margin = 10, r_margin = 10,
                    r_min = int(10*scale), r_max = int(30*scale))
                    if circles:
                        for c in circles:
							# img.draw_circle(c.x(),c.y(),c.r())
                            obstacle_shape = 1
                    else:
                        obstacle_shape = 2
                # 障碍块在左侧或者右侧,判断方法相同
                elif p == 0 or p == 2:
                    if o_b[3] > 35*scale:
						# print(o_b[3] - 35*scale)
                        obstacle_shape = 2
                    else:
						# print(o_b.density())
                        if o_b.density() < 0.85:
                            obstacle_shape = 1
                        else:
                            obstacle_shape = 2
                # 出错
                else:
                    pass
            else:
                obstacle_shape = 0
        else:
            obstacle_shape = 0
    else:
        tube_flag = 0
        tube_offset_distance = 0
        tube_offset_angle = 0

    # 串口通信
    if time_flag == 0:
        uart.write('D%03dA%03dS%01dE' % (tube_offset_distance, tube_offset_angle * 53, obstacle_shape))
        print('D%03dA%03dS%01dE' % (tube_offset_distance, tube_offset_angle * 53, obstacle_shape))
        time_flag = 0
    # 打印帧率
    # print(clock.fps())

方案总结和分析

上面两种方法中,在工训中使用的是openmv方案。
在这里插入图片描述
Openmv方案对置于管道正上方的吸附物识别正确率较高,一旦吸附物位于管道两侧误识别概率较高,且随着距正中线愈远,概率愈高。这是因为在图像中侧置的黑色吸附圆柱和立方体在低像素和纹理少的情况下根本无法分辨(连人眼都无能为力)。
K210方案检测和识别的效果都很好。但是需要采集大量水下图片并标注,耗时长。(这也是我们为什么采用openmv的原因,时间不够了。)


总结

这两个比赛中,特别是这个水下巡检赛项,千万要读懂规则,制定合理的拿分策略。我们队伍就是被规则坑了,花了过多的时间追求跑完全程的速度,而忽略了识别和检测的重要性(分值占比),导致赛前忙手忙脚。最后,便是电控和视觉得多联调!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐