|
""" |
|
This is the code we use to view the camera output. |
|
It also had code to construct a birds-eye view from the four camera streams |
|
our robot runs. Don't worry about that part though. We don't use it. |
|
""" |
|
|
|
import cv2 |
|
import numpy as np |
|
|
|
# Define a few utility functions |
|
array = np.array |
|
unzip = lambda x: zip(*x) # The inverse of the zip function |
|
|
|
# Here are the numbers of the streams we wish to use |
|
cams = [1, 2, 3, 4] |
|
|
|
# Some constants for the bird's eye view stuff |
|
|
|
SCALE = 4 # How much to scale down the outputted bird's eye video |
|
|
|
matrs = [array([[-2.57738482e+00, -6.02161725e+00,1.95984254e+03], [-2.92991022e-01, -5.84669931e+00,1.69815169e+03], [-6.20140939e-04, -4.11602611e-03,1.00000000e+00]]), array([[-1.59379313e-01, -6.15997791e+00,1.29704075e+03], [-8.54180920e-01, -5.64936893e+00,1.59149091e+03], [-2.29296551e-05, -4.20450955e-03,1.00000000e+00]]), array([[ 2.38423630e+00, -7.16491735e+00,8.81147315e+02], [ 6.74209899e-01, -5.87250433e+00,6.82229552e+02], [ 3.49910802e-04, -4.86193013e-03,1.00000000e+00]]), array([[-1.23478369e-01, -4.65842846e+00,1.45208474e+03], [ 7.38559847e-01, -5.13411641e+00,1.09008691e+03], [-2.89831683e-05, -3.81333346e-03,1.00000000e+00]])] |
|
masklines = [((184, 0), (4216, 5661)), ((2584, 0), (-1448, 4496)), ((3024, 2682), (-1008, -1026)), ((0, 2050), (4032, -432))] |
|
seeds = [(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)] |
|
shape = (4032//SCALE, 3024//SCALE, 3) |
|
|
|
doubmat = np.array([[2, 0, 0], [0, 2, 0], [0, 0, 1]]) |
|
halfmat = np.array([[1/SCALE, 0, 0], [0, 1/SCALE, 0], [0, 0, 1]]) |
|
matrs = [np.matmul(mat, doubmat) for mat in matrs] |
|
matrs = [np.matmul(halfmat, mat) for mat in matrs] |
|
|
|
for i, y in enumerate(masklines): |
|
masklines[i] = tuple(tuple(y//SCALE for y in x) for x in masklines[i]) |
|
|
|
def gencap(i): |
|
"""Return a opened stream for the ith camera.""" |
|
print('Opening stream {}'.format(i)) |
|
|
|
# If all your cameras are connected locally to your computer, use this! |
|
# stream = cv2.VideoCapture('v4l2src device=/dev/video{} ! video/x-raw,width=320,height=240 ! videoconvert ! appsink sync=false drop=true'.format(i), cv2.CAP_GSTREAMER) |
|
|
|
# If you're running your RTSP server locally, use this! |
|
# stream = cv2.VideoCapture('rtspsrc location=rtsp://127.0.0.1:5800/stream{} latency=0 transport=tcp ! rtph264depay ! decodebin ! videoconvert ! appsink sync=false drop=true'.format(i), cv2.CAP_GSTREAMER) |
|
|
|
# Stream from the Kangaroo and flip certain videos |
|
if i == 1 or i == 3: |
|
stream = cv2.VideoCapture('rtspsrc location=rtsp://10.10.72.12:5800/stream{} latency=0 transport=tcp ! rtph264depay ! decodebin ! tee name=t ! queue ! videoconvert ! appsink sync=false drop=true t. ! queue ! videoconvert ! autovideosink'.format(i), cv2.CAP_GSTREAMER) |
|
else: |
|
stream = cv2.VideoCapture('rtspsrc location=rtsp://10.10.72.12:5800/stream{} latency=0 transport=tcp ! rtph264depay ! decodebin ! tee name=t ! queue ! videoconvert ! appsink sync=false drop=true t. ! queue ! videoflip method=rotate-180 ! videoconvert ! autovideosink'.format(i), cv2.CAP_GSTREAMER) |
|
|
|
print('Opened!') |
|
return stream |
|
|
|
def genmasks(): |
|
"""Return an array of masks, each used to contstrain a warped birds eye image to the area it covers.""" |
|
masks = [] |
|
for e1, e2, seed in zip(masklines, masklines[1:] + [masklines[0]], seeds): |
|
mask = np.zeros(shape[:2], np.uint8) |
|
cv2.line(mask, e1[0], e1[1], 255, 1) |
|
cv2.line(mask, e2[0], e2[1], 255, 1) |
|
|
|
sseed = int(seed[0]*(shape[1]-1)), int(seed[1]*(shape[0]-1)) |
|
fmask = np.zeros((mask.shape[0]+2, mask.shape[1]+2), np.uint8) # See documentation for why this is needed |
|
cv2.floodFill(mask, fmask, (sseed), 255) |
|
# The generated mask conveniently includes the white area minus the line |
|
# So we'll just use that to avoid having to erase the line |
|
mask = fmask[1:-1, 1:-1] |
|
masks.append(mask) |
|
return masks |
|
|
|
def composite(images, masks): |
|
"""Mask each image in the given array then combine them into one image.""" |
|
result = np.zeros(shape, np.uint8) |
|
for img, mask in zip(images, masks): |
|
masked = cv2.bitwise_and(img, img, mask=mask) |
|
result = cv2.add(result, masked) |
|
dim = 2600 // SCALE |
|
cropped = result[:dim,:dim] |
|
return cropped |
|
|
|
masks = genmasks() |
|
caps = [gencap(i) for i in cams] |
|
|
|
for _ in range(30): # Let the camera figure out auto settings |
|
statuses, images = unzip(c.read() for c in caps) |
|
|
|
while True: |
|
statuses, images = list(unzip(c.read() for c in caps)) |
|
images = list(images) |
|
for i, (c, status) in enumerate(zip(cams, statuses)): |
|
if not status: |
|
if i != 3: |
|
caps[i] = gencap(c) |
|
images[i] = np.zeros((240, 360, 3), dtype=np.uint8) |
|
|
|
# If you only want to display a bird's eye view if all cameras are up, uncomment this. |
|
# if not all(statuses): |
|
# print('Skipping frame', statuses) |
|
# continue |
|
|
|
# Preview the images with OpenCV. |
|
# In practice GStreamer's display code has lower latency, so we split our pipelines into |
|
# a to-OpenCV path and a to-display path and let GStreamer handle showing the videos. |
|
|
|
# for i, img in enumerate(images): |
|
# cv2.imshow('image {}'.format(i), img) |
|
|
|
|
|
homs = list(cv2.warpPerspective(img, mat, (shape[1], shape[0])) for img, mat in zip(images, matrs)) |
|
frame = composite(homs, masks) |
|
|
|
cv2.imshow('birdseye', frame[::-1, ::-1]) |
|
if cv2.waitKey(10) == ord('q'): |
|
break |