robogame技术文档

视觉和控制

author: guizhiyu@mail.ustc.edu.cn

目录

  • 视觉定位

    • 综述

    • 选购相机

    • 读取相机

    • 标定相机

    • 标定场地

    • aruco识别

    • 定位算法

    • 识别矿石颜色

  • 控制

    • 整体架构
    • 旋转
    • 平移
    • 速度解算

视觉定位

综述

​ 总的来说,如果你抛弃了巡线,而是完全依靠视觉完成车辆的定位,你需要做到的事是在场边架个相机,用电脑识别并计算车辆的位置,实现闭环控制。场边视觉平台和PC上位机是被允许的,如果你硬要做SLAM当然也好。

​ 请注意,视觉定位的精度和速度完全决定了你能否完成比赛。请在一审前证明你的视觉方案能满足要求,而不是用开环跑到三审才发现完全行不通。

​ 定位就是建立一个从图像平面内每一个二维点到场地上每一个三维点的一一映射。当然,既然组委会没有要求车飞起来,其实只是从二维点到二维点的映射(场地表面即可,即使场地有高低也是一样的)。如果真的需要三维定位,请使用双目视觉。

​ 反过来,从三维点到图像平面内二维点的映射被称作投影。利用齐次坐标,投影变换可以用 q = M T R Q q=MTRQ q=MTRQ 表示,其中 q q q 为图像平面内的坐标, M M M 为相机的内部参数, T T T 为相机坐标系在世界坐标系中的平移矢量, R R R 为相机坐标系在世界坐标系中的旋转矢量, Q Q Q 为三维点在世界坐标系内的齐次坐标(4*1的)。

​ 在最后我会通过投影实现定位。

​ 代码已开源 https://gitee.com/guizhiyu/cv

选购相机

​ 请在淘宝搜索工业相机,选择可手动调焦的型号,有以下值得注意的参数:

  1. 视场角(fov),fov决定了你的相机要放在场边多远的地方。
  2. 景深,景深越大的相机可以在越大的范围能保持成像清晰。
  3. 帧率,帧率限制了你输出的频率,60帧以上是理想的帧率。你可以要求客服加钱换个更好的主控以提高帧率。
  4. 延迟,100ms以下的延迟属于正常情况。相机的延迟是控制延迟的主要部分,对控制精度和速度影响非常大。
  5. 分辨率,分辨率决定了定位误差的下限。请综合考虑处理平台的算力、相机价格再选购。1920*1080(200万像素)是我们本次使用的相机。
  6. 畸变参数,一个畸变参数很小甚至无畸变的相机能给你省很多事

​ 你需要一个三脚架来架设相机。直观来看,三脚架越高,相机光轴与场地平面法线的夹角越小,定位精度越高。但是过高的架子在顶部的振动难以停止,请咨询机械。

读取相机

​ 一般使用opencv-python读取和处理相机的输出。(当然如果你要用cpp也没区别,速度也不会变快)请自行了解python,opencv,numpy,multiprocessing, thread的相关语法和基本操作,这里只给出基本和进阶的实现

基本实现:

cap = cv2.VideoCapture(0)# 这个id取决于你把相机插在电脑上的哪个usb口
cap.set(cv2.CAP_PROP_FRAME_HEIGHT,1080)# 分辨率
cap.set(cv2.CAP_PROP_FRAME_WIDTH,1920)
cap.set(cv2.CAP_PROP_FPS, 60)# 帧率,这三行参数必须设置,否则会按720p 30fps读取
while cap.isOpened():
    frame=cap.read()# 读取
    # do something

这个实现已经差不多很行了,主要问题在于cap.read()是阻塞式的,对于一个60帧的相机它需要花费33ms,如果你将后续的处理优化到30ms左右,输出位置的频率也只能达到30fps。优化点在于使用多线程技术,将读取和计算同步执行。

进阶实现:

class CameraThread(Thread):# 实现来自网络
    def __init__(self, kill_event, src = 0, width = 1920, height = 1080):
        self.kill_event = kill_event
        
        self.stream = cv2.VideoCapture(src)
        self.stream.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
        self.stream.set(cv2.CAP_PROP_FRAME_WIDTH, width)
        self.stream.set(cv2.CAP_PROP_FPS, 60)
        (self.grabbed, self.frame) = self.stream.read()
        self.read_lock = Lock()

        Thread.__init__(self, args = kill_event)

    def update(self):
        (grabbed, frame) = self.stream.read()
        self.read_lock.acquire()
        self.grabbed, self.frame = grabbed, frame
        self.read_lock.release()

    def read(self):
        self.read_lock.acquire()
        frame = self.frame.copy()
        self.read_lock.release()
        return frame

    def run(self):
        while not self.kill_event.is_set():
            self.update()
kill_event = Event()
cam = CameraThread(kill_event)
cam.start()
while True:
    frame=cam.read()
    # do something

标定相机

​ 你需要通过相机标定获取相机的内参矩阵 M M M 和畸变参数。注意,内参矩阵和相机的焦距(从小孔成像模型来看是焦距,其实是镜头到传感器的距离,应该算像距?)有关,所以测完之后就不要拧镜头了。(不过就算拧了定位结果也不会变,随便吧)

​ 请自行百度opencv相机标定。注意,组委会有价值300rmb的棋盘格标定板,请充分利用。

标定场地

