Skip to content

Commit bf1a5c8

Browse files
authored
Merge pull request #17 from BerkeleyAutomation/video_update
update video recorder to use ffmpeg-python for realtime video
2 parents 03d9b37 + 02ad631 commit bf1a5c8

3 files changed

Lines changed: 55 additions & 109 deletions

File tree

perception/image.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,36 @@ def _image_data(self, normalize=False,
15231523
return im_data.astype(np.uint16)
15241524
return im_data.astype(np.uint8)
15251525

1526+
def save(self, filename, normalize=False, min_depth=MIN_DEPTH, max_depth=MAX_DEPTH, twobyte=False):
1527+
"""Writes the image to a file.
1528+
1529+
Parameters
1530+
----------
1531+
filename : :obj:`str`
1532+
The file to save the image to. Must be one of .png, .jpg,
1533+
.npy, or .npz.
1534+
1535+
Raises
1536+
------
1537+
ValueError
1538+
If an unsupported file type is specified.
1539+
"""
1540+
filename = str(filename)
1541+
file_root, file_ext = os.path.splitext(filename)
1542+
if file_ext in COLOR_IMAGE_EXTS:
1543+
im_data = self._image_data(normalize=normalize, min_depth=min_depth, max_depth=max_depth, twobyte=twobyte)
1544+
if im_data.dtype.type == np.uint8:
1545+
pil_image = PImage.fromarray(im_data.squeeze())
1546+
pil_image.save(filename)
1547+
else:
1548+
try:
1549+
import png
1550+
except:
1551+
raise ValueError('PyPNG not installed! Cannot save 16-bit images')
1552+
png.fromarray(im_data, 'L').save(filename)
1553+
else:
1554+
super().save(filename)
1555+
15261556
def resize(self, size, interp='bilinear'):
15271557
"""Resize the image.
15281558

perception/video_recorder.py

Lines changed: 23 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,10 @@
11
'''
2-
Class to record videos from webcams using opencv
3-
Author: Jacky Liang
2+
Class to record videos from webcams using ffmpeg-python
3+
Author: Mike Danielczuk
44
'''
5-
import cv2
6-
import logging
7-
from multiprocessing import Process, Queue
8-
import numpy as np
9-
import os
10-
import skvideo.io as si
11-
import sys
5+
import ffmpeg
6+
import time
127

13-
class _Camera(Process):
14-
""" Private class to manage a separate webcam data collection process.
15-
16-
Attributes
17-
----------
18-
camera : :obj:`cv2.VideoCapture`
19-
opencv video capturing object
20-
cmd_q : :obj:`Queue`
21-
queue for commands to the recording process
22-
res : 2-tuple
23-
height and width of the video stream
24-
codec : :obj:`str`
25-
string name of codec, e.g. XVID
26-
fps : int
27-
number of frames per second
28-
rate : int
29-
rate at which to read frames (e.g. 2 means skip every other frame)
30-
"""
31-
def __init__(self, camera, cmd_q, res, codec, fps, rate=1):
32-
Process.__init__(self)
33-
34-
self.res = res
35-
self.fps = fps
36-
self.codec = codec
37-
38-
self.camera = camera
39-
self.fourcc = cv2.VideoWriter_fourcc(*self.codec)
40-
self.rate = rate
41-
42-
self.cmd_q = cmd_q
43-
self.recording = False
44-
self.out = None
45-
self.data_buf = None
46-
self.count = 0
47-
48-
def run(self):
49-
""" Continually write images to the filename specified by a command queue. """
50-
if not self.camera.is_running:
51-
self.camera.start()
52-
while True:
53-
if not self.cmd_q.empty():
54-
cmd = self.cmd_q.get()
55-
if cmd[0] == 'stop':
56-
self.out.close()
57-
self.recording = False
58-
elif cmd[0] == 'start':
59-
filename = cmd[1]
60-
self.out = si.FFmpegWriter(filename)
61-
self.recording = True
62-
self.count = 0
63-
64-
if self.recording:
65-
if self.count == 0:
66-
image, _, _ = self.camera.frames()
67-
68-
if self.data_buf is None:
69-
self.data_buf = np.zeros([1, image.height, image.width, image.channels])
70-
self.data_buf[0,...] = image.raw_data
71-
self.out.writeFrame(self.data_buf)
72-
73-
self.count += 1
74-
if self.count == self.rate:
75-
self.count = 0
76-
778
class VideoRecorder:
789
""" Encapsulates video recording processes.
7910
@@ -83,71 +14,55 @@ class VideoRecorder:
8314
USB index of device
8415
res : 2-tuple
8516
resolution of recording and saving. defaults to (640, 480)
86-
codec : :obj:`str`
87-
codec used for encoding video. default to XVID.
17+
video_format : :obj:`str`
18+
format used for video. default to v4l2.
8819
fps : int
8920
frames per second of video captures. defaults to 30
90-
rate : int
91-
rate at which to read frames (e.g. 2 means skip every other frame)
9221
"""
93-
def __init__(self, camera, device_id=0, res=(640, 480), codec='XVID', fps=30, rate=1):
22+
def __init__(self, device_id=0, res=(640, 480), video_format='v4l2', fps=30):
23+
self._device = device_id
9424
self._res = res
95-
self._codec = codec
25+
self._format = video_format
9626
self._fps = fps
97-
self._rate = rate
98-
99-
self._cmd_q = Queue()
100-
101-
self._actual_camera = camera
10227

10328
self._recording = False
104-
self._started = False
10529

10630
@property
10731
def is_recording(self):
10832
return self._recording
109-
33+
11034
@property
11135
def is_started(self):
112-
return self._started
36+
return True
11337

11438
def start(self):
115-
""" Starts the camera recording process. """
116-
self._started = True
117-
self._camera = _Camera(self._actual_camera, self._cmd_q, self._res, self._codec, self._fps, self._rate)
118-
self._camera.start()
39+
pass
11940

120-
def start_recording(self, output_file):
41+
def start_recording(self, output_file, overwrite=True):
12142
""" Starts recording to a given output video file.
12243
12344
Parameters
12445
----------
12546
output_file : :obj:`str`
12647
filename to write video to
12748
"""
128-
if not self._started:
129-
raise Exception("Must start the video recorder first by calling .start()!")
13049
if self._recording:
13150
raise Exception("Cannot record a video while one is already recording!")
13251
self._recording = True
133-
self._cmd_q.put(('start', output_file))
52+
self._video = (
53+
ffmpeg
54+
.input('/dev/video{}'.format(self._device), f=self._format, s='{}x{}'.format(*self._res), framerate=self._fps)
55+
.output(output_file)
56+
.run_async(quiet=True, overwrite_output=overwrite)
57+
)
58+
time.sleep(2) # Pause until video starts
13459

13560
def stop_recording(self):
13661
""" Stops writing video to file. """
13762
if not self._recording:
13863
raise Exception("Cannot stop a video recording when it's not recording!")
139-
self._cmd_q.put(('stop',))
64+
self._video.terminate()
14065
self._recording = False
141-
66+
14267
def stop(self):
143-
""" Stop the camera process. """
144-
if not self._started:
145-
raise Exception("Cannot stop a video recorder before starting it!")
146-
self._started = False
147-
if self._actual_camera.is_running:
148-
self._actual_camera.stop()
149-
if self._camera is not None:
150-
try:
151-
self._camera.terminate()
152-
except:
153-
pass
68+
pass

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
'ipython==5.5.0',
1919
'scikit-image',
2020
'scikit-learn',
21-
'scikit-video'
21+
'scikit-video',
22+
'ffmpeg-python'
2223
]
2324

2425
exec(open('perception/version.py').read())

0 commit comments

Comments
 (0)