# 2021-DianSai-D **Repository Path**: LCW3366/DianSai-D ## Basic Information - **Project Name**: 2021-DianSai-D - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-01-21 - **Last Updated**: 2022-01-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 2021电赛D题方案 作品演示看[这里](https://www.bilibili.com/video/BV1K3411x7Ta) ## 文件夹构成 raspberry: 摄像节点A和B代码和配置文件,使用同一套代码,只有IP配置不同 ## 摄像节点端详解 摄像节点所做的事非常少,总共分为两件事 * 读取usb摄像头的图像并显示 * 监听8001端口,如果有设备连接上来,就开始进行图像传输 #### 依赖的库 python版本:python3.6及以上 只需要opencv和opencv_contrib #### send.py 该文件定义了一个Send类,基于TCP协议,监听本地端口,同时提供了传输图像的函数 主要特性如下 * 支持掉线重连,方便调试,不会出现在调试nano端图像处理代码时,每次启动代码还要重启节点的代码 * 将二维图像降维成一维并编码压缩,在nano端恢复 * 在传输图像的同时传输时间,因为网络延迟的原因,nano端接收到图像的时间其实是不准的, 通过在发送时将发送时间一并发送,可以保证网络出现延迟时nano端收到的时间是准确的 * 传输图像的可靠性由TCP协议保证 #### fix_distortion.py 该文件提供了摄像头畸变矫正功能,是我的队友张同学用matlab计算参数提供,据他说可以矫正畸变, 但是我的肉眼不能分辨添加之后是否有不同,是否有该文件对结果没有任何影响。 #### raspberry.py 树莓派端的主文件,实现了读取视频流和传输视频,需要指出以下几点 1. 创建的显示图像的窗口需要自适应大小,同时设置始终置顶 2. 读取图像和传输图像需要使用双线程以解决帧滞留问题(该问题导致去年省赛识别题只拿了省二,具体定义可以百度) 3. usb摄像头分辨率为480P,即640*480,使用千兆交换机传输帧数每秒可达25帧以上 #### 图像传输协议 * 首先传送16个字节,该字节表示该图像发送时的时间 * 其次再次传送16个字节,该字节表示图像数据的长度 * 压缩后的图像数据直接跟在后面 #### 节点IP配置 终端输入sudo vim /etc/network/interfaces,内容改为本仓库中raspberry/interfaces的内容 #### 开机自启动配置 在/home/pi/.config/autostart(如果没有该目录就创建它),将本仓库中raspberry/opencvtest.desktop复制进去, 该文件中Exec=/home/pi/opencvtest/opencvtest.sh表示需要执行的shell脚本的路径,编写一个shell脚本启动程序就可以了 ## Nano终端 Nano终端负责接收图像显示、激光笔识别和测量相关 #### 依赖的库 依赖的库比较少,就不提供requirements.txt了 python版本:3.6及以上 * numpy * opencv_python * opencv_contrib_python * imutils * pyserial #### 识别方法 激光笔为黑色激光笔,实际场景中在视野里没有比激光笔更大的黑色色块, 所以只需要先去除掉面积过大的黑色色块,再选取剩下的色块中面积最大的, 即可认为是激光笔。识别流程如下: 1. 为了程序阈值鲁棒性,采用hsv图像来进行颜色识别 2. 对hsv图像二值化,阈值通过hsv的色表查询 3. 对二值化后的图像寻找轮廓并按面积排序 4. 选取面积小于一定阈值且面积最大的色块,该色块就是激光笔 #### 测量方法 * 寻找到激光笔后,记录摆动周期,每一帧的时间通过图传协议接收得到, 同时记录激光笔再两路摄像头中横坐标最大摆动之差。经过计算, 平均摆动时间为2s-2.5s之间,所以测量10个周期去掉两个最大值和两个最小值取平均, 可以使结果更加精确。 * 首先计算角度,公式为tan(theta) = x1/x2,求一个反正切就可以得到角度, 科式力对与角度的影响再短时间内(基本为1分钟)摆动时影响不大, 注意松手时不要手抖。 * 计算完角度后,根据角度计算周期,如果角度小于5度,那么认为B节点测量是准确的, 如果角度大于85度,那么认为A节点测量是准确的,否则用A、B节点的平均值作为周期。 * 周期的公式为L=T^2\*g/(4\*pi^2) * 这里我们担心再0度和90度时角度会测量不准,所以加了一个小trick, 我们启动的按键有4个,其中3个是相同功能,另外一个按下后, 测量到的角度一定会小于5度或大于85度,不过实际测量时效果很好, 所以这个小trick并没有用到 #### read.py 图像传输client端,提供了读取视频并解码的功能。 #### search.py 提供了识别和测量T和delta_x的功能,因为两路摄像头其实是做相同功能, 所以封装成一个类,使用时只需要实例化两个对象,方便复用。 该类里再识别和计算中有很多trick,以下稍微简述,详情可以看代码: * 因为激光笔会有反光,所以我们没有直接使用hsv中标准黑色阈值,我们提高了明亮度 * 如果一个黑色色块在图像边缘,那么就忽略他 * 我们除了画边框之外,还画上了坐标,很多同学以为自己是功能做的不够多, 其实更多的是因为界面好不好看,你把结果大大的画在图像上, 肯定比用命令行print给评委老师印象好,你的界面上有一些随时变换的数值, 有一些花花绿绿的线条、方框,评委老师就会觉得你做的很好, 这样你从一开始就已经赢其他队伍太多了。 #### usart.py 封装了串口相关类,提供了包括和32单片机自检,接收开始测量命令, 返回测量结束指令,比较指令是否一致 #### some_image.py 创建空白图像,可以将测量后的结果画在这些空白图像上,比用print打印结果更好 #### algorithm.py 提供了计算线长和角度的函数。注意计算线长时, 得到的长度是激光笔几何中心所在点对应的线长,需要对结果减去一个常量 #### nano.py 程序主入口,具体工作流程如下: 1. 创建两个线程分别创建socket连接并读取图像 2. 主线程使用copy()方法获取图像拷贝,防止识别时图像对象改变 3. 上电后需要自检,具体方法是上位机向下位机发送自检命令,下位机返回一个自检命令表示自检通过 4. 识别时因为使用的是拷贝,所以在你下一次识别时, 不能确定接收线程是否已经接收到另一帧图像,所以需要进行一个判重操作, 否则会对一帧图像多次处理 5. 通过串口接收到测量指令后,会将图像传给A、B两个Search对象, 通过查询对象内的T和dealt_x是否为None,可以得到是否测量结束 6. 根据当前不同的状态,在空白图像里绘制出当前状态或测量结果 7. 画椭圆的代码太复杂,目前不太好整理,这里先不添加画椭圆的办法, 这里先开个坑,等到以后想到怎么简化代码后在添加