2019年广东省计算机检测比赛,PingpongCar

给2020参赛同学的项目说明

2019比赛用的代码全在 PythonCode/proj/robot2/ 目录下,有需要的话,2020比赛的同学可以参考一下。其他的代码大多是开发途中被放弃的,因此显得结构比较乱,可以不去理会。另有些当时查阅到的资料在去年学长的OneNote里,需要的同学可以联系他要pdf。

最开始我们的想法是识别和控制都在树莓派上跑,但是后来考虑到大多数成员对STM32更熟,而会Python的人很少,所以最后树莓派就只用来跑视觉,单片机将识别任务发送给树莓派,树莓派则将识别结果发送给单片机。这样树莓派工作起来就像个传感器一样,所以这个分支名为sensor_mode。

介绍 robot2

一个目录下的所有代码和资源可视为一个项目。

可以看到,仓库里还有一个robot目录,这是被放弃的初始的代码,那么,robot2显然就是在此基础上的新版的代码。项目 robot2 整体上由两大模块组成:视觉识别和网页显示。

视觉识别模块包含main.py,camera.py,communication.py 和 hmi.py。此外还有一个辅助选取阈值的代码,yuzhi.py。调试时先将乒乓球置于摄像头视野内,利用yuzhi.py找到合适的颜色阈值,然后修改main.py中相关变量的值。比赛时运行main.py。

网页显示模块主要为z_imshow_server.py(开头的z是为了排序),功能是把识别结果绘制在原始图片上,并通过网页查看。后来想要把这个模块做成上位机,所以增加了web_yuzhi.py,task_manager.py和几个 *.sh 脚本。但是做成上位机的尝试并不成功,一方面是开始时软件架构就没有考虑到这一点,技术也不是很扎实,导致最终代码的运行很不稳定;另一方面是队友对于新的功能不适应(貌似增加这些功能时,离比赛已不到一个星期)。旧版本中,颜色阈值是硬编码在main.py中的,修改阈值要有很多操作,比较耗时,而新版本中颜色阈值保存在配置文件里,可通过server修改,main.py检测到修改就立即更新,这样在网页端就可以选取阈值和更新阈值一步到位。当然,新版本屏蔽了旧版的操作。而我又没有及时说明新功能(我们有三支队伍),他们发现旧操作不起作用了,加上软件不稳定经常崩,感觉就像是bug。由于种种问题,网页显示模块的功能就仅仅是(超简单的)显示了。

代码注解

实现细节可以看代码的注释,这里对注释里没有的内容做些补充。

项目结构

实际上直到现在我对于代码之间应该有怎样的依赖关系也没有很好的经验,当时就更模糊了,所以该项目的结构并不是很成熟。基于模块化编程的思想,将功能相关的代码分别封装起来,如camera.py负责相机相关功能和而communication.py负责串口通信相关功能。视觉算法的代码似乎也应封装起来,但方便起见,就全部写在main.py里了。

camera.py

顾名思义,就是用来管理相机的模块。实际上OpenCV的VideoCapture()类已经提供了很好的封装,使用open(), read(), grab(), retrieve() 等函数就可以简单的获取图像,还可以设置曝光、白平衡等参数。但是考虑到开发过程中,经常更换摄像头型号,且不同型号的摄像头又对应不同的参数,为了隐藏这些复杂性,还是单独写了个模块。实际上这一工作是否有效还未得到验证,至少我感觉效果不大,一方面技术水平有限,另一方面整个开发过程中使用的基本都是同一款相机,没机会验证这一思想。因此,camera.py最重要的封装就是继承自VideoCapture的Camera类吧,当摄像头读不出相片的时候,可以自动尝试reconnect(),以及实现为一个生成器(实现了__iter__和__next__方法),读取图像的循环略显优雅(也略显难懂)。

communication.py

基于pyserial,封装了应用层的通信协议。应用层的数据为ASCII的 “命令 + 数据” 的格式,底层则基于能传输ASCII字符的通信协议,目前使用的是UART串口,以后也可以是SPI或I2C,只要提供同样的应用层接口即可。至于具体的通信协议的格式我忘记在哪里了,可能在master分支的doc目录下吧,日后找找。

hmi.py

