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.vis.video; 034 035import java.util.ArrayList; 036import java.util.HashMap; 037import java.util.List; 038 039import org.openimaj.image.DisplayUtilities; 040import org.openimaj.image.MBFImage; 041import org.openimaj.image.colour.RGBColour; 042import org.openimaj.image.processing.resize.ResizeProcessor; 043import org.openimaj.image.renderer.MBFImageRenderer; 044import org.openimaj.image.typography.hershey.HersheyFont; 045import org.openimaj.image.typography.hershey.HersheyFontStyle; 046import org.openimaj.math.geometry.shape.Rectangle; 047import org.openimaj.time.Timecode; 048import org.openimaj.video.Video; 049import org.openimaj.video.processing.shotdetector.HistogramVideoShotDetector; 050import org.openimaj.video.processing.shotdetector.ShotBoundary; 051 052/** 053 * Will display a video in a timeline with shot detections marked on it. 054 * 055 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 056 * @created 3 Jul 2012 057 * @version $Author$, $Revision$, $Date$ 058 */ 059public class ShotBoundaryVideoBarVisualisation extends VideoBarVisualisation 060{ 061 /** */ 062 private static final long serialVersionUID = 1L; 063 064 /** Shot detector */ 065 private HistogramVideoShotDetector shotDetector = null; 066 067 /** 068 * To avoid constantly resampling, we cache the resampled images against 069 * the hash code of the original image. 070 */ 071 private final HashMap<Integer, MBFImage> imageCache = new HashMap<Integer,MBFImage>(); 072 073 /** 074 * Default constructor that takes the video to visualise 075 * @param video The video to visualise 076 */ 077 public ShotBoundaryVideoBarVisualisation( final Video<MBFImage> video ) 078 { 079 super( video ); 080 081 /// Use a HistogramVideoShotDetector and store the key frames 082 this.shotDetector = new HistogramVideoShotDetector( video ); 083 this.shotDetector.setFindKeyframes( true ); 084 } 085 086 /** 087 * {@inheritDoc} 088 * @see org.openimaj.vis.video.VideoBarVisualisation#processFrame(org.openimaj.image.MBFImage, org.openimaj.time.Timecode) 089 */ 090 @Override 091 public void processFrame( final MBFImage frame, final Timecode t ) 092 { 093 // Look for shot boundaries 094 this.shotDetector.processFrame( frame ); 095 } 096 097 /** 098 * {@inheritDoc} 099 * @see org.openimaj.vis.video.VideoBarVisualisation#update() 100 */ 101 @Override 102 public void update() 103 { 104 // Set the colour of the bar 105 this.visImage.fill( this.barColour ); 106 107 // Get all the shot boundaries we currently have 108 final List<ShotBoundary<MBFImage>> sbs = new ArrayList<ShotBoundary<MBFImage>>( 109 this.shotDetector.getShotBoundaries() ); 110 111 // Store the list of bounding boxes of labels 112 final List<Rectangle> timecodeLabelBounds = new ArrayList<Rectangle>(); 113 114 // Now loop through the shot boundaries. 115 for( final ShotBoundary<MBFImage> sb : sbs ) 116 { 117 // Try to get the resized image that's stored for a boundary image 118 final int hash = sb.getKeyframe().imageAtBoundary.hashCode(); 119 MBFImage img = this.imageCache.get( hash ); 120 121 // We'll cache the resized image if it's not already there 122 if( img == null ) 123 this.imageCache.put( hash, img = sb.getKeyframe().imageAtBoundary 124 .process( new ResizeProcessor( 100, 100 ) ) ); 125 126 // Now draw the image into the visualisation 127 if( img != null ) 128 { 129 // Find the time position of a given timecode. 130 final int x = (int)this.getTimePosition( sb.getTimecode() ); 131// System.out.println( "Drawing image: "+sb.getTimecode()+" -> "+x ); 132 try 133 { 134 // Draw the image and its timecode 135 final MBFImageRenderer r = this.visImage.createRenderer(); 136 final HersheyFont f = HersheyFont.TIMES_BOLD; 137 final HersheyFontStyle<Float[]> fs = f.createStyle( r ); 138 fs.setFontSize( 12 ); 139 final String string = sb.getTimecode().toString(); 140 final Rectangle bounds = f.getRenderer( r ).getSize( string, fs ); 141 bounds.translate( x, img.getHeight()+bounds.height ); 142 143 // Move the bounds until the text isn't overlapping other text 144 boolean overlapping = true; 145 while( overlapping ) 146 { 147 overlapping = false; 148 for( final Rectangle rect : timecodeLabelBounds ) 149 { 150 if( bounds.isOverlapping( rect ) ) 151 { 152 bounds.translate( 0, rect.height ); 153 overlapping = true; 154 break; 155 } 156 } 157 } 158 159 // Store the bounds of the timecode label we're above to draw 160 bounds.width += 8; 161 timecodeLabelBounds.add( bounds ); 162 163 // Draw the thumbnail image 164 r.drawImage( img, x, 0 ); 165 166 // Draw the line at which the boundary occurred 167 r.drawLine( x, 0, x, this.visImage.getHeight(), 2, RGBColour.BLACK ); 168 169 // Draw a box behind the text 170 r.drawShapeFilled( bounds, new Float[]{0f,0f,0f,0.3f} ); 171 172 // Draw the text 173 r.drawText( string, (int)bounds.x+4, (int)(bounds.y+bounds.height), f, 12, RGBColour.WHITE ); 174 } 175 catch( final Exception e ) 176 { 177 e.printStackTrace(); 178 System.out.println( "Image was: "+img ); 179 System.out.println( " - Size: "+img.getWidth()+"x"+img.getHeight() ); 180 System.out.println( " - Num Bands: "+img.numBands() ); 181 System.out.println( "Being drawn to: "+this.visImage ); 182 System.out.println( " - Size: "+this.visImage.getWidth()+"x"+this.visImage.getHeight() ); 183 System.out.println( " - Num Bands: "+this.visImage.numBands() ); 184 System.out.println( " - At Position: "+x+",0 "); 185 186 DisplayUtilities.display( img, "img" ); 187 DisplayUtilities.display( this.visImage, "Vis" ); 188 189 try 190 { 191 Thread.sleep( 100000000 ); 192 } 193 catch( final InterruptedException e1 ) 194 { 195 e1.printStackTrace(); 196 } 197 } 198 } 199 } 200 201 super.update(); 202 } 203}