树莓派智能车摄像头循迹

实验设备

树莓派3B,电机电路控制板,红外传感器,超声波传感器,蜂鸣器,3.7V电池。

实验目的

实现小车自动循迹,道路是两条黑线之间,与循单线不同。

实现思路

端口设置

按照实体小车进行连接

PWM电机控制

用占空比来控制小车速度。

1
2
3
4
5
6
7
8
9
10
11

def turn_up(speed,t_time): #定义两个参数,一个是速度,一个是时间
L_Motor.ChangeDutyCycle(speed) #设置占空比,控制速度,speed的取值范围0-35,原理是控制电源通断的时间占比
GPIO.output(IN1,GPIO.HIGH)
GPIO.output(IN2,GPIO.LOW)

R_Motor.ChangeDutyCycle(speed)
GPIO.output(IN3,GPIO.HIGH)
GPIO.output(IN4,GPIO.LOW)
time.sleep(t_time)

超声波避障

用两个GPIO端口分别接受和发送。

1
2
3
4
5
6
7
8
9
10
11
12
13
#超声波测距函数
def get_distance():
GPIO.output(Trig,GPIO.HIGH) #给Trig发送高电平,发出触发信号
time.sleep(0.00015) #需要至少10us的高电平信号,触发Trig测距
GPIO.output(Trig,GPIO.LOW)
while GPIO.input(Echo)!=GPIO.HIGH: #等待接收高电平
pass
t1=time.time() #记录信号发出的时间
while GPIO.input(Echo)==GPIO.HIGH: #接收端还没接收到信号变成低电平就循环等待(等高电平结束)
pass
t2=time.time() #记录接收到反馈信号的时间
distance=(t2-t1)*340*100/2 #计算距离,单位换成cm
return distance

图像循迹

  • 预处理:灰度化+低通滤波+0-1二值化+两轮膨胀,效果如下三条黑线为检测算法取的三条线标。效果如下:

  • 双线程分别处理图像和电机驱动,通过全局变量进行通信。

线程一 ,计算道路中心点:分别在灰度图像中取近距离,中距离,远距离三条水平线,用np.where求出每一条线中黑色像素的索引。假设只能看到赛道的一条边,按边界补齐另一条赛道并求平均值。求取中心点用了两种方案,各有所长:
方案一:用类似PD思路,将近距离赛道作为基准,将中距离和远距离做差并加权加到近距离赛道中心点上
方案二:三条线的中心点加权平均。
线程二 ,驱动电机运转:根据线程一维护的全局变量中心点来进行电机驱动。

  • 应急处理

应急处理一:底线倒车。此方法需要和赛道适配,在赛道间距较宽时可以使用,如果赛道间距过窄,需要调低摄像头位置。具体操作为,在视频图像的底线中间附近取像素点,判断其中是否存在黑色道路边界,如果存在边界,则进行0.1秒的倒车操作。

应急处理二:红外传感。用图像处理存在冲出赛道的可能,加上红外传感器,在赛车即将冲出赛道时候进行急转弯。

猫脸识别

使用Haar基于统计的分类器,Haar分类器=Haar-like特征+AdaBoost算法+级联+积分图快速计算。
Haar分类器算法的要点如下:
① 使用Haar-like特征做检测。
② 使用积分图(Integral Image)对Haar-like特征求值进行加速。
③ 使用AdaBoost算法训练区分人脸和非人脸的强分类器。
④ 使用筛选式级联把强分类器级联到一起,提高准确率。

结论及感悟

在本次智能车实验中,我充分学习了树莓派的结构以及使用方法,熟悉了一些树莓派使用的小技巧。同时在我负责的图像处理方面,将上学期学习的数字图像处理课程的一些方法用到实处。在循迹的过程中有很多小技巧,比如倒车,虽然冲出赛道并不是很好检测,但是可以投机取巧的计算最下面一行的黑像素,当然这种方法只是用于路比较宽的赛道,窄的赛道还需另寻他法。使用一些“小聪明”可以让我们非常好的完成任务,体现了矛盾普遍性和特殊性的关系,我们不断的学习和做实验就是为了在特殊性中总结普遍性,从个例中提取共性。只有经过不断的实践,才可以总结出更多的经验学会更多的知识。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
#coding=utf-8
import RPi.GPIO as GPIO
import time
from tkinter import *
from PIL import Image,ImageTk
import tkinter.filedialog
import cv2
import threading
import numpy as np
#定义电机模块的GPIO口

