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