├── .gitignore ├── LICENSE ├── README.md ├── bag2video.py └── manifest.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Oregon State University 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bag2video 2 | ========= 3 | 4 | Convert images in a rosbag to a variable framerate video. Variable framerate is achieved through duplicating frames since OpenCv can't encode at variable framerates. This can produce very large files which should be re-encoded with something like ffmpeg. 5 | 6 | # Usage 7 | usage: bag2video.py [-h] [--outfile OUTFILE] [--precision PRECISION] [--viz] 8 | [--start START] [--end END] [--encoding {rgb8,bgr8,mono8}] 9 | topic bagfile 10 | 11 | Extract and encode video from bag files. 12 | 13 | positional arguments: 14 | topic 15 | bagfile 16 | 17 | optional arguments: 18 | -h, --help show this help message and exit 19 | --outfile OUTFILE, -o OUTFILE 20 | Destination of the video file. Defaults to the 21 | location of the input file. 22 | --precision PRECISION, -p PRECISION 23 | Precision of variable framerate interpolation. Higher 24 | numbers match the actual framerater better, but result 25 | in larger files and slower conversion times. 26 | --viz, -v Display frames in a GUI window. 27 | --start START, -s START 28 | Rostime representing where to start in the bag. 29 | --end END, -e END Rostime representing where to stop in the bag. 30 | --encoding {rgb8,bgr8,mono8} 31 | Encoding of the deserialized image. 32 | -------------------------------------------------------------------------------- /bag2video.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from __future__ import division 4 | import rosbag, rospy, numpy as np 5 | import sys, os, cv2, glob 6 | from itertools import izip, repeat 7 | import argparse 8 | 9 | # try to find cv_bridge: 10 | try: 11 | from cv_bridge import CvBridge 12 | except ImportError: 13 | # assume we are on an older ROS version, and try loading the dummy manifest 14 | # to see if that fixes the import error 15 | try: 16 | import roslib; roslib.load_manifest("bag2video") 17 | from cv_bridge import CvBridge 18 | except: 19 | print "Could not find ROS package: cv_bridge" 20 | print "If ROS version is pre-Groovy, try putting this package in ROS_PACKAGE_PATH" 21 | sys.exit(1) 22 | 23 | def get_info(bag, topic=None, start_time=rospy.Time(0), stop_time=rospy.Time(sys.maxint)): 24 | size = (0,0) 25 | times = [] 26 | 27 | # read the first message to get the image size 28 | msg = bag.read_messages(topics=topic).next()[1] 29 | size = (msg.width, msg.height) 30 | 31 | # now read the rest of the messages for the rates 32 | iterator = bag.read_messages(topics=topic, start_time=start_time, end_time=stop_time)#, raw=True) 33 | for _, msg, _ in iterator: 34 | time = msg.header.stamp 35 | times.append(time.to_sec()) 36 | size = (msg.width, msg.height) 37 | diffs = 1/np.diff(times) 38 | return np.median(diffs), min(diffs), max(diffs), size, times 39 | 40 | def calc_n_frames(times, precision=10): 41 | # the smallest interval should be one frame, larger intervals more 42 | intervals = np.diff(times) 43 | return np.int64(np.round(precision*intervals/min(intervals))) 44 | 45 | def write_frames(bag, writer, total, topic=None, nframes=repeat(1), start_time=rospy.Time(0), stop_time=rospy.Time(sys.maxint), viz=False, encoding='bgr8'): 46 | bridge = CvBridge() 47 | if viz: 48 | cv2.namedWindow('win') 49 | count = 1 50 | iterator = bag.read_messages(topics=topic, start_time=start_time, end_time=stop_time) 51 | for (topic, msg, time), reps in izip(iterator, nframes): 52 | print '\rWriting frame %s of %s at time %s' % (count, total, time), 53 | img = np.asarray(bridge.imgmsg_to_cv2(msg, 'bgr8')) 54 | for rep in range(reps): 55 | writer.write(img) 56 | imshow('win', img) 57 | count += 1 58 | 59 | def imshow(win, img): 60 | cv2.imshow(win, img) 61 | cv2.waitKey(1) 62 | 63 | def noshow(win, img): 64 | pass 65 | 66 | if __name__ == '__main__': 67 | parser = argparse.ArgumentParser(description='Extract and encode video from bag files.') 68 | parser.add_argument('--outfile', '-o', action='store', default=None, 69 | help='Destination of the video file. Defaults to the location of the input file.') 70 | parser.add_argument('--precision', '-p', action='store', default=10, type=int, 71 | help='Precision of variable framerate interpolation. Higher numbers\ 72 | match the actual framerater better, but result in larger files and slower conversion times.') 73 | parser.add_argument('--viz', '-v', action='store_true', help='Display frames in a GUI window.') 74 | parser.add_argument('--start', '-s', action='store', default=rospy.Time(0), type=rospy.Time, 75 | help='Rostime representing where to start in the bag.') 76 | parser.add_argument('--end', '-e', action='store', default=rospy.Time(sys.maxint), type=rospy.Time, 77 | help='Rostime representing where to stop in the bag.') 78 | parser.add_argument('--encoding', choices=('rgb8', 'bgr8', 'mono8'), default='bgr8', 79 | help='Encoding of the deserialized image.') 80 | 81 | parser.add_argument('topic') 82 | parser.add_argument('bagfile') 83 | 84 | args = parser.parse_args() 85 | 86 | if not args.viz: 87 | imshow = noshow 88 | 89 | for bagfile in glob.glob(args.bagfile): 90 | print bagfile 91 | outfile = args.outfile 92 | if not outfile: 93 | outfile = os.path.join(*os.path.split(bagfile)[-1].split('.')[:-1]) + '.avi' 94 | bag = rosbag.Bag(bagfile, 'r') 95 | print 'Calculating video properties' 96 | rate, minrate, maxrate, size, times = get_info(bag, args.topic, start_time=args.start, stop_time=args.end) 97 | nframes = calc_n_frames(times, args.precision) 98 | # writer = cv2.VideoWriter(outfile, cv2.cv.CV_FOURCC(*'DIVX'), rate, size) 99 | writer = cv2.VideoWriter(outfile, cv2.VideoWriter_fourcc(*'DIVX'), np.ceil(maxrate*args.precision), size) 100 | print 'Writing video' 101 | write_frames(bag, writer, len(times), topic=args.topic, nframes=nframes, start_time=args.start, stop_time=args.end, encoding=args.encoding) 102 | writer.release() 103 | print '\n' 104 | -------------------------------------------------------------------------------- /manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | This is a dummy manifest to find cv_bridge for older versions of ROS 4 | 5 | Dan Lazewatsky 6 | BSD 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------