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