Path_Dect_px=320

#---------------------------------
PIN_NO =29 #GPIO蜂鸣器
PWMA=11 #调速端A(左)
IN1=12
IN2=16
PWMB=13 #调速端B(右)
IN3=18
IN4=22
#定义超声波模块的GPIO口
Trig=38 #发射端
Echo=37 #接收端
#定义红外避障传感器GPIO口
L_Senso=36
R_Senso=35
#定义红外循迹传感器GPIO口
LSenso=40
RSenso=33
def init():
#设置解除警告
GPIO.setwarnings(False)
#设置引脚模式为物理模式
GPIO.setmode(GPIO.BOARD)
#对四个电机进行初始化,将引脚设置为输出
GPIO.setup(IN1,GPIO.OUT)
GPIO.setup(IN2,GPIO.OUT)
GPIO.setup(IN3,GPIO.OUT)
GPIO.setup(IN4,GPIO.OUT)
#调速端的初始化
GPIO.setup(PWMA,GPIO.OUT)
GPIO.setup(PWMB,GPIO.OUT)
#超声波传感器引脚初始化
GPIO.setup(Trig,GPIO.OUT) #将发射端引脚设置为输出
GPIO.setup(Echo,GPIO.IN) #将接收端引脚设置为输入
#红外避障传感器引脚初始化,设置为输入,接受红外信号
GPIO.setup(L_Senso,GPIO.IN)
GPIO.setup(R_Senso,GPIO.IN)
#红外循迹传感器引脚初始化,设置为输入,接受红外信号
GPIO.setup(LSenso,GPIO.IN)
GPIO.setup(RSenso,GPIO.IN)
#蜂鸣器初始化
GPIO.setup(PIN_NO, GPIO.OUT)
GPIO.output(PIN_NO, GPIO.LOW)



#根据上面的控制原理,我们通过控制每个引脚的电平信号,来控制小车的转向

#蜂鸣器函数
#哔1次,时长作为参数传递

def beep(seconds):

GPIO.output(PIN_NO, GPIO.HIGH)

time.sleep(seconds)

GPIO.output(PIN_NO, GPIO.LOW)

#哔N次,时长、间隔时长、重复次数作为参数传递

def beepAction(secs, sleepsecs, times):

for i in range(times):

beep(secs)

time.sleep(sleepsecs)

#beepAction(0.02,0.02,30)
#让小车前进
def turn_up(speed,t_time): #定义两个参数,一个是速度,一个是时间
L_Motor.ChangeDutyCycle(speed) #设置占空比,控制速度,speed的取值范围0-35,原理是控制电源通断的时间占比
GPIO.output(IN1,GPIO.HIGH)
GPIO.output(IN2,GPIO.LOW)

R_Motor.ChangeDutyCycle(speed)
GPIO.output(IN3,GPIO.HIGH)
GPIO.output(IN4,GPIO.LOW)
time.sleep(t_time)

#让小车后退
def turn_back(speed,t_time):
L_Motor.ChangeDutyCycle(speed)
GPIO.output(IN1,GPIO.LOW)
GPIO.output(IN2,GPIO.HIGH)

R_Motor.ChangeDutyCycle(speed)
GPIO.output(IN3,GPIO.LOW)
GPIO.output(IN4,GPIO.HIGH)
time.sleep(t_time)

#让小车向左转
def turn_left(speed,t_time):
L_Motor.ChangeDutyCycle(speed-8)
GPIO.output(IN1,GPIO.LOW)
GPIO.output(IN2,GPIO.HIGH)

R_Motor.ChangeDutyCycle(speed)
GPIO.output(IN3,GPIO.HIGH)
GPIO.output(IN4,GPIO.LOW)
time.sleep(t_time)

