001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.demos.sandbox.video; 034 035import java.util.ArrayList; 036import java.util.Collection; 037import java.util.HashSet; 038import java.util.List; 039import java.util.Set; 040 041import org.kohsuke.args4j.Argument; 042import org.kohsuke.args4j.CmdLineException; 043import org.kohsuke.args4j.CmdLineParser; 044import org.kohsuke.args4j.Option; 045import org.openimaj.image.MBFImage; 046import org.openimaj.ml.annotation.Annotated; 047import org.openimaj.video.Video; 048import org.openimaj.video.processing.shotdetector.CombiShotDetector; 049import org.openimaj.video.processing.shotdetector.HistogramVideoShotDetector; 050import org.openimaj.video.processing.shotdetector.LocalHistogramVideoShotDetector; 051import org.openimaj.video.processing.shotdetector.ShotBoundary; 052import org.openimaj.video.timecode.HrsMinSecFrameTimecode; 053import org.openimaj.video.timecode.VideoTimecode; 054import org.openimaj.video.xuggle.XuggleVideo; 055 056/** 057 * A tool for annotating scenes in a video. A scene is defined rather vaguely as 058 * a selection of shots of the same subject but, at least for now, each shot 059 * will be a scene. However, there is the ability for scenes to include multiple 060 * shots. Each scene also has a set of {@link SceneAnnotation} objects which 061 * each have a set of annotations that describe the content of the scene. 062 * 063 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 064 * @created 21 Jan 2013 065 * @version $Author$, $Revision$, $Date$ 066 */ 067public class VideoSceneAnnotationTool 068{ 069 /** 070 * Represents a set of contiguous frames in a video that represent a single 071 * scene. This may or may not contain multiple shots. Equals and hashCode 072 * are implemented based on the start and end times of the scene boundaries 073 * (requires that the VideoTimecode used also supports equals). 074 * 075 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 076 * @created 21 Jan 2013 077 * @version $Author$, $Revision$, $Date$ 078 */ 079 protected static class VideoScene 080 { 081 /** The shot boundary at the beginning of each shot */ 082 protected List<ShotBoundary<MBFImage>> listOfShots; 083 084 /** The timecode of the first frame in the scene */ 085 protected VideoTimecode startOfScene; 086 087 /** The timecode of the last frame in the scene */ 088 protected VideoTimecode endOfScene; 089 090 /** 091 * Constructor 092 */ 093 public VideoScene() 094 { 095 this.listOfShots = new ArrayList<ShotBoundary<MBFImage>>(); 096 } 097 098 @Override 099 public int hashCode() 100 { 101 return (int) (this.startOfScene.getTimecodeInMilliseconds() + this.endOfScene.getTimecodeInMilliseconds()); 102 } 103 104 @Override 105 public boolean equals(final Object obj) 106 { 107 if (obj == this) 108 return true; 109 110 if (obj instanceof VideoScene) 111 { 112 final VideoScene vs = (VideoScene) obj; 113 return this.startOfScene.equals(vs.startOfScene) && 114 this.endOfScene.equals(vs.endOfScene); 115 } 116 117 return false; 118 } 119 } 120 121 /** 122 * A {@link VideoScene} that has a list of annotations associated with it. 123 * 124 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 125 * @created 22 Jan 2013 126 * @version $Author$, $Revision$, $Date$ 127 */ 128 protected static class AnnotatedVideoScene extends VideoScene 129 implements Annotated<VideoScene, SceneAnnotation> 130 { 131 /** A list of the annotations from each annotator */ 132 public Set<SceneAnnotation> annotations; 133 134 /** 135 * Constructor 136 */ 137 public AnnotatedVideoScene() 138 { 139 this.annotations = new HashSet<SceneAnnotation>(); 140 } 141 142 /** 143 * {@inheritDoc} 144 * 145 * @see org.openimaj.ml.annotation.Annotated#getObject() 146 */ 147 @Override 148 public VideoScene getObject() 149 { 150 return this; 151 } 152 153 /** 154 * {@inheritDoc} 155 * 156 * @see org.openimaj.ml.annotation.Annotated#getAnnotations() 157 */ 158 @Override 159 public Collection<SceneAnnotation> getAnnotations() 160 { 161 return this.annotations; 162 } 163 164 /** 165 * {@inheritDoc} 166 * 167 * @see java.lang.Object#toString() 168 */ 169 @Override 170 public String toString() 171 { 172 String s = "Annotated Video Scene " + super.toString() + "\n"; 173 s += "======================================\n"; 174 s += "start : " + this.startOfScene.toString() + "\n"; 175 s += "end : " + this.endOfScene.toString() + "\n"; 176 s += "annotations: " + this.getAnnotations() + "\n"; 177 s += "======================================\n"; 178 return s; 179 } 180 } 181 182 /** 183 * Stores a scene and its annotations. 184 * 185 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 186 * @created 21 Jan 2013 187 * @version $Author$, $Revision$, $Date$ 188 */ 189 public static class SceneAnnotation implements 190 Annotated<VideoScene, String> 191 { 192 /** The annotator that produced the annotations */ 193 @SuppressWarnings("rawtypes") 194 protected Class<? extends VideoAnnotator> annotatorClass; 195 196 /** The scene being annotated */ 197 protected VideoScene scene; 198 199 /** The annotations */ 200 protected Set<String> annotations; 201 202 /** 203 * Constructor 204 */ 205 public SceneAnnotation() 206 { 207 this.annotations = new HashSet<String>(); 208 } 209 210 /** 211 * {@inheritDoc} 212 * 213 * @see org.openimaj.ml.annotation.Annotated#getObject() 214 */ 215 @Override 216 public VideoScene getObject() 217 { 218 return this.scene; 219 } 220 221 /** 222 * {@inheritDoc} 223 * 224 * @see org.openimaj.ml.annotation.Annotated#getAnnotations() 225 */ 226 @Override 227 public Collection<String> getAnnotations() 228 { 229 return this.annotations; 230 } 231 232 @Override 233 public String toString() 234 { 235 return this.annotations.toString(); 236 } 237 } 238 239 /** 240 * Options for the tool 241 * 242 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 243 * @created 23 Jan 2013 244 * @version $Author$, $Revision$, $Date$ 245 */ 246 public static class VideoSceneAnnotationToolOptions 247 { 248 /** The file to use as input */ 249 @Argument( 250 usage = "Video to process", 251 required = true, 252 metaVar = "FILE") 253 public String inputFile; 254 255 /** The number of milliseconds under which a scene is discarded */ 256 @Option( 257 name = "--shortestScene", 258 aliases = "-ss", 259 usage = "Shortest Scene Length (ms) - default 1500") 260 private final double nMillisecondsShortestScene = 1500; 261 } 262 263 /** The list of video processors that will annotate the video */ 264 private final List<VideoAnnotator<MBFImage, String>> annotators; 265 266 /** The list of scene annotations that were last processed */ 267 private List<AnnotatedVideoScene> annotatedScenes; 268 269 /** The current scene being processed */ 270 private AnnotatedVideoScene currentScene = null; 271 272 /** The number of milliseconds under which a scene is discarded */ 273 private double nMillisecondsShortestScene = 1500; 274 275 /** 276 * Constructor that does no processing but sets up the class ready for 277 * processing. Use this constructor if you want to alter the set of 278 * annotation algorithms prior to processing. 279 */ 280 public VideoSceneAnnotationTool() 281 { 282 this.annotators = new ArrayList<VideoAnnotator<MBFImage, String>>(); 283 } 284 285 /** 286 * Constructor that takes the video to process and begins processing 287 * immediately. 288 * 289 * @param video 290 * The video to process. 291 */ 292 public VideoSceneAnnotationTool(final Video<MBFImage> video) 293 { 294 this(); 295 this.processVideo(video); 296 } 297 298 /** 299 * Add a video annotator into the chain. 300 * 301 * @param annotator 302 * The annotator to add 303 */ 304 public void addVideoAnnotator(final VideoAnnotator<MBFImage, String> annotator) 305 { 306 this.annotators.add(annotator); 307 } 308 309 /** 310 * Processes a video from start to finish. 311 * 312 * @param video 313 * The video to process 314 * @return A list of scene annotations 315 */ 316 public List<AnnotatedVideoScene> processVideo(final Video<MBFImage> video) 317 { 318 this.annotatedScenes = new ArrayList<AnnotatedVideoScene>(); 319 320 // Create a shot detector as we're actually going to be processing 321 // shots, not the whole video 322 final CombiShotDetector vsd = new CombiShotDetector(video); 323 vsd.addVideoShotDetector(new HistogramVideoShotDetector(video), 1); 324 vsd.addVideoShotDetector(new LocalHistogramVideoShotDetector(video, 20), 1); 325 326 // Go through the frames in the video detecting shots 327 for (final MBFImage frame : video) 328 { 329 // Used to determine whether this is a new scene. 330 boolean newScene = false; 331 332 // Process the frame with the shot detector 333 // final OtsuThreshold o = new OtsuThreshold(); 334 // double t = (o.calculateThreshold( frame.getBand( 0 ) ) + 335 // o.calculateThreshold( frame.getBand( 1 ) ) + 336 // o.calculateThreshold( frame.getBand( 2 ) ) ) / 3; 337 // t *= frame.getWidth() * frame.getHeight()/2; 338 // System.out.println( t ); 339 // vsd.setThreshold( t ); 340 vsd.processFrame(frame); 341 342 // TODO: Scene detection needs to be implemented. We use shots here. 343 if (vsd.wasLastFrameBoundary() || this.currentScene == null) 344 newScene = true; 345 346 // If we are entering a new scene, then we finish off this current 347 // scene and create a new one. 348 if (newScene) 349 { 350 // First time around, currentScene will be null. 351 if (this.currentScene != null) 352 { 353 // If we already had a scene, we'll set its end 354 // time to be the previous frame processed. 355 this.currentScene.endOfScene = new HrsMinSecFrameTimecode( 356 video.getCurrentFrameIndex() - 1, video.getFPS()); 357 358 // Store the current annotations for the last scene 359 this.currentScene.annotations = new HashSet<SceneAnnotation>(); 360 for (final VideoAnnotator<MBFImage, String> annotator : this.annotators) 361 { 362 final SceneAnnotation sc = new SceneAnnotation(); 363 sc.scene = this.currentScene; 364 sc.annotatorClass = annotator.getClass(); 365 366 // Store annotations 367 sc.annotations.addAll(annotator.getAnnotations()); 368 369 // Add this scene annotation to the current scene 370 this.currentScene.annotations.add(sc); 371 } 372 373 System.out.println("Scene complete: "); 374 375 // Check the scene is long enough to be considered a scene. 376 if (this.currentScene.endOfScene.getTimecodeInMilliseconds() - 377 this.currentScene.startOfScene.getTimecodeInMilliseconds() 378 >= this.nMillisecondsShortestScene) 379 this.annotatedScenes.add(this.currentScene); 380 else 381 System.out.println("Scene discarded: Too short"); 382 } 383 384 // Create the new scene to annotate 385 this.currentScene = new AnnotatedVideoScene(); 386 this.currentScene.startOfScene = new HrsMinSecFrameTimecode( 387 video.getCurrentFrameIndex() - 1, video.getFPS()); 388 389 // Now move on to process the next shot 390 this.resetAnnotators(); 391 } 392 393 // If we've changed shot within this scene, then add the 394 // shot to the scene 395 if (vsd.wasLastFrameBoundary()) 396 this.currentScene.listOfShots.add(vsd.getLastShotBoundary()); 397 398 // Carry on annotating the scene with the next frame 399 this.processFrame(frame); 400 } 401 402 vsd.close(); 403 404 return this.annotatedScenes; 405 } 406 407 /** 408 * Reset the annotators being used 409 */ 410 private void resetAnnotators() 411 { 412 for (final VideoAnnotator<?, ?> annotator : this.annotators) 413 annotator.reset(); 414 } 415 416 /** 417 * Process the given frame with the analysis annotators 418 * 419 * @param frame 420 * the frame 421 */ 422 private void processFrame(final MBFImage frame) 423 { 424 // Send the frame to each of the annotators 425 for (final VideoAnnotator<MBFImage, ?> annotator : this.annotators) 426 annotator.processFrame(frame); 427 } 428 429 /** 430 * Set the shortest length of scene that will be stored. 431 * 432 * @param ms 433 * The number of milliseconds 434 */ 435 public void setShortestSceneLength(final double ms) 436 { 437 this.nMillisecondsShortestScene = ms; 438 } 439 440 /** 441 * @param args 442 */ 443 public static void main(final String[] args) 444 { 445 final VideoSceneAnnotationToolOptions vsato = 446 new VideoSceneAnnotationToolOptions(); 447 final CmdLineParser parser = new CmdLineParser(vsato); 448 try 449 { 450 parser.parseArgument(args); 451 } catch (final CmdLineException e) 452 { 453 System.out.println(e.getMessage()); 454 System.out.println(); 455 System.out.println("VideoSceneAnnotationTool FILE [options]"); 456 parser.printUsage(System.out); 457 System.exit(1); 458 } 459 460 // Create the tool 461 final VideoSceneAnnotationTool vsa = new VideoSceneAnnotationTool(); 462 vsa.setShortestSceneLength(vsato.nMillisecondsShortestScene); 463 464 // Setup the tool - add some annotators 465 vsa.addVideoAnnotator(new FaceShotTypeAnnotator()); 466 467 // Create the video to process and then process it 468 final XuggleVideo video = new XuggleVideo(vsato.inputFile); 469 final List<AnnotatedVideoScene> finalScenes = vsa.processVideo(video); 470 471 System.out.println("\n\n\n============================================="); 472 System.out.println("============================================="); 473 System.out.println("Final List of Scenes: "); 474 System.out.println(finalScenes); 475 System.out.println("============================================="); 476 } 477}