文章目录[隐藏]
2019年的电赛终于结束了,今年的电赛题目真的是神仙出题,对于我这种传统自控选手充满了恶意。但无奈与自己技术有限,最后我们队还是选择了电磁炮这道题。然后,我们从七夕节开始爆肝了四天三夜……打了四天三夜的炮
由于题目比较长,在这里我不在正文部分把题目放出来了,真的不是我懒得排版有需要的可以直接下载PDF查看题目。不过想必有兴趣看这篇文章的都应该知道题目吧……
下载 “2019电赛H题-模拟电磁曲射炮” H题_模拟电磁曲射炮.pdf – 已下载389次 – 225.76 KB说实话,在看到这个题目的时候,我的内心是崩溃的……尼玛电磁炮是什么啊???这玩意真的是随随便便就能搭出来的吗???整个人瞬间就懵了,或者说整个实验室瞬间就懵了,吐槽声络绎不绝……好在队友给力,不一会就从网上翻出来了电磁炮的电路图(图我忘记存了,不过网上这种图一堆一堆的……)然后火速坐地铁去了器件店里买齐了所有的材料,大概是在我把图像识别写完之后,我们组的电磁炮居然就已经可以运行了,而且好像一下就能发射了???这么说是不是显得我特别菜的样子……不得不说,大佬牛逼!虽然我们搭的电路还很粗糙,但是花了几个小时就已经把我觉得最为棘手的电磁炮部分搞得差不多了,感觉我们这次从起跑线上就很顺利了。
闲话就说到这里,接下来我会通过列出一个个问题的方式并解决的方式来记录我的这次电赛经历。语文学渣,叫我写记叙文的话还是免了吧……
Prob.1 目标追踪
看到这个题目,我首先想到的就是目标的追踪问题,因为电赛前一阵子我刚好在学图像处理方面的知识。这次题目一出来,我瞬间就爽了,我手边刚好有一个不错的颜色识别的程序,只需略加改动,我们的目标追踪系统就完成了,而且连串口的配置我都在之前就弄好了。因为这次的图像识别还是比较简单的,所以我这边只贴出我使用的代码,如果想学习的话还是建议去看看opencv的教程。
图像识别代码基于树莓派上运行的opencv,使用树莓派摄像头模块:(具体的代码实现就不讲解啦……看了注释应该就懂了)
#encoding:utf-8
import cv2
import numpy as np
import serial
import imutils
import time
#由于脚本开机启动,等待20秒后再开始工作防止因系统还没完全初始化导致出错
time.sleep(20)
#初始化串口
ser=serial.Serial('/dev/ttyUSB0',115200,timeout=0.5)
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
position=-1
#设置镜头分辨率提高传输效率
cap.set(3,640/2)
cap.set(4,480/2)
while True:
e1 = cv2.getTickCount()
# 读取每一帧
ret, frame = cap.read()
# 重设图片尺寸以提高计算速度
frame = imutils.resize(frame, width=200)
cv2.imwrite("test.jpg",frame)
# 进行高斯模糊
blurred = cv2.GaussianBlur(frame, (11, 11), 0)
# 转换颜色空间到HSV
hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV)
# 定义红色无图的HSV阈值
lower_red = np.array([173, 20, 20])
upper_red = np.array([180, 255, 255])
# 对图片进行二值化处理
mask1 = cv2.inRange(hsv, lower_red, upper_red)
lower_red = np.array([0, 20, 20])
upper_red = np.array([12, 255, 255])
mask2 = cv2.inRange(hsv, lower_red, upper_red)
mask = cv2.bitwise_or(mask1,mask2)
# 腐蚀操作
mask = cv2.erode(mask, None, iterations=2)
# 膨胀操作,先腐蚀后膨胀以滤除噪声
mask = cv2.dilate(mask, None, iterations=2)
# cv2.imshow('mask', mask)
# 寻找图中轮廓
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
# 如果存在至少一个轮廓则进行如下操作
if len(cnts) > 0:
# 找到面积最大的轮廓
c = max(cnts, key=cv2.contourArea)
# 使用最小外接圆圈出面积最大的轮廓
((x, y), radius) = cv2.minEnclosingCircle(c)
# 计算轮廓的矩
M = cv2.moments(c)
# 计算轮廓的重心
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
sp=frame.shape
position=round((x-sp[1]/2),2)
print(round((x-sp[1]/2),2))
# 只处理尺寸足够大的轮廓
if radius > 5:
# 画出最小外接圆
cv2.circle(frame, (int(x), int(y)), int(radius), (0, 255, 255), 2)
# 画出重心
cv2.circle(frame, center, 5, (0, 0, 255), -1)
else:
position=999.9
print("Not Found.")
#读取串口数据
size = ser.inWaiting()
if size != 0:
response = ser.read(size)
#如果收到请求
if response == "request":
#发送数值
ser.write(str(position)+"\n")
print("position send")
#测试时用的按键退出和代码计时,实际并不需要
k = cv2.waitKey(5) & 0xFF
if k == 27:
break
e2 = cv2.getTickCount()
time = (e2 - e1)/ cv2.getTickFrequency()
print time
cap.release()
cv2.destroyAllWindows()
在做这个目标追踪的时候我们还遇到了一个小问题,那就是如何让这个python脚本开机后就自动运行。对于这个问题,我是采用注册系统服务的方式进行的,具体是编写了一个系统服务脚本,详细过程比较简单,网上都有教程,就不在此赘述了。
写完了树莓派上的部分,接下来就要写单片机上的配套功能了。为了不引起串口堵塞或其他一些不可预料的问题,树莓派和单片机之间的数据交互采用先请求后反馈的方式进行,即单片机先想树莓派发送请求信号,树莓派收到后再将当前值发送给单片机。接下来展示一下单片机配套的搜索函数:
void Search()
{
static double value=0;
static int counter=-1;
get_pos();//预定义函数,用于获取当前值
delay_ms(10);
UARTprintf("pos=%d\n",(int)postion);//debug用
UARTprintf("value=%d\n",(int)value);
//分阶段控制舵机位置数值
if(postion<4 && postion >-4)return;
if((int)postion==999)
{
if(counter>0)
{
counter--;
}
else
{
if(value>10)value=-10;
else if(value<-10)value=10;
else value=15;
counter=5;
}
}
else
{
value+=postion/30.0;
}
Servo_Control_1(value);
}
Prob.2 电磁炮的发射控制
这个问题在最开始的时候确实困扰了我一阵,不过后来想到使用继电器的话就可以完美地解决这个问题了,用继电器来控制充能和发射电路的通断,就可以实现用单片机的小信号控制整个电磁炮电路的功能了。不得不说,在这种小问题上,真的就是想不到的时候觉得很难很复杂,但是一旦想到了就会发现其实还是很简单的。
Prob.3 测距
测距是题目要求里比较重要的一个功能,本来我们已经准备好了超声波模块来应对这种测距类的题目了。但是在实际使用的时候,我们发现超声波完全没有办法稳定测出数米外的靶子,于是我们只能寻求其他的解决方案了,备选方案是激光测距。我们发现使用激光模块测距的话,其精度和稳定性都极高。毕竟这可是氪了二百多的!
Prob.4 云台的控制
这一块就比较简单了,我们选用了大功率舵机来控制云台,而大功率舵机的控制还是比较简单的,只需要输出指定频率的pwm波就可以使其偏转到指定的角度了,和我们这次的项目简直就是绝配。不过舵机本来就是个非常实用的模块啊……接下来简单地放一下控制云台的代码:其实这代码简单到我也不想放出来……
void Servo_Control_1(double angle)
{
const double zero=4.90,min=4.09;//设置零位
double angle_per_degree=(zero-min)/30;//设置控制量变化幅度
const int res=50;//设置限幅
if(angle<-res)
{
angle=-res;
}
else if(angle>res)
{
angle=res;
}
double duty=zero+angle_per_degree*angle;//计算需要的pwm波
PWMPulseWidthSet(PWM0_BASE, PWM_OUT_2,(int) PWMGenPeriodGet(PWM0_BASE, PWM_GEN_1)*(duty)/20.0);//输出
}
void Servo_Control_2(double angle)
{
const double zero=5.00,max=8.55;
double angle_per_degree=(max-zero)/90;
if(angle>45)
{
angle=45;
}
else if(angle<-45)
{
angle=-45;
}
double duty=zero-angle_per_degree*angle;
PWMPulseWidthSet(PWM0_BASE, PWM_OUT_3,(int) PWMGenPeriodGet(PWM0_BASE, PWM_GEN_1)*(duty)/20.0);
}
什么,这就结束了?作者有点懒得写了,以后看心情再补吧……(咕咕咕)
写在最后
电赛是一项非常考验选手问题解决能力的比赛,即使事前准备再充分,在比赛的那三天里仍然有很多出错和出问题的可能性,这是每个选手都需要面对的。当然,有大佬带除外……所以,这个比赛不仅仅是你学会了xxx去参加就一定能取得好成绩的。这些知识和技能储备仅仅是参加比赛的准入门槛,你还需要在平时进行大量的练习,在这些练习中获取大量解决问题的经验,最后才能获得一个自己满意的成绩。
P.S.这次参赛因为种种情况只拿到了省一,还是很不甘心的。本来以为自己有机会进入国赛的,看来运气也是很重要的吧……
发表评论