#让小车向右转
def turn_right(speed,t_time):
L_Motor.ChangeDutyCycle(speed)
GPIO.output(IN1,GPIO.HIGH)
GPIO.output(IN2,GPIO.LOW)

R_Motor.ChangeDutyCycle(speed-8)
GPIO.output(IN3,GPIO.LOW)
GPIO.output(IN4,GPIO.HIGH)
time.sleep(t_time)

#让小车停止
def car_stop():
L_Motor.ChangeDutyCycle(0)
GPIO.output(IN1,False)
GPIO.output(IN2,False)

L_Motor.ChangeDutyCycle(0)
GPIO.output(IN3,False)
GPIO.output(IN4,False)
#超声波测距函数
def get_distance():
GPIO.output(Trig,GPIO.HIGH) #给Trig发送高电平,发出触发信号
time.sleep(0.00015) #需要至少10us的高电平信号,触发Trig测距
GPIO.output(Trig,GPIO.LOW)
while GPIO.input(Echo)!=GPIO.HIGH: #等待接收高电平
pass
t1=time.time() #记录信号发出的时间
while GPIO.input(Echo)==GPIO.HIGH: #接收端还没接收到信号变成低电平就循环等待(等高电平结束)
pass
t2=time.time() #记录接收到反馈信号的时间
distance=(t2-t1)*340*100/2 #计算距离,单位换成cm
return distance
#避障功能函数(超声波避障结合红外避障)
def bizhang():

safe_dis=8 #设置一个安全距离
while True:
barrier_dis=get_distance() #获取当前障碍物的距离
#当测得前方障碍物距离小于安全距离时,先让小车停止
while (barrier_dis < safe_dis) == True:
print (barrier_dis,'cm')
L_S=GPIO.input(L_Senso)
R_S=GPIO.input(R_Senso) #接受红外避障传感器的信号
#如果红外传感器检测到左边有障碍物,右边没有,小车向右转
if L_S == False and R_S == True:

print ("左有障碍物先后退再右转")
turn_back(35,1)
turn_right(50,0.5)
#如果红外传感器检测到右边有障碍物,左边没有,小车向左转
elif (L_S == True and R_S == False):
print ("右有障碍物先后退再左转")
turn_back(35,1)
turn_left(50,0.5)
#如果红外传感器检测到左右两边都有障碍物,小车后退
elif L_S ==False and R_S == False:
print ("两边都有障碍物后退")
turn_back(35,1)
#再次接收红外信号(说明刚才的路线已经不能再走,退到一定程度,小车就得左转或者右转,提前寻找新的路线)
L_S=GPIO.input(L_Senso)
R_S=GPIO.input(R_Senso)
#退到左前和右前都没有障碍物的位置
if L_S == True and R_S == True:
print ("右转")
turn_right(50,0.5)
#退到了右前没有障碍物的位置
elif L_S == False and R_S == True:
print ("右转")
turn_right(50,0.5)
#退到了左前没有障碍物的位置
elif L_S == True and R_S == False:
print ("左转")
turn_left(50,0.5)
#还没有退出刚才的死路,继续后退

elif L_S == False and R_S == False:
print ("后退")
turn_back(35,1)
#如果红外传感器检测到两边都没有障碍物,此时让小车后退一点,然后向右转
elif L_S == True and R_S == True:

print ("两边都没有障碍物,后退再右转")
turn_back(35,1)
turn_right(50,0.5)

barrier_dis=get_distance()

while (barrier_dis < safe_dis) == False:
print (barrier_dis,'cm')