​ 标定场地是为了获得 R R R T T T ,即相机相对于场地坐标系的位置。注意,你需要在1分钟准备时间内完成场地标定。你应当知道一些点在场地坐标系里的位置,又知道这些点在图像坐标系里的位置,就可以通过opencv的solvePNP函数计算 R R R T T T,具体用法请参考官网。

​ 问题在于如何又快又准地找到这些点在图像坐标系里的位置。比较平凡的方法是在图形化界面上手动点,这样精度很低,速度也很慢,所以能使用的点也很少,又降低了 R T RT RT 计算的精度。

​ 合理的做法是使用图像配准。我使用了superglue,你也可以试试LoFTR。具体的做法是我挑选了一些(30+)特征足够明显的点,保证它们可以被superpoint识别为特征点,然后拍摄一幅场地的图像 A A A,手工标出 A A A 中每个选中的特征点的坐标。在使用图像 B B B 场地标定时,superglue可以使用深度学习计算出 A A A 中每个特征点投影到 B B B 中时的位置。所以你的图像处理平台既要有很好的cpu也要有很好的gpu。

​ 当然,标出每个选中的特征点的坐标也很累。我写了一个程序方便你标位置,不过里面是用opencv的角点检测实现的,你最好换成superglue输出的特征点。

aruco识别

​ 暂时抛开定位的问题,我们先来解决一个更急迫的问题:如何从图像中识别出车辆?

​ 你当然有一些炫酷的方法,比如OnePose by ZJU(这个方法把定位也一并解决了,很强大)。不过我们还是考虑一些传统而稳定的方法,即aruco。你可以打印一个巨大的aruco图标(用雪弗板打,周围围一圈白的,如果你有不会反光的材料就更好了)盖在车上,然后使用cv2.aruco.detectMarkers检测aruco图标的四角。

​ 这套方法在大部分情况下都很好用,但是场地光照情况的变化可能会让你前功尽弃,比如过强的光照导致反光增强而识别失败。所以你需要调整参数并对图像进行预处理。

​ 一个相当可靠的预处理是将图像按亮度的阈值二值化,这样可以去除雪弗板上反光的影响。当然,这个阈值应当根据场地的光照调整。

cv2.aruco.DetectorParameters_create()自动生成的参数也需要调整。

arucoDict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_4X4_50)
arucoParams = cv2.aruco.DetectorParameters_create()
arucoParams.adaptiveThreshConstant=7
arucoParams.adaptiveThreshWinSizeStep=5
# arucoParams.adaptiveThreshWinSizeMax=40
# arucoParams.aprilTagMaxNmaxima=40
# arucoParams.aprilTagCriticalRad=0
# arucoParams.aprilTagMinClusterPixels=0
arucoParams.maxErroneousBitsInBorderRate=0.4
# arucoParams.errorCorrectionRate=0.8
arucoParams.perspectiveRemoveIgnoredMarginPerCell=0.3
arucoParams.polygonalApproxAccuracyRate=0.01
arucoParams.maxMarkerPerimeterRate =4
arucoParams.minCornerDistanceRate=0
arucoParams.minDistanceToBorder=0
arucoParams.minMarkerDistanceRate=0.005 #重要
arucoParams.aprilTagMaxLineFitMse=0.00

注意:如果你将aruco图标放在雪弗板的正中心,请将minMarkerDistanceRate 调整至0.005或更小,并在输出的四边形中取最大的一个,详见github。这是opencv的一个bug,即它在判断每个四边形框是否是合法的aruco前,会将中心过于靠近的四边形过滤掉。

定位算法

​ 铺垫完了,现在唯一的问题就是如何快速且准确的定位。

​ 对于投影,我们有cv2.projectPoints(),所以我们其实只需要对于场地上的每个点,计算其在图像上的投影,定位时通过查表,查到投影对应的三维点即可。结束了。

​ 当然对于如何在一分钟准备时间内建完这张表还需要一些优化,比如多进程、使用cython、使用gpu加速。

识别矿石颜色

​ 既然矿物的位置是已知的,你又知道相机的内参和外参,通过投影你当然知道矿石在图像上的坐标及其颜色。

控制

整体架构

​ 主体gui架构在这里,与控制相关的代码在这里

​ 主题部分我使用了pyqt进行人机交互,包括显示相机实时图像,启动车辆等。

​ 点击启动后会启动控制车辆运动的子进程,车辆的位置数据通过multiprocessing的Queue传给子进程。这个Queue的最大大小只有1,所以可以保证收到的是最新的数据。multiprocessing的Array和Value可能会出一些神必问题所以这样写。

旋转

​ 把旋转的角速度传给车辆即可,轮子怎么动是电控的问题。考虑到相机+计算+无线通信的延迟可以达到200ms,应当让角速度在快转到时减小已提高精度。出于方便我直接写了分段函数,理论上应该做个PID。

平移

​ 每个时刻都令轮子朝向目标点即可,只需要把轮子角度和速度传给车辆。同理,应当在快到达时减速。

速度解算

​ 由于安装精度的误差,平移的时候车体会发生相当大的自转,每次停下来纠正相当花时间。使用速度解算可以令车辆边平移边自转,既可以保持车辆平移时角度不变,又可以节省停车转向的时间。确定好平移速度方向,自转角速度后把所需参数传给车辆即可。同理,你应当写个PID之类的,不过分段函数也够用.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值