/
towertrack.py
329 lines (269 loc) · 11.5 KB
/
towertrack.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
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
#!/usr/bin/env python
# ------------------------------------------------------------------------
# imports
# ------------------------------------------------------------------------
import cv2, sys, time, os, math, logging
from point import point
from networktables import NetworkTable
from threading import Thread
# ------------------------------------------------------------------------
# config
# ------------------------------------------------------------------------
if sys.platform == 'win32':
method = 0 # 0 = face tracking, 1 = SURF
feedback = True
camera = "c905"
use_servo = False # when False, use networktables
show_image = True # display grabbed image
capsize = point(640, 480)
cam_center_angle = 0.0
else:
method = 0 # 0 = face tracking, 1 = SURF
feedback = False
camera = "pi"
use_servo = True # when False, use networktables
show_image = True # display grabbed image
capsize = point(640, 480)
cam_center_angle = 0.0
print("using %s camera" % camera)
if camera == "pi":
fov = point(53.5, 41.4) # see https://www.raspberrypi.org/documentation/hardware/camera.md
elif camera == "c905":
fov = point(64.0, 48.0) # est. Logitech c905 webcam
else:
fov = point(64.0, 48.0) # est. Logitech c905 webcam
if use_servo:
from pantilt import *
# ------------------------------------------------
# Webcam lag management
#
# If you have ffmpeg support enabled (which it is by default in most opencv builds), you can retrieve
# the mjpeg stream directly from the camera via FFMPEG.
# Something similar to the following (error checking omitted):
#
# vc = cv2.VideoCapture()
# vc.open('http://%s/mjpg/video.mjpg' % camera_host)
#
# h = vc.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)
# w = vc.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)
#
# capture_buffer = np.empty(shape=(h, w, 3), dtype=np.uint8)
#
# while True:
# retval, img = vc.read(capture_buffer)
# ------------------------------------------------
class CameraStream:
def __init__(self, cam_idx = 0):
if camera == 'pi':
# modprobe required after pi reboot
os.system('sudo modprobe bcm2835-v4l2') # Load the BCM V4l2 driver for /dev/video0
os.system('v4l2-ctl -p 4') # Set the framerate (not sure this does anything!)
#os.system('v4l2-ctl -c focus_auto=0') # Disable autofocus??
# try 'v4l2-ctl -l' to show all possible controls
pass
self.cam_idx = cam_idx
def start(self):
self.capture = cv2.VideoCapture(self.cam_idx)
# self.capture.set(cv2.CAP_PROP_SETTINGS, 1) # pops up dialog for webcam settings
self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, capsize.x)
self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, capsize.y)
buffsize = self.capture.get(cv2.CAP_PROP_BUFFERSIZE)
if buffsize != -1:
print("VideoCapture buffer size = %d, setting to 1" % buffsize)
self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 1)
# Start new thread to continuously get images
time.sleep(2 if camera == 'pi' else 1)
self.quit = False
self.ret, self.currentFrame = self.capture.read()
self.img_thread = Thread(target=self.updateFrame, args=()).start()
def stop(self):
self.quit = True
self.img_thread.join()
self.currentFrame = None
self.capture.release()
# Continually updates self.currentFrame
def updateFrame(self):
while not self.quit:
ret, frame = self.capture.read()
while ret == False: # Continually grab frames until we get a good one
time.sleep(0.005) # but sleep for 5 milliseconds
ret, frame = self.capture.read()
self.ret = True
self.currentFrame = frame
def getFrame(self):
return (self.ret, self.currentFrame)
def nextFrame(self):
self.currentFrame = None
self.ret = False
# ------------------------------------------------
# Frame Size. Smaller is faster, but less accurate.
# ------------------------------------------------
# Target info
# -----------
top_target_height = 97.0 # the height to the top of the target in first stronghold is 97 inches
# Camera info
# -----------
# camera_height = 20.0 # height of the camera on robot - update later
# Default Pan/Tilt for the camera in degrees.
# Camera range is from 0 to 180
# -------------------------------------------
cam_pan_cur = 70.0
cam_tilt_cur = 70.0
# ------------------------------------------------------------------------
# Set up the CascadeClassifier for face tracking
# ------------------------------------------------------------------------
def initFaceTracking():
global faceCascade
if sys.platform == 'win32':
cascPath = 'C:/greg/dev/opencv/data/lbpcascades/lbpcascade_frontalface.xml'
else:
cascPath = '/usr/local/share/OpenCV/lbpcascades/lbpcascade_frontalface.xml'
faceCascade = cv2.CascadeClassifier(cascPath)
# ------------------------------------------------------------------------
# Given a frame, find the first face, draw a green rectangle around it,
# and return the center of the face
# ------------------------------------------------------------------------
def findFace(frame):
global faceCascade
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.equalizeHist( gray )
faces = faceCascade.detectMultiScale(gray, 1.1, 3, 0, (10, 10))
if show_image:
for (x, y, w, h) in faces:
cv2.rectangle(gray, (x, y), (x+w, y+h), (0, 255, 0), 2) # Draw a green rectangle around the face
for (x, y, w, h) in faces:
return (gray, point(x, y) + (point(w, h) / 2)) # and return the center of the first one found
return (gray, None)
# ------------------------------------------------------------------------
# Given a frame, find the FRC Stronghold 2016 tower target
# and return the center of it
# ------------------------------------------------------------------------
def findTargetSift(frame, kp1, desc1):
# Convert to greyscale for detection
# ----------------------------------
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.equalizeHist(gray)
return (gray, None)
# ------------------------------------------------------------------------
# move the camera so that it points to pixel (x, y) of the previous frame
# ------------------------------------------------------------------------
def track(p, raspi):
global cam_center_angle
global cam_pan_cur, cam_tilt_cur
# Correct relative to center of image
# -----------------------------------
diff = p - capsize / 2
# Convert to percentage offset
# ----------------------------
diff.toFloat()
if feedback:
print('diff = (%f, %f)' % (diff.x, diff.y)) # in pixels
turn = point(diff.x / capsize.x, diff.y / capsize.y) # in %
## servo seems to react with excessively large movement to tilt commands, so
## divide turn.y by 8 instead of by 2
turn = point(turn.x / 2.0, turn.y / 8)
if feedback:
print('turn = (%f, %f)' % (turn.x, turn.y))
# Scale offset to degrees
# -----------------------
turn.x = turn.x * fov.x
turn.y = turn.y * fov.y
# print('turn = (%f, %f)' % (turn.x, turn.y))
cam_pan = -turn.x
cam_tilt = cam_center_angle + turn.y
# Update the robot
# ----------------
if feedback:
print('pan = %f, tilt = %f ' % (cam_pan, cam_tilt))
if use_servo:
cam_pan_cur += cam_pan
cam_tilt_cur += cam_tilt
if feedback:
print('pan_cur = %f, tilt_cur = %f ' % (cam_pan_cur, cam_tilt_cur))
# Clamp Pan/Tilt to 0 to 180 degrees
# ----------------------------------
cam_tilt_cur = max(0, min(130, cam_tilt_cur))
pan(cam_pan_cur)
tilt(cam_tilt_cur)
else:
if int(cam_pan) == 0:
raspi.putNumber('shoot', 1)
else:
raspi.putNumber('shoot', 0)
raspi.putNumber('pan', int(cam_pan))
raspi.putNumber('tilt', int(cam_tilt))
# ------------------------------------------------------------------------
# networktables
# ------------------------------------------------------------------------
def initNetworktables():
logging.basicConfig(level=logging.DEBUG) # to see messages from networktables
NetworkTable.setIPAddress('127.0.0.1')
# NetworkTable.setIPAddress('roborio-5260.local')
NetworkTable.setClientMode()
NetworkTable.initialize()
return NetworkTable.getTable('Pi')
# ------------------------------------------------------------------------
# main capture/track/display loop
# ------------------------------------------------------------------------
def MainProgram():
cam_stream = CameraStream(0)
cam_stream.start()
if use_servo:
pan(cam_pan_cur) # Turn the camera to the default position
tilt(cam_tilt_cur)
raspi = None
else:
raspi = initNetworktables()
if method == 0:
initFaceTracking()
elif method == 1:
# Initiate SIFT detector
sift = cv2.xfeatures2d.SIFT_create()
# find the keypoints and descriptors with SIFT
img1 = cv2.imread('target.png', 0)
if img1 and not img1.empty():
kp1, desc1 = sift.detectAndCompute(img1, None)
#skip = 0
loop_cnt = 0
while True:
loop_cnt += 1
ret, frame = cam_stream.getFrame() # get a frame
if ret == False:
if loop_cnt < 10 or loop_cnt % 100 == 0:
print("Error getting image") # if we didn't get a frame, try again
time.sleep(0.025) # sleep for 25 milliseconds so the image thread can get its image
continue # maybe it was just a hiccup!
#if skip:
# skip = skip - 1
#if skip:
# continue
if method == 0:
img, p = findFace(frame) # Do face detection
elif method == 1 and img1 and not img1.empty():
img, p = findTargetSift(frame, kp1, descl) # Do target detection using SIFT
else:
img, p = None, None
if p:
#cv2.circle(frame, p.asTuple(), 3, 255, -1)
track(p, raspi) # Point the camera to the returned position
#skip = 5
# only on windows, show captured image in window
# ----------------------------------------------
if show_image:
frame = img
frame = cv2.resize(frame, (capsize * 1).asTuple())
#frame = cv2.flip(frame, 1)
cv2.imshow('Video', frame) # Display the image, with rectangle
if p:
cam_stream.nextFrame() # make sure we get a frame after the servo moved
time.sleep(0.030) # sleep for a few milliseconds
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# When everything is done, release the capture
# --------------------------------------------
cam_stream.stop()
cv2.destroyAllWindows()
# ------------------------------------------------------------------------
# just run main program
# ------------------------------------------------------------------------
MainProgram()