#小车在安全区里内,但是由于超声波传感器无法检测到除了正前方其他方向的障碍物,所以在此接收红外信号,通过左前和右前有没有障碍物,来判断小车该怎样运行
L_S=GPIO.input(L_Senso)
R_S=GPIO.input(R_Senso) #接受红外避障传感器的信号
#在安全距离内同时红外传感器检测到左前和有前方没有障碍物,小车前进
if L_S == True and R_S == True:
print ("前方3cm内没有障碍物,且左前和右前方没有障碍物,前进")
turn_up(35,1)
#在安全距离内,但左前有障碍物,让小车后退,再右转
elif L_S == False and R_S == True:
print ("前方3cm内没有障碍物,左前有障碍物,右前方没有障碍物,后退右转")
turn_back(35,1)
turn_right(50,0.5)
#在安全距离内,但右前有障碍物,让小车后退,再左转
elif L_S == True and R_S == False:
print ("前方3cm内没有障碍物,右前有障碍物,左前方没有障碍物,后退右转")
turn_back(35,1)
turn_left(50,0.5)
#在安全距离内,但左前,右前都有障碍物,让小车后退
elif L_S == False and R_S == False:
print ("前方3cm内没有障碍物,左前,右前方都有障碍物,后退")
turn_back(35,1)
#再次接收红外信号(说明刚才的路线已经不能再走,退到一定程度,小车就得左转或者右转,提前寻找新的路线)
L_S=GPIO.input(L_Senso)
R_S=GPIO.input(R_Senso)
#退到左前和右前都没有障碍物的位置
if L_S == True and R_S == True:
print ("右转")
turn_right(50,0.5)
#退到了右前没有障碍物的位置
elif L_S == False and R_S == True:
print ("右转")
turn_right(50,0.5)
#退到了左前没有障碍物的位置

elif L_S == True and R_S == False:
print ("左转")
turn_left(50,0.5)
#还没有退出刚才的死路,继续后退
elif L_S == False and R_S == False:
print ("后退")
turn_back(35,1)

barrier_dis=get_distance()

#红外循迹函数
def track():

while True:
#接收两个红外传感器的信号
LS=GPIO.input(LSenso)
RS=GPIO.input(RSenso)
#左右两个传感器都检测到黑色,小车在赛道上,前进
if LS==True and RS==True:
print ("前进")
turn_up(35,0.2)
#左边的传感器没检测到黑色,说明小车车身偏离赛道靠左,右转将小车车身向右调整
elif LS==False and RS==True:
print ("右转")
turn_right(50,0.2)
#右边的传感器没检测到黑色,说明小车车身偏离赛道靠右,左转将小车车身向左调整
elif LS==True and RS==False:
print ("左转")
turn_left(50,0.2)
#两个传感器都没有检测到黑色,说明小车完全偏离赛道,停止
else:
print ("停止")
car_stop()

#控制界面函数
def ConInterface():
#播放本地视频的函数
def playLocalVideo():
moviePath=filedialog.askopenfilename() #文件对话框,用来打开文件夹,选择文件
print(moviePath)
playBtn.place_forget() #隐藏播放按钮
movie=cv2.VideoCapture(moviePath)
waitTime= 1000/movie.get(5) #获取每帧需要的时间
print(waitTime)
movieTime = (movie.get(7) / movie.get(5))/60 #用视频文件的总帧数除以视频文件的帧速率得到视频的总时长,并换算成分钟
print(movieTime)
playSc.config(to=movieTime)
while movie.isOpened():
ret,readyFrame=movie.read() #read()返回两个值,ret 表示帧是否为空,readyFrame表示当前帧
if ret==True:
movieFrame=cv2.cvtColor(readyFrame,cv2.COLOR_BGR2RGBA) #校正颜色
newImage=Image.fromarray(movieFrame).resize((675,360)) #调整大小
newCover=ImageTk.PhotoImage(image=newImage)
video.configure(image=newCover) #将图像加入到video标签中
video.image=newCover
video.update()
cv2.waitKey(int(waitTime)) #播放完每帧等待的时间
else:
break

#使用opencv调用树莓派摄像头
def playVideo():
playBtn.place_forget() #点击播放按钮之后开始播放视频,隐藏按钮
movie=cv2.VideoCapture(0) #打开摄像头
movie_fps=movie.get(5) #得到画面的帧速率
print(movie_fps)
waitTime=int(1000/movie_fps)
print(waitTime)
while movie.isOpened():
#读取帧,返回布尔值和当前帧,ret 表示帧是否为空,readyFrame表示当前帧
ret,readyFrame=movie.read()
if ret==True:
#校正图像颜色
movieFrame=cv2.cvtColor(readyFrame,cv2.COLOR_BGR2RGBA)
#设置图像大小
newImage=Image.fromarray(movieFrame).resize((675,360))
#将照片设置成和tkinter兼容的图像
newCover=ImageTk.PhotoImage(image=newImage)
#将图像放入Label中
video.configure(image=newCover)
video.image=newCover
video.update()
cv2.waitKey(20)
else:
break
movie.release() #释放资源



