1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 import time
35 import os
36 import pyvision as pv
37 import cv
38 import random
39
40
41
42
44 '''
45 VideoInterface is an abstract class meant only to define a common interface
46 for all Video subtypes. The VideoInterface defines the methods that every
47 video data source should provide.
48 '''
50 '''
51 Must be overridden to implement the specific frame-grabbing required
52 by different video sources.
53 '''
54 raise NotImplemented
55
57 '''
58 This is a placeholder for the open cv webcam interface that sometimes
59 requires this call.
60 '''
61 pass
62
64 '''
65 The next method calls self.query(), so it is common to most video sources
66 and may not need to be overridden.
67 @return: The next frame in the sequence, or raise StopIteration if done.
68 '''
69 frame = self.query()
70 if frame == None:
71 raise StopIteration("End of video sequence")
72 return frame
73
75 '''
76 Used to resize the source frame to the desired output size. This
77 method is common to most sources and may not need to be overridden.
78 The query() method will typically call this resize() method prior
79 to returning the captured image.
80 @param frame: An openCV image (note: not a pyvision image)
81 @return: An openCV image with the new dimensions
82 '''
83 if self.size == None:
84 return frame
85 else:
86 depth = frame.depth
87 channels = frame.channels
88 w,h = self.size
89 resized = cv.CreateImage( (w,h), depth, channels )
90 cv.Resize( frame, resized, cv.CV_INTER_LINEAR )
91 return resized
92
94 '''
95 Override to provide an appropriate iterator for your video source
96 so that it can be used in a for loop as "for im in videoX: ..."
97 '''
98 raise NotImplemented
99
100 - def play(self, window="Input", pos=None, delay=20,
101 annotate=True, imageBuffer=None, startframe=0, endframe=None,
102 onNewFrame=None, **kwargs ):
103 '''
104 Plays the video, calling the onNewFrame function after loading each
105 frame from the video. The user may interrupt video playback by
106 hitting (sometimes repeatedly) the spacebar, upon which they are
107 given a text menu in the console to abort program, quit playback,
108 continue playback, or step to the next frame.
109 @param window: The window name used to display the video. If None,
110 then the video won't be shown, but onNewFrame will be called at
111 each frame.
112 @param pos: A tuple (x,y) where the output window should be located
113 on the users screen. None indicates default openCV positioning algorithm
114 will be used.
115 @param delay: The delay in ms between window updates. This allows the user
116 to control the playback frame rate. A value of 0 indicates that the video
117 will wait for keyboard input prior to advancing to the next frame. This
118 delay is used by the pauseAndPlay interface, so it will affect the rate
119 at which onNewFrame is called as well.
120 @param annotate: If True, the image will be annotated with the frame number
121 in the upper left corner. Set false for no frame number annotation.
122 @param imageBuffer: An optional pyvision ImageBuffer object to contain the
123 most recent frames. This is useful if a buffer is required for background
124 subtraction, for example. The buffer contents is directly modified each
125 time a new image is captured from the video, and a reference to the buffer
126 is passed to the onNewFrame function (defined below).
127 @param startframe: If > 0, then the video will cue itself by quickly fast-forwarding
128 to the desired start frame before any images are shown. During the cueing process,
129 any _onNewFrame function callbacks (or VideoStreamProcessor objects) will NOT be
130 activated.
131 @param endframe: If not None, then the playback will end after this frame has
132 been processed.
133 @param onNewFrame: A python callable object (function) with a
134 signature of 'foo( pvImage, frameNum, key=None, buffer=None )', where key is
135 the key pressed by the user (if any) during the pauseAndPlay interface, and
136 buffer is a reference to the optional image buffer provided to the play method.
137 @param kwargs: Optional keyword arguments that should be passed
138 onto the onNewFrame function.
139 @return: The final frame number of the video, or the frame number at which
140 the user terminated playback using the 'q'uit option.
141 '''
142 fn = -1
143 vid = self
144 if delay==0:
145 delayObj = {'wait_time':20, 'current_state':'PAUSED'}
146 else:
147 delayObj = {'wait_time':delay, 'current_state':'PLAYING'}
148 key=''
149 for fn, img in enumerate(vid):
150 if fn == 0 and startframe > 0: print "Cueing video to start at %d"%startframe
151 if fn < startframe: continue
152 if not endframe is None and fn > endframe: break
153
154 if imageBuffer != None:
155 imageBuffer.add(img)
156
157 if annotate:
158 pt = pv.Point(10, 10)
159 img.annotateLabel(label="Frame: %d"%(fn+1), point=pt, color="white", background="black")
160
161 if window != None:
162 img.show(window=window,pos=pos,delay=1)
163
164 if onNewFrame != None:
165 onNewFrame( img, fn, key=key, imageBuffer=imageBuffer, **kwargs )
166
167 key = self._pauseAndPlay(delayObj)
168 if key == 'q': break
169
170 if window != None: cv.DestroyWindow(window)
171 return(fn)
172
173 - def _pauseAndPlay(self,delayObj={'wait_time':20, 'current_state':'PLAYING'}):
174 '''
175 This function is intended to be used in the playback loop of a video.
176 It allows the user to interrupt the playback to pause the video, to
177 step through it one frame at a time, and to register other keys/commands
178 that the user may select.
179 @param delayObj: The "delay object", which is just a dictionary that
180 specifies the wait_time (the delay in ms between frames), and
181 the current_state of either 'PLAYING' or 'PAUSED'
182 '''
183 state = delayObj['current_state']
184 wait = delayObj['wait_time']
185
186
187 if state=="PAUSED":
188 print "PAUSED: Select <a>bort program, <q>uit playback, <c>ontinue playback, or <s>tep to next frame."
189 wait = 0
190
191 c = cv.WaitKey(wait)
192 c = c & 127
193
194
195
196
197
198 while c==ord(' '):
199 print "PAUSED: Select <a>bort program, <q>uit playback, <c>ontinue playback, or <s>tep to next frame."
200 c = cv.WaitKey(0)
201 c = c & 127
202
203
204 if c == ord('a'):
205 print "User Aborted Program."
206 raise SystemExit
207 elif c == ord('q'):
208 return 'q'
209 elif c == ord('c'):
210 delayObj['current_state'] = "PLAYING"
211 return 'c'
212 elif c == ord('s'):
213 delayObj['current_state'] = "PAUSED"
214 return 's'
215 else:
216
217 return chr(c)
218
219
220
221
222
223
224
225
227 - def __init__(self,camera_num=0,size=(640,480),flipped=False):
228 '''
229 Web camera interface for cameras attached to your computer via USB or built-in.
230 For IP/network cameras, use the Video object instead.
231 @param camera_num: The camera index. Usually 0 if you only have a single webcam
232 on your computer. See the OpenCV highgui documentation for details.
233 @param flipped: Set to true if camera is installed upside-down.
234 '''
235 self.cv_capture = cv.CreateCameraCapture( camera_num )
236 self.flipped = flipped
237
238
239
240
241 self.size = size
242
244 ''' Return an iterator for this video '''
245 return self
246
248 '''
249 The returned image also include a field named orig_frame which returns
250 the original image returned before rescaling.
251
252 @returns: the frame rescaled to a given size.
253 '''
254
255 frame = cv.QueryFrame( self.cv_capture )
256 if self.flipped:
257 cv.Flip(frame,frame,-1)
258 im = pv.Image(self.resize(frame))
259 im.orig_frame = pv.Image(frame)
260 im.capture_time = time.time()
261 return im
262
264 return cv.GrabFrame( self.cv_capture );
265
267 '''
268 The returned image also include a field named orig_frame which returns
269 the original image returned before rescaling.
270
271 @returns: the frame rescaled to a given size.
272 '''
273 frame = cv.RetrieveFrame( self.cv_capture );
274 im = pv.Image(self.resize(frame))
275 im.orig_frame = pv.Image(frame)
276 return im
277
278
279 -class Video(VideoInterface):
281 '''
282 The basic video class that is used to play back a movie file.
283 @param filename: The full path name of the video file including extension. Also, with
284 current versions of OpenCV, this can be a url to a network IP camera, but you will need
285 to consult your IP camera manufacturer's documentation as url formats vary.
286 @note: The following is an example of using the Video class with an IP camera.
287 The rtsp url is for a linksys WVC54GCA IP camera. The ip address will need to be changed
288 as appropriate for your local network. Other model cameras use different urls. It can take
289 a few seconds for the feed to be established.
290 cam_url = "rtsp://192.168.2.55/img/video.sav"
291 vid = Video(cam_url)
292 vid.play()
293 '''
294 self.filename = filename
295 self.cv_capture = cv.CaptureFromFile( filename );
296 self._numframes = cv.GetCaptureProperty(self.cv_capture,cv.CV_CAP_PROP_FRAME_COUNT)
297 self.size = size
298 self.current_frame = 0
299
301 if self.current_frame > 0 and cv.GetCaptureProperty(self.cv_capture,cv.CV_CAP_PROP_POS_AVI_RATIO) == 1.0:
302 return None
303 frame = cv.QueryFrame( self.cv_capture )
304 if frame == None:
305 raise StopIteration("End of video sequence")
306 self.current_frame += 1
307 frame = cv.CloneImage(frame);
308 return pv.Image(self.resize(frame))
309
311 assert n >= 0 and n <= 1
312 cv.SetCaptureProperty(self.cv_capture, cv.CV_CAP_PROP_POS_AVI_RATIO, float(n))
313
314
316 ''' Return an iterator for this video '''
317 return Video(self.filename,self.size)
318
320 return self._numframes
321
322
323
325 '''
326 Given a sorted list of filenames (including full path), this will
327 treat the list as a video sequence.
328 '''
329 - def __init__(self, filelist, size=None):
330 '''
331 @param filelist: a list of full file paths to the images that comprise the video.
332 They must be files capable of being loaded into a pv.Image() object, and should
333 be in sorted order for playback.
334 @param size: Optional tuple to indicate the desired playback window size.
335 '''
336 self.filelist = filelist
337 self.idx = 0
338 self.size = size
339
342
344 if self.idx >= len(self.filelist): return None
345 f = self.filelist[self.idx]
346 frame = pv.Image(f).asOpenCV()
347 self.idx += 1
348 return pv.Image(self.resize(frame))
349
351 ''' Return an iterator for this video '''
352 return VideoFromFileList(self.filelist)
353
354
356 '''
357 This class allows the user to treat a directory of images as a video.
358
359 This class will recursively search the directories and will load
360 and return any image with an image extension: JPG,JPEG,PNG,TIF,TIFF,GIF,BMP,PPM,PGM
361 '''
362
363 - def __init__(self,dirname,order='ascending',limit=None,size=None,followlinks=True):
364 '''
365 Recursively scans a directory for images and returns all images that
366 could be loaded.
367
368 Example:
369 images = pv.VideoFromDirectory(dirname)
370 for im in images:
371 do something
372
373
374 @param dirname: directory where the images comprising the video exist
375 @type dirname: str
376 @param order: return the images in a random order using the randam.shuffle function.
377 @type order: 'random' | 'ascending'
378 @param limit: limit the number of images returned.
379 @type limit: int
380 @param size: resize all images to this size.
381 @type: (int,int)
382 '''
383 self._dirname = dirname
384 self._order = order
385 self._limit = limit
386 self._size = size
387 self._counter = 0
388
389 self._followlinks = followlinks
390
391 self._image_paths = []
392
393 self._scanImageDir()
394
395 if self._order == 'ascending':
396 self._image_paths.sort()
397 elif self._order == 'random':
398 random.shuffle(self._image_paths)
399 else:
400 raise ValueError("unknown ordering type: %s"%(self._order,))
401
403 '''
404 Check the extension on a filename to see if it is in the list.
405 '''
406 parts = filename.split('.')
407 if len(parts) > 0:
408 ext = parts[-1].upper()
409 return ext in ("JPG",'JPEG',"PNG","TIF","TIFF","GIF","BMP","PPM","PGM")
410
411
413 '''
414 Scan the directory and populate image_paths.
415 '''
416 for dirpath,_,filenames in os.walk(self._dirname,followlinks=self._followlinks):
417 for filename in filenames:
418 if self._checkExtension(filename):
419 path = os.path.join(dirpath,filename)
420 self._image_paths.append(path)
421
422
424 while True:
425 im_path = None
426 try:
427 if self._counter >= len(self._image_paths) or (self._limit != None and self._counter >= self._limit):
428 return None
429 im_path = self._image_paths[self._counter]
430 self._counter += 1
431 im = pv.Image(im_path)
432 if self._size != None:
433 im = im.resize(self.size)
434 return im
435 except:
436 print "Warning: could not process image:",im_path
437
439 ''' Return an iterator for this video '''
440 return VideoFromDirectory(self._dirname, self._order, self._limit, self._size)
441
443 return len(self._image_paths)
444
446 '''
447 This class allows the user to treat a directory of images as a video. It is assumed that
448 the files in the directory are named as follows:
449 {prefix}{num}.{ext}
450 where
451 prefix is any string that is constant for all the files,
452 ext is the file extension/type like jpg, png, etc.
453 num is a zero-padded number like 0001, 0002, ...
454
455 note: the amount of padded zeros is the minimum required based on the length
456 (num frames) in the video, unless a specific padding is specified. So if you only had
457 120 frames, then it would be 001, 002,...120.
458
459 We assume the frames are sequential with no gaps, and start at number startnum (with
460 appropriate padding).
461 '''
462 - def __init__(self,dirname,numframes,prefix="frame",ext="jpg", pad=None, startnum=1, size=None):
463 '''
464 The file names are of the format {prefix}{zero-padded num}.{ext}, the amount of
465 zero-padding is determined automatically based on numframes. If there is additional
466 zero-padding required, put it in the prefix.
467 Example: a directory with images: vid_t1_s1_f001.jpg, ..., vid_t1_s1_f999.jpg
468 would have prefix="vid_t1_s1_f", startnum=1, numframes=999, ext="jpg"
469
470 @param dirname: directory where the images comprising the video exist
471 @param numframes: the number of frames in the video...0 to numframes will be read.
472 specify None to read all images in directory, in which case you must specify
473 a value for the pad parameter.
474 @param prefix: a string which remains as a constant prefix to all frames in video
475 @param ext: the extension of the images, like jpg, png, etc. Do not include the dot.
476 @param pad: the padding (like string.zfill(x)) used on the sequential numbering of
477 the input files. Specify None, and the padding will be determined based on length
478 of numframes. (So if numframes = 1234, then pad=4, 0001,0002,...1234)
479 @param startnum: the starting number of the first frame, defaults to 1
480 @param size: the optional width,height to resize the input frames
481 '''
482 self.dirname = dirname
483 if numframes == None:
484
485 assert(pad != None and pad>0)
486
487 if pad == None:
488 pad = len(str(numframes))
489
490 self.pad = pad
491 self.maxframes = numframes
492 self.prefix = prefix
493 self.ext = ext
494 self.size = size
495 self.startnum = startnum
496 self.current_frame = startnum
497
498
499 if not os.path.exists(dirname):
500 print "Error. Directory: %s does not exist."%dirname
501 raise IOError
502
504 numstr = str(self.current_frame).zfill(self.pad)
505 filename = self.prefix + numstr + "." + self.ext
506 f = os.path.join(self.dirname, filename)
507
508 if (self.maxframes == None) or (self.current_frame <= self.maxframes):
509
510 if os.path.exists(f):
511 frame = pv.Image(f).asOpenCV()
512 self.current_frame += 1
513 return( pv.Image(self.resize(frame)) )
514 else:
515 print "Image file %s does not exist. Stopping VideoFromImages."%f
516
517 return None
518
520 ''' Return an iterator for this video '''
521 return VideoFromImages(self.dirname, self.maxframes, self.prefix, self.ext, self.pad, self.startnum, self.size)
522
524 '''
525 This class allows the user to treat a stack of grayscale images in a 3D numpy array as a video.
526 We assume that the dimensions of the array are ordered as (frame number, width, height).
527 '''
528 - def __init__(self, imageStack, size=None):
529 '''
530 imageStack is the numpy ndarray that represents the image stack. Should be of dimensions (frames,width,height).
531 Optionally, this can be any object, such as pyvision.ImageBuffer, that implements asStackBW() method that returns
532 the grayscale image stack.
533 size is the optional width,height to resize the input frames.
534 '''
535 if str( type(imageStack) ) == "<type 'instance'>":
536 self.imageStack = imageStack.asStackBW()
537 else:
538 self.imageStack = imageStack
539
540 (f,_,_) = self.imageStack.shape
541 self.numFrames = f
542 self.current_frame = 0
543 self.size = size
544
546 if self.current_frame < self.numFrames:
547 frame = pv.Image( self.imageStack[self.current_frame,:,:])
548 self.current_frame += 1
549 return( pv.Image(self.resize(frame.asOpenCV())))
550 return None
551
553 ''' Return an iterator for this video '''
554 return VideoFromImageStack(self.imageStack, self.size)
555
556
558
559 '''
560 FFMPEGVideo is an alternate way to capture video from a file,
561 not directly using OpenCV's highgui. This class does not implement
562 the VideoInterface abstract class, and it does not have the same
563 usage pattern.
564 '''
565 - def __init__(self,filename,size=None,aspect=None,options=""):
566 self.filename = filename
567 self.size = size
568 self.aspect = aspect
569 self.options = options
570
571
572 args = "/opt/local/bin/ffmpeg -i %s %s -f yuv4mpegpipe - "%(filename,options)
573
574
575 self.stdin, self.stdout, self.stderr = os.popen3(args)
576
577
578 line = self.stdout.readline()
579
580
581
582 _,w,h,_,_,aspect,_,_ = line.split()
583
584
585 assert format=='YUV4MPEG2'
586
587
588
589
590
591 assert w[0] == "W"
592 assert h[0] == "H"
593
594 self.w = int(w[1:])
595 self.h = int(h[1:])
596
597
598 if size == None and self.aspect != None:
599 h = self.h
600 w = int(round(self.aspect*h))
601 size = (w,h)
602
603
604 self.size = size
605
606 self.frame_y = cv.CreateImage( (self.w,self.h), cv.IPL_DEPTH_8U, 1 )
607 self.frame_u2 = cv.CreateImage( (self.w/2,self.h/2), cv.IPL_DEPTH_8U, 1 )
608 self.frame_v2 = cv.CreateImage( (self.w/2,self.h/2), cv.IPL_DEPTH_8U, 1 )
609
610 self.frame_u = cv.CreateImage( (self.w,self.h), cv.IPL_DEPTH_8U, 1 )
611 self.frame_v = cv.CreateImage( (self.w,self.h), cv.IPL_DEPTH_8U, 1 )
612 self.frame_col = cv.CreateImage( (self.w,self.h), cv.IPL_DEPTH_8U, 3 )
613
614
615 if self.size != None:
616 w,h = self.size
617 self.frame_resized = cv.CreateImage( (w,h),cv.IPL_DEPTH_8U,3)
618
619
620
622 _ = self.stdout.readline()
623
624
625 y = self.stdout.read(self.w*self.h)
626 u = self.stdout.read(self.w*self.h/4)
627 v = self.stdout.read(self.w*self.h/4)
628 if len(y) < self.w*self.h:
629 raise EOFError
630
631 cv.SetData(self.frame_y,y)
632 cv.SetData(self.frame_u2,u)
633 cv.SetData(self.frame_v2,v)
634
635 cv.Resize(self.frame_u2,self.frame_u)
636 cv.Resize(self.frame_v2,self.frame_v)
637
638 cv.Merge(self.frame_y,self.frame_u,self.frame_v,None,self.frame_col)
639 cv.CvtColor(self.frame_col,self.frame_col,cv.CV_YCrCb2RGB)
640
641 out = self.frame_col
642
643 if self.size != None:
644 cv.Resize(self.frame_col,self.frame_resized)
645 out = self.frame_resized
646
647 return pv.Image(self.frame_y),pv.Image(self.frame_u),pv.Image(self.frame_v),pv.Image(out)
648
649
651 ''' Return an iterator for this video '''
652 return FFMPEGVideo(self.filename,size=self.size,aspect=self.aspect,options=self.options)
653
654
656 try:
657 _,_,_,frame = self.frame()
658 except EOFError:
659 raise StopIteration("End of video sequence")
660 return frame
661