-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRaceCodeFunctions.py
executable file
·276 lines (238 loc) · 7.55 KB
/
RaceCodeFunctions.py
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
#!/usr/bin/env python
# coding: Latin-1
#############################################################
# This is the functions script for the Formula Pi Race Code #
# #
# It is responsible for supplying the Race Code Functions #
# available to Race.py during the race #
#############################################################
import inspect
import time
import os
import math
import cv2
import numpy
import Settings
import ImageProcessor
import Globals
RAD_TO_DEG = 180.0 / math.pi
def LogUserCall(paramFormat, paramTupple):
calleeName = inspect.stack()[1][3] # Calling functions name
if paramFormat:
paramString = paramFormat % paramTupple
logLine = '%s %s(%s)\n' % (ImageProcessor.TimeOnlyStamp(), calleeName, paramString)
else:
logLine = '%s %s()\n' % (ImageProcessor.TimeOnlyStamp(), calleeName)
if Globals.userLogFile:
Globals.userLogFile.write(logLine)
print logLine
# Race Code Functions
def WaitForGo():
LogUserCall(None, None)
if Globals.running:
ImageProcessor.SetImageMode(ImageProcessor.WAIT_FOR_LIGHTS)
while Globals.running and (Globals.imageMode == ImageProcessor.WAIT_FOR_LIGHTS):
time.sleep(Globals.pollDelay)
def FinishRace():
LogUserCall(None, None)
Globals.running = False
def WaitForSeconds(seconds):
LogUserCall('%.3f', (seconds))
time.sleep(seconds)
def Speed(speed):
LogUserCall('%.3f', (speed))
if speed > 100.0:
speed = 100.0
elif speed < 0.0:
speed = 0.0
Globals.userSpeed = float(speed) / 100.0
def AimForLane(position):
LogUserCall('%.3f', (position))
Globals.userTargetLane = float(position)
def WaitForWaypoint(pointNumber):
LogUserCall('%d', (pointNumber))
if Globals.running:
if pointNumber < 1:
# Not valid
return
elif pointNumber > len(Settings.distanceBetweenWaypoints):
# Not valid
return
elif pointNumber == 1:
# Start marker
distance = 0.0
else:
# Other waypoint, lookup distance from start
index = pointNumber - 2
distance = Settings.waypointDistances[index]
# Wait for the distance to match / exceed the waypoint distance
WaitForDistance(distance)
def WaitForDistance(distance):
LogUserCall('%.3f', (distance))
if Globals.running:
lap = LapCount()
# Check if we already passed the marker this lap, if so wait for the next one
if (GetDistance() - distance) > Settings.waitForNextLapAfter:
while lap == LapCount():
time.sleep(Globals.pollDelay)
# Wait until we reach or passed the correct distance
while GetDistance() < distance:
time.sleep(Globals.pollDelay)
# If we complete a lap while waiting it means we messed up the distance :(
if lap != LapCount():
break
def LapCount():
LogUserCall(None, None)
if Globals.running:
return Globals.lapCount
else:
return 99999
def GetDistance():
LogUserCall(None, None)
if Globals.running:
return Globals.lapTravelled
else:
return 99999.9
def TrackFound():
LogUserCall(None, None)
return Globals.trackFound
def CurrentTrackPosition():
LogUserCall(None, None)
calculatedTrackValues = Globals.controller.lastSample
return calculatedTrackValues[0] + calculatedTrackValues[1]
def CurrentAngle():
LogUserCall(None, None)
radians = math.atan(Globals.controller.lastD1 / Settings.angleCorrection)
return radians * RAD_TO_DEG
def TrackCurve():
LogUserCall(None, None)
return Globals.controller.lastD2
def TrackLines():
LogUserCall(None, None)
return Globals.lastLines
def GetLatestImage():
LogUserCall(None, None)
return Globals.lastRawFrame
def PerformProcessingStage(stage, image):
LogUserCall('%d, <image>', (stage))
if stage < 1:
return None
elif stage > len(processingStages):
return None
else:
func = processingStages[stage - 1]
return func(image)
def SaveImage(image, name):
LogUserCall('<image>, "%s"', (name))
if not os.path.isdir('./logs'):
os.mkdir('./logs')
cv2.imwrite('./logs/%s.jpg' % (name), image)
def StartDetailedLoging():
LogUserCall(None, None)
if Globals.processingLogFile:
Globals.processingLogFile.close()
Globals.processingLogFile = None
logPath = './logs/Processing %s.txt' % (ImageProcessor.FullTimeStamp())
try:
if not os.path.isdir('./logs'):
os.mkdir('./logs')
logFile = open(logPath, 'w')
Globals.processingLogFile = logFile
except:
print 'Failed to start processing logging!'
def StartUserLog():
if Globals.userLogFile:
Globals.userLogFile.close()
Globals.userLogFile = None
logPath = './logs/User %s.txt' % (ImageProcessor.FullTimeStamp())
try:
if not os.path.isdir('./logs'):
os.mkdir('./logs')
logFile = open(logPath, 'w')
Globals.userLogFile = logFile
except:
print 'Failed to start user logging!'
LogUserCall(None, None)
def EndDetailedLog():
LogUserCall(None, None)
if Globals.processingLogFile:
Globals.processingLogFile.close()
Globals.processingLogFile = None
def EndUserLog():
LogUserCall(None, None)
if Globals.userLogFile:
Globals.userLogFile.close()
Globals.userLogFile = None
#############################################################
# Below are the image processing stages #
# They are replicas of the standard functionality and are #
# called from the PerformProcessingStage fuction #
#############################################################
# Stage 1 - Get maximum ranges from the image
def GetRanges(image):
bAll, gAll, rAll = cv2.split(image)
return rAll.min(), rAll.max(), gAll.min(), gAll.max(), bAll.min(), bAll.max(),
# Stage 2 - Crop the image to the desired size
def CropFrame(image):
cropped = image[Settings.cropY1 : Settings.cropY2, Settings.cropX1 : Settings.cropX2, :]
return cropped
# Stage 3 - Automatic brightness adjustment
def AutoBrightness(image):
maximum = numpy.max(image)
adjustment = 255.0 / maximum
if adjustment > Settings.autoGainMax:
adjustment = Settings.autoGainMax
corrected = image * adjustment
corrected = numpy.clip(corrected, 0, 255)
corrected = numpy.array(corrected, dtype = numpy.uint8)
return corrected
# Stage 4 - Find the black parts of the image
def ExtractBlackMask(image):
black = cv2.inRange(image, numpy.array((0, 0, 0)),
numpy.array((Settings.blackMaxB, Settings.blackMaxG, Settings.blackMaxR)))
return black
# Stage 5 - Erode a channel
def ErodeChannel(channel):
erodeKernel = numpy.ones((Settings.erodeChannels, Settings.erodeChannels), numpy.uint8)
eroded = cv2.erode(channel, erodeKernel)
return eroded
# Stage 6 - Split colour channels
def SplitRGB(image):
blue, green, red = cv2.split(image)
return red, green, blue
# Stage 7 - Auto level single channel based on settings
def AutoLevel((channel, minimum, maximum, gain)):
autoGain = Settings.targetLevel / maximum
adjusted = (channel - minimum) * gain * autoGain
adjusted = numpy.clip(adjusted, 0, 255)
adjusted = numpy.array(adjusted, dtype = numpy.uint8)
return adjusted
# Stage 8 - Get maximums for each pixel
def MaxImage((red, green, blue)):
return numpy.maximum(numpy.maximum(blue, green), red)
# Stage 9 - Exclude the unwanted ares on a channel
def ExcludeSections((channel, maxImage, black)):
sectionOnly = channel[:]
sectionOnly[sectionOnly < maxImage] = 0
exclude = black > 0
sectionOnly[exclude] = 0
return sectionOnly
# Stage 10 - Build single view from channels
def BuildView((red, green, blue, black)):
viewImage = cv2.merge([blue, green, red])
walls = cv2.merge([black, black, black])
viewImage = cv2.addWeighted(viewImage, 1.0, walls, 1.0, 0)
return viewImage
# The stage list for processing images
processingStages = [
GetRanges,
CropFrame,
AutoBrightness,
ExtractBlackMask,
ErodeChannel,
SplitRGB,
AutoLevel,
MaxImage,
ExcludeSections,
BuildView
]