#猫脸检测function
def det_catface():
# 加载猫脸检测器,此处使用data/haarcascade的猫脸识别训练集
catPath = "/home/pi/Desktop/haarcascade_frontalcatface.xml"
faceCascade = cv2.CascadeClassifier(catPath)
#humanPath = '/home/pi/Desktop/haarcascade_frontalface_default.xml'
#face_cascade = cv2.CascadeClassifier(humanPath)
# 读取图片
movie=cv2.VideoCapture(0) #打开摄像头
movie_fps=movie.get(5) #得到画面的帧速率
#print(movie_fps)
waitTime=int(1000/movie_fps)
#print(waitTime)
while movie.isOpened():
#读取帧,返回布尔值和当前帧,ret 表示帧是否为空,readyFrame表示当前帧
ret,readyFrame=movie.read()
if ret==True:
img=readyFrame
gray=cv2.cvtColor(readyFrame,cv2.COLOR_BGR2RGBA)
# 猫脸检测
catfaces = faceCascade.detectMultiScale(
gray,
scaleFactor= 1.02,
minNeighbors=3,
minSize=(150, 150),
flags=cv2.CASCADE_SCALE_IMAGE
)
''''humanfaces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.15,
minNeighbors=5,
minSize=(3, 3)
)'''
# 框出猫脸并加上文字说明
for (x, y, w, h) in catfaces:
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 0, 255), 2)
cv2.putText(img,'Cat',(x,y-7), 3, 1.2, (0, 255, 0), 2, cv2.LINE_AA)
# 绘制人脸的矩形区域(红色边框)
'''for (x, y, w, h) in humanfaces:
cv2.rectangle(img, (x,y), (x+w,y+h), (0,0,255), 2)
cv2.putText(img,'Human',(x,y-7), 3, 1.2, (0, 255, 0), 2, cv2.LINE_AA)'''
# 显示视频(live)
#cv2.imshow('Detecting',readyFrame)
#刘泽家新加的部分,将视频放入控制框
#设置图像大小
newImage=Image.fromarray(readyFrame).resize((675,360))
#将照片设置成和tkinter兼容的图像
newCover=ImageTk.PhotoImage(image=newImage)
#将图像放入Label中
video.configure(image=newCover)
video.image=newCover
video.update()
num_catface=len(catfaces)
# 猫脸个数
#num_humanface=len(humanfaces)
# 人脸个数
print('检测到'+str(num_catface)+'只猫')#'检测到'+str(num_humanface)+'个人')
if num_catface>=1: #or num_humanface>=1:
#猫脸个数输出,给喇叭高电平
beepAction(1,1,num_catface)


print('停止前进')
car_stop()
#car_stop()
else:
print('正常前进')
turn_up(35,0.5)
cv2.imwrite("Finding.jpg",img)
cv2.waitKey(1)
else:
break
movie.release() #释放资源

#设置线程执行播放视频程序

def denoise(areas, pos):
#剔除误差过大数据
#因为距离近的话,摄像头拍的黑线会宽一点,所以这里的len有细微差异

result = []
result = areas
#for a in areas:
# if pos == 'near' and abs(len(a) - 125) < 15:
# result.append(a)
# elif pos == 'medium' and abs(len(a) - 90) < 15:
# result.append(a)
# elif pos == 'far' and abs(len(a) - 75) < 15:
# result.append(a)
#print(result)
#取最长的两组作为黑线两边
result1 = []
if len(areas) == 0:
return [[320]]
a_last = areas[0]
for a in areas:
if a[-1]>320:
if a[0]<320:
result1.append(a)
result1.append(a)
else:
result1.append(a_last)
result1.append(a)
a_last = a

