CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

GitHub Repository: Ardupilot/ardupilot
Path: blob/master/libraries/AP_Camera/examples/gst_udp_to_wx.py
Views: 1799
1
""""
2
Capture a UDP video stream and display in wxpython
3
4
Usage
5
-----
6
7
1. udpsink
8
9
gst-launch-1.0 -v videotestsrc ! 'video/x-raw,format=I420,width=640,height=480,framerate=50/1' ! queue ! videoconvert ! x264enc bitrate=800 speed-preset=6 tune=4 key-int-max=10 ! rtph264pay ! udpsink host=127.0.0.1 port=5600
10
11
2. display in wxpython
12
13
python ./gst_udp_to_wx.py
14
15
Acknowledgments
16
---------------
17
18
Video class to capture GStreamer frames
19
https://www.ardusub.com/developers/opencv.html
20
21
ImagePanel class to display openCV images in wxWidgets
22
https://stackoverflow.com/questions/14804741/opencv-integration-with-wxpython
23
"""
24
25
import copy
26
import cv2
27
import gi
28
import numpy as np
29
import threading
30
import wx
31
32
33
gi.require_version("Gst", "1.0")
34
from gi.repository import Gst
35
36
37
class VideoStream:
38
"""BlueRov video capture class constructor
39
40
Attributes:
41
port (int): Video UDP port
42
video_codec (string): Source h264 parser
43
video_decode (string): Transform YUV (12bits) to BGR (24bits)
44
video_pipe (object): GStreamer top-level pipeline
45
video_sink (object): Gstreamer sink element
46
video_sink_conf (string): Sink configuration
47
video_source (string): Udp source ip and port
48
latest_frame (np.ndarray): Latest retrieved video frame
49
"""
50
51
def __init__(self, port=5600):
52
"""Summary
53
54
Args:
55
port (int, optional): UDP port
56
"""
57
58
Gst.init(None)
59
60
self.port = port
61
self.latest_frame = self._new_frame = None
62
63
# [Software component diagram](https://www.ardusub.com/software/components.html)
64
# UDP video stream (:5600)
65
self.video_source = "udpsrc port={}".format(self.port)
66
# [Rasp raw image](http://picamera.readthedocs.io/en/release-0.7/recipes2.html#raw-image-capture-yuv-format)
67
# Cam -> CSI-2 -> H264 Raw (YUV 4-4-4 (12bits) I420)
68
self.video_codec = (
69
"! application/x-rtp, payload=96 ! rtph264depay ! h264parse ! avdec_h264"
70
)
71
# Python don't have nibble, convert YUV nibbles (4-4-4) to OpenCV standard BGR bytes (8-8-8)
72
self.video_decode = (
73
"! decodebin ! videoconvert ! video/x-raw,format=(string)BGR ! videoconvert"
74
)
75
# Create a sink to get data
76
self.video_sink_conf = (
77
"! appsink emit-signals=true sync=false max-buffers=2 drop=true"
78
)
79
80
self.video_pipe = None
81
self.video_sink = None
82
83
self.run()
84
85
def start_gst(self, config=None):
86
""" Start gstreamer pipeline and sink
87
Pipeline description list e.g:
88
[
89
'videotestsrc ! decodebin', \
90
'! videoconvert ! video/x-raw,format=(string)BGR ! videoconvert',
91
'! appsink'
92
]
93
94
Args:
95
config (list, optional): Gstreamer pileline description list
96
"""
97
98
if not config:
99
config = [
100
"videotestsrc ! decodebin",
101
"! videoconvert ! video/x-raw,format=(string)BGR ! videoconvert",
102
"! appsink",
103
]
104
105
command = " ".join(config)
106
self.video_pipe = Gst.parse_launch(command)
107
self.video_pipe.set_state(Gst.State.PLAYING)
108
self.video_sink = self.video_pipe.get_by_name("appsink0")
109
110
@staticmethod
111
def gst_to_opencv(sample):
112
"""Transform byte array into np array
113
114
Args:
115
sample (TYPE): Description
116
117
Returns:
118
TYPE: Description
119
"""
120
buf = sample.get_buffer()
121
caps_structure = sample.get_caps().get_structure(0)
122
array = np.ndarray(
123
(caps_structure.get_value("height"), caps_structure.get_value("width"), 3),
124
buffer=buf.extract_dup(0, buf.get_size()),
125
dtype=np.uint8,
126
)
127
return array
128
129
def frame(self):
130
"""Get Frame
131
132
Returns:
133
np.ndarray: latest retrieved image frame
134
"""
135
if self.frame_available:
136
self.latest_frame = self._new_frame
137
# reset to indicate latest frame has been 'consumed'
138
self._new_frame = None
139
return self.latest_frame
140
141
def frame_available(self):
142
"""Check if a new frame is available
143
144
Returns:
145
bool: true if a new frame is available
146
"""
147
return self._new_frame is not None
148
149
def run(self):
150
"""Get frame to update _new_frame"""
151
152
self.start_gst(
153
[
154
self.video_source,
155
self.video_codec,
156
self.video_decode,
157
self.video_sink_conf,
158
]
159
)
160
161
self.video_sink.connect("new-sample", self.callback)
162
163
def callback(self, sink):
164
sample = sink.emit("pull-sample")
165
self._new_frame = self.gst_to_opencv(sample)
166
167
return Gst.FlowReturn.OK
168
169
170
class ImagePanel(wx.Panel):
171
def __init__(self, parent, video_stream, fps=30):
172
wx.Panel.__init__(self, parent)
173
174
self._video_stream = video_stream
175
176
# Shared between threads
177
self._frame_lock = threading.Lock()
178
self._latest_frame = None
179
180
print("Waiting for video stream...")
181
waited = 0
182
while not self._video_stream.frame_available():
183
waited += 1
184
print("\r Frame not available (x{})".format(waited), end="")
185
cv2.waitKey(30)
186
print("\nSuccess! Video stream available")
187
188
if self._video_stream.frame_available():
189
# Only retrieve and display a frame if it's new
190
frame = copy.deepcopy(self._video_stream.frame())
191
192
# Frame size
193
height, width, _ = frame.shape
194
195
parent.SetSize((width, height))
196
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
197
198
self.bmp = wx.Bitmap.FromBuffer(width, height, frame)
199
200
self.timer = wx.Timer(self)
201
self.timer.Start(int(1000 / fps))
202
203
self.Bind(wx.EVT_PAINT, self.OnPaint)
204
self.Bind(wx.EVT_TIMER, self.NextFrame)
205
206
def OnPaint(self, evt):
207
dc = wx.BufferedPaintDC(self)
208
dc.DrawBitmap(self.bmp, 0, 0)
209
210
def NextFrame(self, event):
211
if self._video_stream.frame_available():
212
frame = copy.deepcopy(self._video_stream.frame())
213
214
# Convert frame to bitmap for wxFrame
215
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
216
self.bmp.CopyFromBuffer(frame)
217
self.Refresh()
218
219
220
def main():
221
222
# create the video stream
223
video_stream = VideoStream(port=5600)
224
225
# app must run on the main thread
226
app = wx.App()
227
wx_frame = wx.Frame(None)
228
229
# create the image panel
230
image_panel = ImagePanel(wx_frame, video_stream, fps=30)
231
232
wx_frame.Show()
233
app.MainLoop()
234
235
236
if __name__ == "__main__":
237
main()
238
239