在当前项目中,没有实现任何功能,只是从思想上来说,应该有这么一个模块。HMI(Human Machine Interface),人机交互接口。代码出错有编译器或解释器报错,从而方便调试,而机器人出错通常是发疯,让人摸不着头脑。因此该模块的本意是,在可能出错的时候(读不到照片、串口超时等),通过LED或屏幕等提示,从而方便定位错误。目前的设计在硬件上貌似确实有这样的LED(详询电控组同学),但是Python代码上全是空函数。(灵光一现:为什么不在网页上显示这些信息呢?)

main.py

重头戏来了,只要运行了main.py,机器人就可以工作了。限于当时的技术水平,有些地方写得不是那么讲究,一些用词也不够准确,请谨慎参考。

从功能上来说,main.py的任务就是识别目标,并将识别出的坐标发送给单片机;此外还要将识别结果绘制出来,以便能够在网页上显示。Quiet simple,right?

识别目标

关于识别目标的原理,可以找学长要2019赛后写的论文(目前知网上貌似还找不到),这里不再赘述。识别的步骤上,有两条路径:

颜色分割(二值化) => 通过轮廓的几何特征搜索目标;

颜色分割(二值化) => 利用上一次识别的坐标跟踪目标;

程序启动后,首先进入路径1,如果成功识别出目标则切换到路径2,直到目标丢失(raise TargetNoFound)。而识别目标则有乒乓球,投球点,篮筐。虽然对于每个目标,识别步骤的路径是一致的,但是颜色参数是不同的。对于这种2条路径 + 三个参数的情形,我使用了大量的 if else来实现功能,导致代码量非常庞大。或许这样写会好懂一些,但实际上可读性和可维护性是比较差的(难以应对更复杂的任务,或者后续的功能改动),这一点可以通过寻找合适的设计模式来解决(it's your problem now)。

程序封装

分别将识别代码和跟踪代码封装成了 BaseRecognizer 类和 xxxTracker 类。比赛中使用的跟踪算法是CamShift,可以看到,先定义了一个BaseTracker类,然后再定义一个继承自BaseTracker的类。实际上,之前还基于光流跟踪写了个OpticFlowTracker,但是因为效果不好,所以给删了。之所以先定义BaseTracker类,然后再继承它,是因为对于不同的xxxTracker,可能应当具有一些完全一样的method,而这些共享的method就很适合放在基类中被继承,至于不同xxxTracker独特的部分,再单独在xxxTracker类中定义。举个例子,猫和狗都是哺乳动物,所以它们都要吃奶,都有四条腿,会走会跑。但是猫会上树、爬墙、玩毛线,这是狗做不到的,而狗能看门,猫却不行。于是,当我们设计Mammal、Cat 和 Dog 类的时候,猫和狗共有的功能:吃奶、四条腿、会走会跑,适合写在Mammal类中,然后Cat 和 Dog 继承Mammal,猫特有的功能:上树、爬墙则应在 Cat 类中单独实现,狗特有的功能看门应在 Dog 类中单独实现。

对于识别代码来说,封装成 Recognizer 除了形式上和 xxxTracker 一致之外,没有任何定义为类的理由。这是不太规范的地方,但是我比较懒,就不展开了。

Shower

这个类用来将识别结果绘制在图片上。程序最核心的功能是识别,而绘制结果是为了观察。为了避免识别代码和观察代码之间相互干扰,就将绘制代价封装成了一个类。这样做的好处是,可很容易地实现使能(enable),即可以禁用/启用绘制功能(禁用绘制可以减少计算量)。另外还可以不需要大量的改动就将这些代码移出到单独的文件中。

State

主要是为了将一些影响程序运行的参数集中起来,而不是采用OOP的方法。所以自始至终都是直接用State类的(Python中,一切皆对象,所以类也是对象),而不是使用该类的实例。

网页显示

不多说了,上 w3school、菜鸟教程或者bilibili大学入门下前端和Flask应该就够了吧。唯一需要解释的就是,Linux中,/dev/shm 虚拟出来的存储空间,也就是说,使用上和硬盘一样,但实际的位置是在内存中的。这一特性好用,但也是技术水平不够的做法,后续可以尝试更高级(当然性能也更好的做法)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值