if a[0]>320:
break


# for i in range(len(result)):
# len_list.append(len(result[i]))
# if len_list == []:
# return [340]
# result1.append(result[len_list.index(max(len_list))])
# if len(len_list)>1:
# len_list.remove(len(result1[0]))
# result1.append(result[len_list.index(max(len_list))])
#print('111')
return result1

def group_consecutives(vals, step=1):
#将黑线分组,如果abs超过2没有黑线,就切断(是不是有点苛刻?)
#print(vals)
run = []
result = []
expect = None
for v in vals:
if expect is None or abs(v - expect) < 2:
run.append(v)
else:
print(1)
result.append(run)
run = []
expect = v + step
#if result != []:
#print(len(result[0]))
#print(vals)
return vals
def get_size(line):
res = 0
for i in range(len(line)):
res+=len(line[i])
return res
def calc_m(line):
pre = line[0]
left = line[0]
right = line[0]
if line[0]>320:
return (line[0]-40)/2
if line[-1]<320:
return (line[-1]+680)/2
for i in line:
if i>320:
left = pre
right = i
pre = i
return (left+right)/2
def calc_eachline_center(line):


if len(line) == 1:



#print('m')
#print(m)
if len(line[0]) == 0:
return 320
m = np.mean(line[0])
#m = calc_m(line[0])
#return m
if m<315:
return (m+680)/2
elif m>325:
return (m-40)/2
else:
return m
return m
if len(line) == 2:
first_m = int(np.sum(np.sum(line))/get_size(line))
#sec = calc_eachline_center([[first_m]])
return first_m
else:
return 320
def get_center(near_line,medium_line,far_line):
#假设1:near的点必定取到两个点
#假设2:如果medium的点只能取到一边,则far的点不用考虑另外一边
#用大津法模糊二值化,寻找赛道
#主要思路类似PD控制,用差分来估测未来走向
#print(np.array(near_line).size)
center1,center2,center3 = calc_eachline_center(near_line),calc_eachline_center(medium_line),calc_eachline_center(far_line)
# center1 = int(np.sum(np.sum(near_line))/get_size(near_line))
# center3 = int(np.sum(np.sum(far_line))/get_size(far_line))
# center2 = int(np.sum(np.sum(medium_line))/get_size(medium_line))
print(center1,center2,center3)
#if len(near_line) == 2:
#如果要用左右center就取消注释
#center_l = int(np.sum(near_line[0])/near_line[0].size)
#center_r = int(np.sum(near_line[1])/near_line[1].size)
#center_l,center_r = min(center_l,center_r),max(center_l,center_r)
if len(medium_line) == 1:
#使用假设2
if center2>center3:
ans_center = np.mean([center1,center2-0*(center2 - center3)])
else:
ans_center = np.mean([center1,center2+0*(center3 - center2)])#常数根据实际情况调整
elif len(far_line) == 1:
ans_center = 0.4*center1+0.4*center2+0.2*center3
else:
ans_center = 0.5*center1+0.3*center2+0.2*center3
#ans_center = 0.5*center1+0.3*center2+0.2*center3
return ans_center

def looking_for_path():
#一些需要调试的参数,先摆在这里
#-----------------------
width_flag = 20#检测路线的宽度下限
near_pos = 300
medium_pos = 200
far_pos = 80
back_pos = 479
#-----------------------
cap = cv2.VideoCapture(0) #实例化摄像头
Path_Dect_px_sum = 0 #坐标值求和
threads = []
t1 = threading.Thread(target=PathDect,args=(u'监听',))
threads.append(t1)
for t in threads:
t.setDaemon(True)#奖pathdect作为子线程,应该是守护线程的设置
t.start()
while True:
ret,frame = cap.read() #capture frame_by_frame
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) #获取灰度图像
gray = cv2.blur(gray,(5,5))#有必要低通一下
ret,thresh1=cv2.threshold(gray,30,255,cv2.THRESH_BINARY)#对灰度图像进行二值化,thresh是
thresh1 = cv2.dilate(thresh1,None,iterations = 2)#膨胀操作一下,增加白色区域

