接着上一篇,进入while(1)循环
while(1)
{
int k=0;
cap>>frame; //存图像于frame
w.workthread[1]->red = digitalRead(27);
if(w.workthread[1]->close==1)
{
w.workthread[1]->closer = digitalRead(26);
}
imshow("this",frame);
cvtColor(frame, edges, CV_BGR2HSV);//彩色转换成灰度
split(edges,channels); //将图像按通道分离
判断语句中:
w.workthread[1]->close
为小车是否进入灭灯动作的标志位,等于0为进入灭灯动作,此时应该关闭车头的红外对管,不然红外对管会持续触发;
因此,只有在小车未进入灭灯动作的时候,才需要读取红外对管的数据,即:
w.workthread[1]->closer = digitalRead(26);
逻辑为:小车找到灯位置-->开启车头红外对管-->小车接近目标触发红外对管-->小车进入灭灯状态-->同时关闭红外对管-->灭灯完成-->继续开启红外对管
3.1、设置阈值并将图像二值化
for(int i = 0;i(i);
uchar*redData_1 = channels[2].ptr(i);
for(int j = 0;j179)
{
redData[j] = 255;
k+=1;
}else if(redData[j]w.m_dowm&&redData_1[j]>w.I)
{
redData[j] = 255;
k+=1;
}else
{
redData[j] = 0;
}
}
}
这里只设置H通道和V通道的阈值,在效果较好的情况下保证速度
3.2、开操作,闭操作
//morphologyEx(channels[0], channels[0], MORPH_OPEN, element);
//morphologyEx(channels[0], channels[0], MORPH_CLOSE, element);
3.3、寻找小灯的位置
cv::findContours(channels[0],contours,hierarchy,RETR_CCOMP,CHAIN_APPROX_SIMPLE);
double maxArea = 0;
vector maxContour;
int second = 0;
int max = 1;
for(size_t i = 0;i = maxArea)
{
max = i;
maxArea = area;
maxContour = contours[i];
}
}
Point2f vertex[4];
Point s1,l,r,u,d;
找出图像中的最大连通域的位置
3.4、得到目标的坐标,并用pid算法控制舵机转向
Point2f vertex[4];
Point s1,l,r,u,d;
if(k>0) //如果图像上有信号,k大于0
{
RotatedRect box = minAreaRect(Mat(contours[max]));
box.points(vertex); //得到矩形的坐标
s1.x = (vertex[0].x+vertex[2].x)/2.0; //分别得到矩形中心的x坐标与y坐标
s1.y = (vertex[0].y+vertex[2].y)/2.0;
l.x = s1.x-10;
l.y = s1.y;
r.x = s1.x+10;
r.y = s1.y;
u.x = s1.x;
u.y = s1.y-10;
d.x = s1.x;
d.y = s1.y+10;
line(channels[0],l,r,Scalar(100,0,0),2,LINE_AA);
line(channels[0],u,d,Scalar(100,0,0),2,LINE_AA); //以上为画一个十字表示中心坐标位置
imshow("largest region",channels[0]);
ThisError_x = s1.x - 160;
ThisError_Y = s1.y - 120;
if(LastError_x-ThisError_x>12) //防止噪点干扰的方法
{
ThisError_x=LastError_x-12;
}
else if(LastError_x-ThisError_x<-12)
{
ThisError_x=LastError_x+12;
}
pwm_x = ThisError_x*P + D*(ThisError_x-LastError_x); //pid算法
if(pwm_x>=4)
{
speed=pwm_x;
}else if(pwm_x<0)
{
speed=-pwm_x;
}
if(LastError_Y-ThisError_Y>12)
{
ThisError_Y=LastError_x-12;
}
else if(LastError_Y-ThisError_Y<-12)
{
ThisError_Y=LastError_Y+12;
}
pwm_y = ThisError_Y*P_Y + D_Y*(ThisError_Y-LastError_Y);
if(pwm_x<5&&pwm_x>-5&&back==1) //控制舵机转向
{
softPwmWrite(3,15+pwm_x);
}else if(pwm_x<=-5&&back==1)
{
softPwmWrite(3,10);
speed=5;
}else if(pwm_x>=5&&back==1)
{
softPwmWrite(3,20);
speed=5;
}
if((s1.y>=w.pwm_x_up&&w.workthread[1]->flag ==1))
{
w.workthread[1]->flag = 0;
w.workthread[1]->start(); //进入线程2
w.timethread[1]->start();
w.timethread[1]->timer=0;
//m_timer=0;
}else if(s1.y>=w.pwm_x_dowm&&s1.yflag ==1)
{
w.I=170-(s1.y-55)/4;
softPwmWrite(2,0);
softPwmWrite(4,15);
//softPwmWrite(1,25);
}
else if(s1.yflag ==1)
{
softPwmWrite(2,0);
softPwmWrite(4,28);
//softPwmWrite(1,25);
}
}
LastError_x = ThisError_x;
LastError_Y = ThisError_Y;
//qDebug()<<"pwm_x:"<
其中的以下代码:
if((s1.y>=w.pwm_x_up&&w.workthread[1]->flag ==1))
{
w.workthread[1]->flag = 0;
w.workthread[1]->start(); //进入线程2
w.timethread[1]->start();
w.timethread[1]->timer=0;
//m_timer=0;
}
有必要解释一下,当目标的y轴坐标大于一定值时,也就是说小车接近目标一定距离,并且此时小车不在预灭灯状态时,小车进入workthread线程,同时也开启timethread线程,先看workthread线程内部的代码:
void WorkThread::run()
{
softPwmWrite(2,30);
softPwmWrite(4,0); //以上两行表示刹车
delay(200);
while(flag==0) //刹车后进入预灭灯状态循环
{
softPwmWrite(2,0);
if(speed>=4) //speed为舵机的转动角度
{
softPwmWrite(4,11);
}else if(speed<4)
{
softPwmWrite(4,8);
}
//qDebug()<<"start!!";
if(closer==0) //如果closer=0,判断50ms后是否还为0,这是为了避免误判
{
delay(50);
if(closer==0) //进入真正灭灯状态,closer的值为车头红外对管的值
{
close=0;
m_timer=0;
qDebug()<<"stop!!";
softPwmWrite(2,30);
softPwmWrite(4,0);
softPwmWrite(1,5); //挡板展开
delay(100);
softPwmWrite(2,0);
softPwmWrite(4,0);
softPwmWrite(3,15); //11向左转,19向右转
back=0;
delay(1000);
softPwmWrite(1,22); //挡板合上
softPwmWrite(2,60); //后退
softPwmWrite(4,0);
delay(1000);
softPwmWrite(3,12);
softPwmWrite(2,30); //后退
softPwmWrite(4,0);
delay(400);
back=1;
flag =1; //flag等于1后,将会跳出预灭灯状态循环
close=1;
}
}else if(m_timer==1) //timethread的标志为1后同样会跳出循环
{
flag = 1;
}
}
}
基本思路为,小车刹车后 进入预灭灯状态
此时小车的车速根据舵机的值变化,也就是说舵机转角越大,车速相应越快,这是为了防止由于转角过大造成的卡死停车
如果红外对管触发,则进入实际灭灯状态,灭灯完成后退出预灭灯状态,退出线程;
如果进入预灭灯状态超过3秒。也就是timethread的标志位m_timer=1,小车认为之前的目标为误判目标(因为比赛场地的反光问题,小车在一定的角度下,有可能把地面的反射光线错误识别为目标)此时同样退出预灭灯状态,退出线程;
如果以上情况都没有发生,小车会持续在预灭灯状态,直到以上一种情况发生;
下面时timethread线程内部代码:
void TimeThread::run()
{
while(timer==0)
{
m_timer=0;
delay(1000);
delay(1000);
delay(1000);
m_timer=1;
timer=1;
}
}
3.5、如果这一帧图像没有找到目标位置,小车保持右转,在原地转圈
else
{
if(w.workthread[1]->flag ==1)digitalWrite(28,0); //0表示前进
w.Tento2(70); //马达速度
if(w.workthread[1]->flag ==1)
{
softPwmWrite(1,22);
softPwmWrite(2,0);
softPwmWrite(4,45);
softPwmWrite(3,20); //11向左转,19向右转
}
//softPwmWrite(2,0);
//softPwmWrite(4,50);
}
至此,while(1)循环内的代码已经完成,整车工程及源代码将在下一篇中分享,由于这个比赛小车是仓促制作,小车车模不大,在对抗赛中的第二轮被大车淘汰,代码也写的很匆忙,很渣渣,而且不应该用qt进行开发的,直接用python会省下很多时间,在io口的控制上也应该使用stm32等单片机才对,树莓派的io口做这类比赛太吃力了,总之写的不好,请见谅,谢谢观看!