From 050f83bb23eb9269a0a2f2290d63ff64c5bd7d39 Mon Sep 17 00:00:00 2001 From: Pieter Holtzhausen Date: Sun, 17 Jul 2011 17:45:17 +0200 Subject: [PATCH 1/7] GStreamer and opencv player versions. --- scikits/image/io/video.py | 135 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 scikits/image/io/video.py diff --git a/scikits/image/io/video.py b/scikits/image/io/video.py new file mode 100644 index 00000000..b861647e --- /dev/null +++ b/scikits/image/io/video.py @@ -0,0 +1,135 @@ +import cv +import numpy as np +import pygst +import os, time +pygst.require("0.10") +import gst, gobject +gobject.threads_init() +from gst.extend.discoverer import Discoverer + +class CvVideo(object): + """ + Opencv-based video player. + + Parameters + ---------- + source : str + Media location. + size: tuple, optional + Size of returned array. + """ + def __init__(self, source, size=None): + self.source = source + self.capture = cv.CreateFileCapture(self.source) + self.size = size + + def get(self): + """ + Retrieve a video frame as a numpy array. + """ + img = cv.QueryFrame(self.capture) + if not self.size: + self.size = cv.GetSize(img) + img_mat = np.empty((self.size[1], self.size[0], 3), dtype=np.uint8) + if cv.GetSize(img) == self.size: + cv.Copy(img, img_mat) + else: + cv.Resize(img, img_mat) + return img_mat + + +class GstVideo(object): + """ + GStreamer-based video player. + + Parameters + ---------- + source : str + Media location. + size: tuple, optional + Size of returned array. + sync: bool, optional + Frames are extracted per frame or per time basis. + """ + def __init__(self, source, size=None, sync=False): + self.source = source + self.size = size + self.time_format = gst.Format(gst.FORMAT_TIME) + + # extract video size + if not size: + gobject.idle_add(self._discover_one) + self.mainloop = gobject.MainLoop() + self.mainloop.run() + if not self.size: + self.size = (640, 480) + if os.path.exists(self.source): + self.source = "file://" + self.source + self._create_main_pipeline(self.source, self.size, sync) + + def _discover_one(self): + """ + Callback to start media discovery process. + """ + discoverer = Discoverer(self.source) + discoverer.connect('discovered', self._discovered) + discoverer.discover() + return False + + def _discovered(self, d, is_media): + """ + Callback to on media discovery result. + """ + if is_media: + self.size = (d.videowidth, d.videoheight) + self.mainloop.quit() + return False + + def _create_main_pipeline(self, source, size, sync): + """ + Create the frame extraction pipeline. + """ + pipeline_string = "uridecodebin name=decoder uri=%s ! ffmpegcolorspace ! videoscale ! appsink name=play_sink" % self.source + self.pipeline = gst.parse_launch(pipeline_string) + caps = "video/x-raw-rgb, width=%d, height=%d, depth=24, bpp=24" % size + self.decoder = self.pipeline.get_by_name("decoder") + self.appsink = self.pipeline.get_by_name('play_sink') + self.appsink.set_property('emit-signals', True) + self.appsink.set_property('sync', sync) + self.appsink.set_property('drop', True) + self.appsink.set_property('max-buffers', 1) + self.appsink.set_property('caps', gst.caps_from_string(caps)) + if self.pipeline.set_state(gst.STATE_PLAYING) == gst.STATE_CHANGE_FAILURE: + raise NameError("Failed to load video source %s" % self.source) + buff = self.appsink.emit('pull-preroll') + + def get(self): + """ + Retrieve a video frame as a numpy array. + """ + buff = self.appsink.emit('pull-buffer') + img_mat = np.ndarray(shape=(self.size[1], self.size[0], 3), dtype='uint8', buffer=buff.data) + return img_mat + + + +if __name__ == '__main__': + cv.NamedWindow ('display', cv.CV_WINDOW_AUTOSIZE) + cv.MoveWindow("display", 100, 100); + #camera = GstVideo(source="http://146.232.169.185/video.mjpg") + camera = GstVideo(source="/home/tzhau/hacking/video/ing1.avi", sync=1) + #camera = CvVideo(source="/home/tzhau/hacking/video/ing1.avi") + camera.seek_frame(200) + i = 0 + while 1: + i += 1 + #print 'hey' + #time.sleep(0.5) + #print 'hallo' + a = time.time() + img = camera.get() + + b = time.time() + print b-a + cv.ShowImage('display', img) + cv.WaitKey(100) From 5d8210c29ce9d65ed68b0c18a713920d7a2f50dc Mon Sep 17 00:00:00 2001 From: Pieter Holtzhausen Date: Mon, 18 Jul 2011 16:11:15 +0200 Subject: [PATCH 2/7] Seeking in video --- scikits/image/io/video.py | 50 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/scikits/image/io/video.py b/scikits/image/io/video.py index b861647e..9d1da187 100644 --- a/scikits/image/io/video.py +++ b/scikits/image/io/video.py @@ -37,6 +37,28 @@ class CvVideo(object): cv.Resize(img, img_mat) return img_mat + def seek_frame(self, frame_number): + """ + Seek to specified frame in video. + + Parameters + ---------- + frame_number : int + Frame position + """ + cv.SetCaptureProperty(self.capture, cv.CV_CAP_PROP_POS_FRAMES, frame_number) + + def seek_time(self, milliseconds): + """ + Seek to specified time in video. + + Parameters + ---------- + milliseconds : int + Time position + """ + cv.SetCaptureProperty(self.capture, cv.CV_CAP_PROP_POS_MSEC, milliseconds) + class GstVideo(object): """ @@ -111,15 +133,37 @@ class GstVideo(object): img_mat = np.ndarray(shape=(self.size[1], self.size[0], 3), dtype='uint8', buffer=buff.data) return img_mat - + def seek_frame(self, frame_number): + """ + Seek to specified frame in video. + + Parameters + ---------- + frame_number : int + Frame position + """ + self.pipeline.seek_simple(gst.FORMAT_DEFAULT, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT, frame_number) + + def seek_time(self, milliseconds): + """ + Seek to specified time in video. + + Parameters + ---------- + milliseconds : int + Time position + """ + self.pipeline.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT, milliseconds/1000.0 * gst.SECOND) + if __name__ == '__main__': cv.NamedWindow ('display', cv.CV_WINDOW_AUTOSIZE) cv.MoveWindow("display", 100, 100); #camera = GstVideo(source="http://146.232.169.185/video.mjpg") - camera = GstVideo(source="/home/tzhau/hacking/video/ing1.avi", sync=1) + #camera = GstVideo(source="/home/tzhau/hacking/video/ing1.avi", sync=1) #camera = CvVideo(source="/home/tzhau/hacking/video/ing1.avi") - camera.seek_frame(200) + time.sleep(1) + camera.seek_frame(300) i = 0 while 1: i += 1 From 9b55c5d9f634735119da913d1263f93fece5fc9d Mon Sep 17 00:00:00 2001 From: Pieter Holtzhausen Date: Mon, 18 Jul 2011 17:23:02 +0200 Subject: [PATCH 3/7] Added video duration routines --- scikits/image/io/video.py | 91 +++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 14 deletions(-) diff --git a/scikits/image/io/video.py b/scikits/image/io/video.py index 9d1da187..5d25f0ff 100644 --- a/scikits/image/io/video.py +++ b/scikits/image/io/video.py @@ -7,6 +7,13 @@ import gst, gobject gobject.threads_init() from gst.extend.discoverer import Discoverer +""" +@add_backends +class Video(object): + def __init__(self): + raise NotImplementedError("Please select a video backend.") +""" + class CvVideo(object): """ Opencv-based video player. @@ -18,7 +25,7 @@ class CvVideo(object): size: tuple, optional Size of returned array. """ - def __init__(self, source, size=None): + def __init__(self, source=None, size=None): self.source = source self.capture = cv.CreateFileCapture(self.source) self.size = size @@ -26,6 +33,11 @@ class CvVideo(object): def get(self): """ Retrieve a video frame as a numpy array. + + Returns + ------- + output : array (image) + Retrieved image. """ img = cv.QueryFrame(self.capture) if not self.size: @@ -59,6 +71,30 @@ class CvVideo(object): """ cv.SetCaptureProperty(self.capture, cv.CV_CAP_PROP_POS_MSEC, milliseconds) + def frame_count(self): + """ + Returns frame count of video. + + Returns + ------- + output : int + Frame count. + """ + return cv.GetCaptureProperty(self.capture, cv.CV_CAP_PROP_FRAME_COUNT) + + def duration(self): + """ + Returns time length of video in milliseconds. + + Returns + ------- + output : int + Time length [ms]. + """ + return cv.GetCaptureProperty(self.capture, cv.CV_CAP_PROP_FPS) * \ + cv.GetCaptureProperty(self.capture, cv.CV_CAP_PROP_FRAME_COUNT) + + class GstVideo(object): """ @@ -73,11 +109,11 @@ class GstVideo(object): sync: bool, optional Frames are extracted per frame or per time basis. """ - def __init__(self, source, size=None, sync=False): + def __init__(self, source=None, size=None, sync=False): self.source = source self.size = size - self.time_format = gst.Format(gst.FORMAT_TIME) - + self.video_length = 0 + self.video_rate = 0 # extract video size if not size: gobject.idle_add(self._discover_one) @@ -91,7 +127,7 @@ class GstVideo(object): def _discover_one(self): """ - Callback to start media discovery process. + Callback to start media discovery process, used to retrieve video parameters. """ discoverer = Discoverer(self.source) discoverer.connect('discovered', self._discovered) @@ -103,7 +139,9 @@ class GstVideo(object): Callback to on media discovery result. """ if is_media: - self.size = (d.videowidth, d.videoheight) + self.size = (d.videowidth, d.videoheight) + self.video_length = d.videolength / gst.MSECOND + self.video_rate = d.videorate.num self.mainloop.quit() return False @@ -128,6 +166,11 @@ class GstVideo(object): def get(self): """ Retrieve a video frame as a numpy array. + + Returns + ------- + output : array (image) + Retrieved image. """ buff = self.appsink.emit('pull-buffer') img_mat = np.ndarray(shape=(self.size[1], self.size[0], 3), dtype='uint8', buffer=buff.data) @@ -155,21 +198,41 @@ class GstVideo(object): """ self.pipeline.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT, milliseconds/1000.0 * gst.SECOND) + def frame_count(self): + """ + Returns frame count of video. + + Returns + ------- + output : int + Frame count. + """ + return self.video_length/1000*self.video_rate + + def duration(self): + """ + Returns time length of video in milliseconds. + + Returns + ------- + output : int + Time length [ms]. + """ + return self.video_length + if __name__ == '__main__': cv.NamedWindow ('display', cv.CV_WINDOW_AUTOSIZE) cv.MoveWindow("display", 100, 100); - #camera = GstVideo(source="http://146.232.169.185/video.mjpg") - #camera = GstVideo(source="/home/tzhau/hacking/video/ing1.avi", sync=1) - #camera = CvVideo(source="/home/tzhau/hacking/video/ing1.avi") - time.sleep(1) - camera.seek_frame(300) + camera = GstVideo(source="http://146.232.169.185/video.mjpg") + #$camera = GstVideo(source="/home/tzhau/hacking/video/ing1.avi", sync=1) +# camera = CvVideo(source="/home/tzhau/hacking/video/ing1.avi") + #camera = CvVideo(source="/home/tzhau/Desktop/lego.flv") + print camera.frame_count() + print camera.duration() i = 0 while 1: i += 1 - #print 'hey' - #time.sleep(0.5) - #print 'hallo' a = time.time() img = camera.get() From 03c68121f6a874a591421b9e7d2c67403c22c59c Mon Sep 17 00:00:00 2001 From: Pieter Holtzhausen Date: Fri, 19 Aug 2011 01:27:43 +0200 Subject: [PATCH 4/7] Created Video base class and handle backend import errors. --- scikits/image/io/__init__.py | 1 + scikits/image/io/video.py | 150 +++++++++++++++++++++++++++-------- 2 files changed, 118 insertions(+), 33 deletions(-) diff --git a/scikits/image/io/__init__.py b/scikits/image/io/__init__.py index 4badbbd6..d903712e 100644 --- a/scikits/image/io/__init__.py +++ b/scikits/image/io/__init__.py @@ -20,6 +20,7 @@ from sift import * from collection import * from io import * +from video import * def _update_doc(doc): """Add a list of plugins to the module docstring, formatted as diff --git a/scikits/image/io/video.py b/scikits/image/io/video.py index 5d25f0ff..4b79d3e4 100644 --- a/scikits/image/io/video.py +++ b/scikits/image/io/video.py @@ -1,18 +1,22 @@ -import cv import numpy as np -import pygst import os, time -pygst.require("0.10") -import gst, gobject -gobject.threads_init() -from gst.extend.discoverer import Discoverer -""" -@add_backends -class Video(object): - def __init__(self): - raise NotImplementedError("Please select a video backend.") -""" +try: + import pygst + pygst.require("0.10") + import gst, gobject + gobject.threads_init() + from gst.extend.discoverer import Discoverer + gstreamer_available = True +except ImportError: + gstreamer_available = False + +try: + import cv + opencv_available = True +except ImportError: + opencv_available = False + class CvVideo(object): """ @@ -25,7 +29,9 @@ class CvVideo(object): size: tuple, optional Size of returned array. """ - def __init__(self, source=None, size=None): + def __init__(self, source=None, size=None, backend=None): + if not opencv_available: + raise ImportError("Opencv 2.0+ required") self.source = source self.capture = cv.CreateFileCapture(self.source) self.size = size @@ -44,9 +50,11 @@ class CvVideo(object): self.size = cv.GetSize(img) img_mat = np.empty((self.size[1], self.size[0], 3), dtype=np.uint8) if cv.GetSize(img) == self.size: - cv.Copy(img, img_mat) + cv.Copy(img, img_mat) else: cv.Resize(img, img_mat) + # opencv stores images in BGR format + cv.CvtColor(img_mat, img_mat, cv.CV_BGR2RGB) return img_mat def seek_frame(self, frame_number): @@ -94,7 +102,6 @@ class CvVideo(object): return cv.GetCaptureProperty(self.capture, cv.CV_CAP_PROP_FPS) * \ cv.GetCaptureProperty(self.capture, cv.CV_CAP_PROP_FRAME_COUNT) - class GstVideo(object): """ @@ -108,8 +115,12 @@ class GstVideo(object): Size of returned array. sync: bool, optional Frames are extracted per frame or per time basis. + If enabled the video time step continues onward according to the play rate. + Useful for ip cameras and other real time video feeds. """ def __init__(self, source=None, size=None, sync=False): + if not gstreamer_available: + raise ImportError("GStreamer Python bindings 0.10+ required") self.source = source self.size = size self.video_length = 0 @@ -221,22 +232,95 @@ class GstVideo(object): return self.video_length -if __name__ == '__main__': - cv.NamedWindow ('display', cv.CV_WINDOW_AUTOSIZE) - cv.MoveWindow("display", 100, 100); - camera = GstVideo(source="http://146.232.169.185/video.mjpg") - #$camera = GstVideo(source="/home/tzhau/hacking/video/ing1.avi", sync=1) -# camera = CvVideo(source="/home/tzhau/hacking/video/ing1.avi") - #camera = CvVideo(source="/home/tzhau/Desktop/lego.flv") - print camera.frame_count() - print camera.duration() - i = 0 - while 1: - i += 1 - a = time.time() - img = camera.get() +class Video(object): + """ + Video player. Supports Opencv and Gstreamer backends. + + Parameters + ---------- + source : str + Media location. + size: tuple, optional + Size of returned array. + sync: bool, optional + Frames are extracted per frame or per time basis. Gstreamer only. + If enabled the video time step continues onward according to the play rate. + Useful for IP cameras and other real time video feeds. + backend: str, 'gstreamer' or 'opencv' + Backend to use. + """ + def __init__(self, source=None, size=None, sync=False, backend=None): + if backend == None: + # select backend that is available + if gstreamer_available: + self.video = GstVideo(source, size, sync) + elif opencv_available: + self.video = CvVideo(source, size) + else: + # if no backend available, raise exception + self.video = GstVideo(source, size, sync) + elif backend == "gstreamer": + self.video = GstVideo(source, size, sync) + elif backend == "opencv": + self.video = CvVideo(source, size) + else: + raise ValueError("Unknown backend: %s", backend) + + def get(self): + """ + Retrieve a video frame as a numpy array. + + Returns + ------- + output : array (image) + Retrieved image. + """ + return self.video.get() + + def seek_frame(self, frame_number): + """ + Seek to specified frame in video. + + Parameters + ---------- + frame_number : int + Frame position + """ + self.video.seek_frame(frame_number) + + def seek_time(self, milliseconds): + """ + Seek to specified time in video. + + Parameters + ---------- + milliseconds : int + Time position + """ + self.video.seek_time(milliseconds) + + def frame_count(self): + """ + Returns frame count of video. + + Returns + ------- + output : int + Frame count. + """ + return self.video.frame_count() + + def duration(self): + """ + Returns time length of video in milliseconds. + + Returns + ------- + output : int + Time length [ms]. + """ + return self.video.duration() + + +__all__ = ["Video"] - b = time.time() - print b-a - cv.ShowImage('display', img) - cv.WaitKey(100) From 24252090755c7f0263ac63758e1fb97758228da8 Mon Sep 17 00:00:00 2001 From: Pieter Holtzhausen Date: Mon, 22 Aug 2011 13:53:01 +0200 Subject: [PATCH 5/7] get_collection method interfacing ImageCollection --- scikits/image/io/video.py | 50 +++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/scikits/image/io/video.py b/scikits/image/io/video.py index 4b79d3e4..34299b93 100644 --- a/scikits/image/io/video.py +++ b/scikits/image/io/video.py @@ -1,5 +1,6 @@ import numpy as np import os, time +from scikits.image.io import ImageCollection try: import pygst @@ -20,12 +21,12 @@ except ImportError: class CvVideo(object): """ - Opencv-based video player. + Opencv-based video loader. Parameters ---------- source : str - Media location. + Media location URI. Video file path or http address of IP camera. size: tuple, optional Size of returned array. """ @@ -105,12 +106,12 @@ class CvVideo(object): class GstVideo(object): """ - GStreamer-based video player. + GStreamer-based video loader. Parameters ---------- source : str - Media location. + Media location URI. Video file path or http address of IP camera. size: tuple, optional Size of returned array. sync: bool, optional @@ -234,12 +235,12 @@ class GstVideo(object): class Video(object): """ - Video player. Supports Opencv and Gstreamer backends. + Video loader. Supports Opencv and Gstreamer backends. Parameters ---------- source : str - Media location. + Media location URI. Video file path or http address of IP camera. size: tuple, optional Size of returned array. sync: bool, optional @@ -268,7 +269,7 @@ class Video(object): def get(self): """ - Retrieve a video frame as a numpy array. + Retrieve the next video frame as a numpy array. Returns ------- @@ -321,6 +322,41 @@ class Video(object): """ return self.video.duration() + def get_index_frame(self, frame_number): + """ + Retrieve a specified video frame as a numpy array. + + Parameters + ---------- + frame_number : int + Frame position + + Returns + ------- + output : array (image) + Retrieved image. + """ + self.video.seek_frame(frame_number) + return self.video.get() + + def get_collection(self, time_range=None): + """ + Returns an ImageCollection object + + Parameters + ---------- + time_range: range (int) + Time steps to extract. + + Returns + ------- + output: ImageCollection + Collection of images iterator. + """ + if not time_range: + time_range = range(0, self.frame_count()) + return ImageCollection(time_range, load_func=self.get_index_frame) + __all__ = ["Video"] From c9ec1afea0bf8e7bdfd01721884d6ee174b11ec9 Mon Sep 17 00:00:00 2001 From: Pieter Holtzhausen Date: Mon, 22 Aug 2011 13:59:43 +0200 Subject: [PATCH 6/7] Docstring mods --- scikits/image/io/video.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scikits/image/io/video.py b/scikits/image/io/video.py index 34299b93..774dd8be 100644 --- a/scikits/image/io/video.py +++ b/scikits/image/io/video.py @@ -114,7 +114,7 @@ class GstVideo(object): Media location URI. Video file path or http address of IP camera. size: tuple, optional Size of returned array. - sync: bool, optional + sync: bool, optional (default False) Frames are extracted per frame or per time basis. If enabled the video time step continues onward according to the play rate. Useful for ip cameras and other real time video feeds. @@ -243,7 +243,7 @@ class Video(object): Media location URI. Video file path or http address of IP camera. size: tuple, optional Size of returned array. - sync: bool, optional + sync: bool, optional (default False) Frames are extracted per frame or per time basis. Gstreamer only. If enabled the video time step continues onward according to the play rate. Useful for IP cameras and other real time video feeds. @@ -341,7 +341,7 @@ class Video(object): def get_collection(self, time_range=None): """ - Returns an ImageCollection object + Returns an ImageCollection object. Parameters ---------- @@ -354,7 +354,7 @@ class Video(object): Collection of images iterator. """ if not time_range: - time_range = range(0, self.frame_count()) + time_range = range(self.frame_count()) return ImageCollection(time_range, load_func=self.get_index_frame) From c1c5be1b61a27d52e54700519bcb146de9327842 Mon Sep 17 00:00:00 2001 From: Pieter Holtzhausen Date: Mon, 22 Aug 2011 14:02:01 +0200 Subject: [PATCH 7/7] Docstring optional --- scikits/image/io/video.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scikits/image/io/video.py b/scikits/image/io/video.py index 774dd8be..c79d553d 100644 --- a/scikits/image/io/video.py +++ b/scikits/image/io/video.py @@ -345,8 +345,8 @@ class Video(object): Parameters ---------- - time_range: range (int) - Time steps to extract. + time_range: range (int), optional + Time steps to extract, defaults to the entire length of video. Returns -------