#接收两个红外传感器的信号

#print(thresh1[near_pos])
#for i in range(len(thresh1)):
# for j in range(len(thresh1)):
# thresh1[i,j] = 255-thresh1[i,j]
near_line = group_consecutives(np.where(thresh1[near_pos] == 0))
medium_line = group_consecutives(np.where(thresh1[medium_pos] == 0))
far_line = group_consecutives(np.where(thresh1[far_pos] == 0))
back_line = group_consecutives(np.where(thresh1[back_pos] == 0))
#print(near_line,medium_line,far_line)
#near_line = denoise(near_line,'near')
#medium_line = denoise(medium_line,'medium')
#far_line = denoise(far_line,'far')

global Path_Dect_px
Path_Dect_px = get_center(near_line,medium_line,far_line)
print(Path_Dect_px)
if len(back_line[0]) != 0:
Path_Dect_px = 0
# Path_Dect_fre_count = 1
# for j in range(0,640,5): #采样像素点,5为步进,一共128个点
# if thresh1[240,j] == 0:
# Path_Dect_px_sum = Path_Dect_px_sum + j #黑色像素点坐标值求和
# Path_Dect_fre_count = Path_Dect_fre_count + 1 #黑色像素点个数求和
# Path_Dect_px = (Path_Dect_px_sum )/(Path_Dect_fre_count) #黑色像素中心点为坐标和除以个数
# Path_Dect_px_sum = 0

#我们需要多取几个点才能算斜率
#取三组,六个点,但可能存在取不到的情况。
#应该保证至少最下面的一组点能取到,这个做一个基本假设。
green = (0,255,0)
cv2.line(thresh1,(0,near_pos),(640,near_pos),green,1)
cv2.line(thresh1,(0,medium_pos),(640,medium_pos),green,1)
cv2.line(thresh1,(0,far_pos),(640,far_pos),green,1)
#刘泽家新加的部分,将视频放入控制框
#设置图像大小
newImage=Image.fromarray(thresh1).resize((675,360))
#将照片设置成和tkinter兼容的图像
newCover=ImageTk.PhotoImage(image=newImage)
#将图像放入Label中
video.configure(image=newCover)
video.image=newCover
video.update()
#cv2.imshow('BINARY',thresh1) #树莓派桌面显示二值化图像,比较占资源默认注释掉调试时可以打开
if cv2.waitKey(1)&0XFF ==ord('q'):#检测到按键q退出
car_stop()
break
cap.release()
def PathDect(func):
run_time = 0.01

while True:
if Path_Dect_px == 0:
turn_back(40,0.1)
LS=GPIO.input(LSenso)
RS=GPIO.input(RSenso)
#左边的传感器没检测到黑色,说明小车车身偏离赛道靠左,右转将小车车身向右调整
if LS==False and RS==True:
print ("infrared 右转")
#turn_left(50,0.1)
#右边的传感器没检测到黑色,说明小车车身偏离赛道靠右,左转将小车车身向左调整
if LS==True and RS==False:
print ("infrared 左转")
#turn_right(50,0.1)
print(sensitive)
print(Path_Dect_px)
#print 'Path_Dect_px %d '%Path_Dect_px #打印巡线中心点坐标值
if (Path_Dect_px < 290)&(Path_Dect_px > 0): #如果巡线中心点偏左,就需要左转来校正。
print("turn left")
turn_left(60,run_time)
elif Path_Dect_px > 350:#如果巡线中心点偏右,就需要右转来校正。
print("turn right")
turn_right(60,run_time)
else : #如果巡线中心点居中,就可以直行。
print("go stright")
turn_up(45,run_time)
time.sleep(0.007)
car_stop()
time.sleep(0.007)
def Thread_playVideo():

thread_playVideo=threading.Thread(target=looking_for_path)
thread_playVideo.start()


def Thread_detcatface():
thread_detcatface=threading.Thread(target=det_catface)
thread_detcatface.start()


