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 package org.openimaj.video.processing.shotdetector;
34
35 import gnu.trove.list.array.TDoubleArrayList;
36
37 import java.awt.HeadlessException;
38 import java.util.ArrayList;
39 import java.util.List;
40
41 import org.openimaj.feature.DoubleFV;
42 import org.openimaj.image.Image;
43 import org.openimaj.video.Video;
44 import org.openimaj.video.VideoDisplay;
45 import org.openimaj.video.VideoDisplay.EndAction;
46 import org.openimaj.video.VideoDisplayListener;
47 import org.openimaj.video.processor.VideoProcessor;
48 import org.openimaj.video.timecode.HrsMinSecFrameTimecode;
49 import org.openimaj.video.timecode.VideoTimecode;
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 public abstract class VideoShotDetector<I extends Image<?,I>>
71 extends VideoProcessor<I>
72 implements VideoDisplayListener<I>
73 {
74
75 private VideoKeyframe<I> currentKeyframe = null;
76
77
78 private final List<ShotBoundary<I>> shotBoundaries =
79 new ArrayList<ShotBoundary<I>>();
80
81
82 private final TDoubleArrayList differentials = new TDoubleArrayList();
83
84
85 private int frameCounter = 0;
86
87
88 private Video<I> video = null;
89
90
91 private boolean findKeyframes = true;
92
93
94 private boolean storeAllDiffs = false;
95
96
97 private boolean needFire = false;
98
99
100 protected boolean lastFrameWasBoundary = false;
101
102
103 private final List<ShotDetectedListener<I>> listeners = new ArrayList<ShotDetectedListener<I>>();
104
105
106 private double fps = 25;
107
108
109 private boolean firstFrame = true;
110
111
112 private final boolean generateStartShot = true;
113
114
115 protected double threshold = 100;
116
117
118
119
120
121
122
123 public VideoShotDetector()
124 {
125 }
126
127
128
129
130
131
132
133
134
135
136
137 public VideoShotDetector( final double fps )
138 {
139 this.fps = fps;
140 }
141
142
143
144
145
146
147 public VideoShotDetector( final Video<I> video )
148 {
149 this( video, false );
150 }
151
152
153
154
155
156
157
158
159 public VideoShotDetector( final Video<I> video, final boolean display )
160 {
161 this.video = video;
162 this.fps = video.getFPS();
163 if( display )
164 {
165 try
166 {
167 final VideoDisplay<I> vd = VideoDisplay.createVideoDisplay( video );
168 vd.addVideoListener( this );
169 vd.setEndAction( EndAction.STOP_AT_END );
170 }
171 catch( final HeadlessException e )
172 {
173 e.printStackTrace();
174 }
175 }
176 }
177
178
179
180
181
182
183 public boolean wasLastFrameBoundary()
184 {
185 return this.lastFrameWasBoundary;
186 }
187
188
189
190
191 @Override
192 public void process()
193 {
194 super.process( this.video );
195 }
196
197
198
199
200
201 @Override
202 public void afterUpdate( final VideoDisplay<I> display )
203 {
204 }
205
206
207
208
209
210 @Override
211 public void beforeUpdate( final I frame )
212 {
213 this.checkForShotBoundary( frame );
214 }
215
216
217
218
219
220
221
222 public void addShotDetectedListener( final ShotDetectedListener<I> sdl )
223 {
224 this.listeners.add( sdl );
225 }
226
227
228
229
230
231
232 public void removeShotDetectedListener( final ShotDetectedListener<I> sdl )
233 {
234 this.listeners.remove( sdl );
235 }
236
237
238
239
240
241 public ShotBoundary<I> getLastShotBoundary()
242 {
243 if( this.shotBoundaries.size() == 0 )
244 return null;
245 return this.shotBoundaries.get( this.shotBoundaries.size()-1 );
246 }
247
248
249
250
251
252 public VideoKeyframe<I> getLastKeyframe()
253 {
254 return this.currentKeyframe;
255 }
256
257
258
259
260
261
262
263
264 private void checkForShotBoundary( final I frame )
265 {
266 this.lastFrameWasBoundary = false;
267 final double dist = this.getInterframeDistance( frame );
268
269 if( this.storeAllDiffs )
270 {
271 this.differentials.add( dist );
272 this.fireDifferentialCalculated( new HrsMinSecFrameTimecode(
273 this.frameCounter, this.video.getFPS() ), dist, frame );
274 }
275
276
277
278
279
280 if( dist > this.threshold || (this.generateStartShot && this.firstFrame) )
281 {
282 this.needFire = true;
283
284
285 final VideoTimecode tc = new HrsMinSecFrameTimecode(
286 this.frameCounter, this.fps );
287
288
289 final ShotBoundary<I> sb = this.getLastShotBoundary();
290
291
292 if( sb != null &&
293 tc.getFrameNumber() - sb.getTimecode().getFrameNumber() < 4 )
294 {
295
296
297
298 if( sb instanceof FadeShotBoundary )
299 ((FadeShotBoundary<I>)sb).setEndTimecode( tc );
300 else
301 {
302
303 this.shotBoundaries.remove( sb );
304
305
306 final FadeShotBoundary<I> fsb = new FadeShotBoundary<I>( sb );
307 fsb.setEndTimecode( tc );
308
309 this.lastFrameWasBoundary = true;
310
311 if( this.findKeyframes )
312 {
313 if( this.currentKeyframe == null )
314 this.currentKeyframe = new VideoKeyframe<I>( tc, frame );
315 else
316 {
317 this.currentKeyframe.timecode = tc;
318 this.currentKeyframe.imageAtBoundary = frame.clone();
319 }
320 fsb.keyframe = this.currentKeyframe.clone();
321 }
322
323 this.shotBoundaries.add( fsb );
324 }
325 }
326 else
327 {
328
329 final ShotBoundary<I> sb2 = new ShotBoundary<I>( tc );
330
331 if( this.findKeyframes )
332 {
333 if( this.currentKeyframe == null )
334 this.currentKeyframe = new VideoKeyframe<I>( tc, frame );
335 else
336 {
337 this.currentKeyframe.timecode = tc;
338 this.currentKeyframe.imageAtBoundary = frame;
339 }
340 sb2.keyframe = this.currentKeyframe.clone();
341 }
342
343 this.lastFrameWasBoundary = true;
344 this.shotBoundaries.add( sb2 );
345 this.fireShotDetected( sb2, this.currentKeyframe );
346 }
347 }
348 else
349 {
350
351
352
353
354 if( this.frameCounter > 0 && this.needFire )
355 {
356 this.needFire = false;
357
358 final VideoTimecode tc = new HrsMinSecFrameTimecode(
359 this.frameCounter-1, this.fps );
360
361 final ShotBoundary<I> lastShot = this.getLastShotBoundary();
362
363 if( lastShot != null && lastShot instanceof FadeShotBoundary )
364 if( ((FadeShotBoundary<I>)lastShot).getEndTimecode().equals( tc ) )
365 this.fireShotDetected( lastShot, this.getLastKeyframe() );
366 }
367 }
368
369 this.frameCounter++;
370 this.firstFrame = false;
371 }
372
373
374
375
376
377 protected abstract double getInterframeDistance( I thisFrame );
378
379
380
381
382
383 public List<ShotBoundary<I>> getShotBoundaries()
384 {
385 return this.shotBoundaries;
386 }
387
388
389
390
391
392
393 public void setThreshold( final double threshold )
394 {
395 this.threshold = threshold;
396 }
397
398
399
400
401
402 public double getThreshold()
403 {
404 return this.threshold;
405 }
406
407
408
409
410
411
412
413 public void setFindKeyframes( final boolean k )
414 {
415 this.findKeyframes = k;
416 }
417
418
419
420
421
422
423
424 public void setStoreAllDifferentials( final boolean d )
425 {
426 this.storeAllDiffs = d;
427 }
428
429
430
431
432
433 public DoubleFV getDifferentials()
434 {
435 return new DoubleFV( this.differentials.toArray() );
436 }
437
438
439
440
441
442 @Override
443 public I processFrame( final I frame )
444 {
445 if( frame == null ) return null;
446 this.checkForShotBoundary( frame );
447 return frame;
448 }
449
450
451
452
453
454
455 protected void fireShotDetected( final ShotBoundary<I> sb, final VideoKeyframe<I> vk )
456 {
457 for( final ShotDetectedListener<I> sdl : this.listeners )
458 sdl.shotDetected( sb, vk );
459 }
460
461
462
463
464
465
466
467 protected void fireDifferentialCalculated( final VideoTimecode vt, final double d, final I frame )
468 {
469 for( final ShotDetectedListener<I> sdl : this.listeners )
470 sdl.differentialCalculated( vt, d, frame );
471 }
472
473
474
475
476
477 @Override
478 public void reset()
479 {
480 }
481
482
483
484
485
486 public void setFPS( final double fps )
487 {
488 this.fps = fps;
489 }
490 }