root=Tk() #初始化tk
root.title("控制界面") #设置标题
root.geometry("1080x720") #设置窗口大小
root['bg']="#333333" #设置窗口背景颜色
#root.iconbitmap('/home/pi/ZRcode/go.ico')
root.resizable(width=False,height=False) #设置窗口长和宽是否可以变化


#分别设置三个Frame 模块用于存放Label,Button
videof=Frame(root,height=360,width=675,cursor="cross")
videof.place(x=199,y=0)

f1=Frame(root,height=270,width=540,cursor="circle",bg="#333333")
f1.place(x=269,y=359)

f2=Frame(root,height=180,width=540,cursor="plus",bg="#333333")
f2.place(x=269,y=629)

#视频窗口
movieImage=Image.open('/home/pi/Desktop/coverimg.jpg').resize((675,360))
cover=ImageTk.PhotoImage(image=movieImage)

video=Label(videof,width=675,height=360,bd=0,bg="pink",image=cover)
video.place(x=0,y=0)

#播放按钮的布局
iconImag=Image.open('/home/pi/Desktop/niubi2.PNG').resize((32,32))
icoBtn=ImageTk.PhotoImage(image=iconImag)
playBtn=Button(videof,image=icoBtn,cursor="hand2",command=Thread_playVideo,relief="groove")
playBtn.place(x=319,y=159)
#playSc=Scale(videof,from_=0,to=90,length=675,orient=HORIZONTAL,resolution=0.2,showvalue=0,bd=0,cursor="hand2",troughcolor="white")
#playSc.place(x=0,y=310)


#加载猫脸检测器
catImag=Image.open('/home/pi/Desktop/niubi3.jpg').resize((50,34))
catBtn=ImageTk.PhotoImage(image=catImag)
playBtn=Button(videof,image=catBtn,cursor="hand2",command=Thread_detcatface,relief="groove")
playBtn.place(x=309,y=259)


#控制按钮
up=Button(f1,text="前进",command=lambda:turn_up(40,1),activeforeground="green",activebackground="yellow",height=1,width=4)
up.place(x=267,y=39)
left=Button(f1,text="左转",command=lambda:turn_left(40,1),activeforeground="green",activebackground="yellow",height=1,width=4)
left.place(x=132,y=134)
right=Button(f1,text="右转",command=lambda:turn_right(40,1),activeforeground="green",activebackground="yellow",height=1,width=4)
right.place(x=412,y=134)
back=Button(f1,text="后退",command=lambda:turn_back(40,1),activeforeground="green",activebackground="yellow",height=1,width=4)
back.place(x=267,y=230)
stop=Button(f1,text="停止",command=car_stop,activeforeground="green",activebackground="yellow",height=1,width=4)
stop.place(x=267,y=134)
xunji=Button(f2,text="循迹",command=Thread_track,activeforeground="green",activebackground="yellow",height=1,width=4)
xunji.place(x=68,y=44)
bz=Button(f2,text="避障",command=Thread_bizhang,activeforeground="green",activebackground="yellow",height=1,width=4)
bz.place(x=461,y=44)
over=Button(f2,text="OVER",command=Thread_gpio_clean,activeforeground="green",activebackground="yellow",height=1,width=6)
over.place(x=263,y=44)

root.mainloop()

#清除GPIO占用
def gpio_clean():
print ("清除GPIO占用")
GPIO.cleanup()


#创建线程执行避障函数
def Thread_bizhang():
thread_bizhang=threading.Thread(target=bizhang)
thread_bizhang.start()

#创建线程执行循迹函数
def Thread_track():
thread_track=threading.Thread(target=track)
thread_track.start()

#创建线程执行清理GPIO函数
def Thread_gpio_clean():
Thread_gpio_clean=threading.Thread(target=gpio_clean)
Thread_gpio_clean.start()



#主函数
if __name__=="__main__":
init()
L_Motor=GPIO.PWM(PWMA,100)
L_Motor.start(0)
R_Motor=GPIO.PWM(PWMB,100)
R_Motor.start(0)
try:
ConInterface()
except KeyboardInterrupt:
GPIO.cleanup() #清除GPIO占用



Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2015-2023 galaxy
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信