Skip to content

Commit fd854c0

Browse files
committed
Added optimized Part 2 code.
1 parent 555a09d commit fd854c0

File tree

2 files changed

+97
-81
lines changed

2 files changed

+97
-81
lines changed

examples/part1-threshold.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@
88
# algorithm using a set threshold compared to the average pixel intensity
99
# of each frame. Usage:
1010
#
11-
# > python part1-threshold.py [video-file] [intensity = 16]
11+
# > python part1-threshold.py [video-file] [intensity = 15]
1212
#
1313
# Where [video-file] is a path to the video to be parsed, and [intensity]
1414
# is the average pixel intensity from 0 to 255 to be used as a cut-off
15-
# (if unspecified, the default value of 16 is used). Example:
15+
# (if unspecified, the default value of 15 is used). Example:
1616
#
1717
# > python part1-threshold.py testvideo.mp4 8
1818
#
1919
# For each fade/cut that is detected, the timecodes and frame numbers
2020
# are printed to stdout. Note that this program depends on the Python
21-
# OpenCV bindings and NumPy.
22-
#
21+
# OpenCV bindings and NumPy. This software is a proof of concept related
22+
# to the tutorial above, so note many error checks are skipped for brevity.
2323
#
2424
# Copyright (C) 2013-2014 Brandon Castellano <http://www.bcastell.com>.
2525
#
@@ -42,6 +42,7 @@
4242
def main():
4343
if len(sys.argv) < 2:
4444
print "Error - file name must be specified as first argument."
45+
print "See the header of part1-threshold.py for usage details."
4546
return
4647

4748
cap = cv2.VideoCapture()
@@ -60,12 +61,11 @@ def main():
6061
print "Video Resolution: %d x %d" % (width, height)
6162

6263
# Allow the threshold to be passed as an optional, second argument to the script.
63-
threshold = 16
64+
threshold = 15
6465
if len(sys.argv) > 2 and int(sys.argv[2]) > 0:
6566
threshold = int(sys.argv[2])
6667
print "Detecting scenes with threshold = %d.\n" % threshold
6768

68-
6969
last_mean = 0 # Mean intensity of the *last* frame processed.
7070
start_time = cv2.getTickCount() # Used for benchmarking/statistics after loop.
7171

examples/part2-adaptive.py

Lines changed: 91 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,21 @@
1+
#!/usr/bin/env python
12
#
23
# Scene Detection with Python and OpenCV - Example Program
34
# Part 2: Adaptive Fade Detection By: Brandon Castellano
45
#
56
# http://www.bcastell.com/tech-articles/pyscenedetect-tutorial-part-2/
67
#
7-
# This Python program implements a more advanced and optimized threshold-
8-
# based scene detection algorithm, and improves the output format so
9-
# the scene cuts can be more easily imported into other programs (e.g.
10-
# ffmpeg, mkvmerge). Basic usage is:
8+
# This Python program implements an optimized, threshold-based
9+
# scene detection algorithm, and improves the output format so that
10+
# scene cuts can be more easily imported into other programs (e.g.
11+
# ffmpeg, mkvmerge). Usage:
1112
#
12-
# > python part2-adaptive.py [-h] -i VIDEO_FILE
13+
# ./python part2-adaptive.py VIDEO_FILE [intensity = 16]
1314
#
14-
# Where -i denotes the input video, and -h shows the help message.
15-
# Optional arguments that can be passed after VIDEO_FILE are:
16-
#
17-
# -h, --help show this help message and exit
18-
# -i VIDEO_FILE, --input VIDEO_FILE
19-
# [REQUIRED] Path to input video. (default: None)
20-
# -t intensity, --threshold intensity
21-
# 8-bit intensity value, from 0-255, to use as a fade
22-
# in/out detection threshold. (default: 8)
23-
# -m percent, --minpercent percent
24-
# Amount of pixels in a frame, from 0-100%, that must
25-
# fall under [intensity]. (default: 95)
26-
# -b rows, --blocksize rows
27-
# Number of rows in frame to check at once, can be tuned
28-
# for performance. (default: 32)
29-
# -s offset, --startindex offset
30-
# Starting index for chapter/scene output. (default: 0)
31-
#
32-
# Example:
33-
# > python part2-adaptive.py -i testvideo.mp4
34-
# > python part2-adaptive.py -i testvideo.mp4 -t 8 -m 95 -b 32 -s 0
15+
# Which will export a list of scenes and their timecodes (by default)
16+
# detected in VIDEO_FILE, using the intensity as a threshold. Other
17+
# parameters (e.g. minimum match percent and block size) can be
18+
# modified just above the main function in the source below.
3519
#
3620
# For each fade/cut that is detected, the timecodes and frame numbers
3721
# are printed to stdout. Note that this program depends on the Python
@@ -49,65 +33,97 @@
4933
# OTHER DEALINGS IN THE SOFTWARE.
5034
#
5135

52-
5336
import sys
54-
import argparse
55-
5637
import cv2
5738
import numpy as np
5839

5940

60-
def int_type_check(min_val, max_val = None, metavar = None):
61-
if metavar == None: metavar = 'value'
62-
def _type_check(value):
63-
value = int(value)
64-
valid = True
65-
msg = ''
66-
if (max_val == None):
67-
if (value < min_val): valid = False
68-
msg = 'invalid choice: %d (%s must be at least %d)' % (
69-
value, metavar, min_val )
70-
else:
71-
if (value < min_val or value > max_val): valid = False
72-
msg = 'invalid choice: %d (%s must be between %d and %d)' % (
73-
value, metavar, min_val, max_val )
74-
if not valid:
75-
raise argparse.ArgumentTypeError(msg)
76-
return value
77-
return _type_check
78-
79-
80-
def get_cli_parser():
81-
parser = argparse.ArgumentParser(
82-
formatter_class = argparse.ArgumentDefaultsHelpFormatter)
83-
parser._optionals.title = 'arguments'
84-
85-
parser.add_argument('-i', '--input', metavar = 'VIDEO_FILE',
86-
type = file, required = True,
87-
help = '[REQUIRED] Path to input video.')
88-
parser.add_argument('-t', '--threshold', metavar = 'intensity',
89-
type = int_type_check(0, 255, 'intensity'), default = 8,
90-
help = '8-bit intensity value, from 0-255, to use as a fade in/out detection threshold.')
91-
parser.add_argument('-m', '--minpercent', metavar = 'percent',
92-
type = int_type_check(0, 100, 'percentage'), default = 95,
93-
help = 'Amount of pixels in a frame, from 0-100%%, that must fall under [intensity].')
94-
parser.add_argument('-b', '--blocksize', metavar = 'rows',
95-
type = int_type_check(0, None, 'number of rows'), default = 32,
96-
help = 'Number of rows in frame to check at once, can be tuned for performance.')
97-
parser.add_argument('-s', '--startindex', metavar = 'offset',
98-
type = int, default = 0,
99-
help = 'Starting index for chapter/scene output.')
100-
101-
return parser
41+
# Advanced Scene Detection Parameters
42+
INTENSITY_THRESHOLD = 16 # Pixel intensity threshold (0-255), default 16
43+
MINIMUM_PERCENT = 95 # Min. amount of pixels to be below threshold.
44+
BLOCK_SIZE = 32 # Num. of rows to sum per iteration.
10245

10346

10447
def main():
105-
args = get_cli_parser().parse_args()
48+
if len(sys.argv) < 2:
49+
print "Error - file name must be specified as first argument."
50+
return
51+
52+
cap = cv2.VideoCapture()
53+
cap.open(sys.argv[1])
54+
55+
if not cap.isOpened():
56+
print "Fatal error - could not open video %s." % sys.argv[1]
57+
return
58+
else:
59+
print "Parsing video %s..." % sys.argv[1]
60+
61+
# Do stuff with cap here.
10662

107-
print args
63+
width = cap.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)
64+
height = cap.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)
65+
print "Video Resolution: %d x %d" % (width, height)
66+
67+
# Allow the threshold to be passed as an optional, second argument to the script.
68+
threshold = 16
69+
if len(sys.argv) > 2 and int(sys.argv[2]) > 0:
70+
threshold = int(sys.argv[2])
71+
print "Detecting scenes with threshold = %d" % threshold
72+
print "Min. pixels under threshold = %d %%" % MINIMUM_PERCENT
73+
print "Block/row size = %d" % BLOCK_SIZE
74+
print ""
75+
76+
min_percent = MINIMUM_PERCENT / 100.0
77+
num_rows = BLOCK_SIZE
78+
last_amt = 0 # Number of pixel values above threshold in last frame.
79+
start_time = cv2.getTickCount() # Used for statistics after loop.
80+
81+
while True:
82+
# Get next frame from video.
83+
(rv, im) = cap.read()
84+
if not rv: # im is a valid image if and only if rv is true
85+
break
86+
87+
# Compute # of pixel values and minimum amount to trigger fade.
88+
num_pixel_vals = float(im.shape[0] * im.shape[1] * im.shape[2])
89+
min_pixels = int(num_pixel_vals * (1.0 - min_percent))
90+
91+
# Loop through frame block-by-block, updating current sum.
92+
frame_amt = 0
93+
curr_row = 0
94+
while curr_row < im.shape[0]:
95+
# Add # of pixel values in current block above the threshold.
96+
frame_amt += np.sum(
97+
im[curr_row : curr_row + num_rows,:,:] > threshold )
98+
if frame_amt > min_pixels: # We can avoid checking the rest of the
99+
break # frame since we crossed the boundary.
100+
curr_row += num_rows
101+
102+
# Detect fade in from black.
103+
if frame_amt >= min_pixels and last_amt < min_pixels:
104+
print "Detected fade in at %dms (frame %d)." % (
105+
cap.get(cv2.cv.CV_CAP_PROP_POS_MSEC),
106+
cap.get(cv2.cv.CV_CAP_PROP_POS_FRAMES) )
107+
108+
# Detect fade out to black.
109+
elif frame_amt < min_pixels and last_amt >= min_pixels:
110+
print "Detected fade out at %dms (frame %d)." % (
111+
cap.get(cv2.cv.CV_CAP_PROP_POS_MSEC),
112+
cap.get(cv2.cv.CV_CAP_PROP_POS_FRAMES) )
113+
114+
last_amt = frame_amt # Store current mean to compare in next iteration.
115+
116+
# Get # of frames in video based on the position of the last frame we read.
117+
frame_count = cap.get(cv2.cv.CV_CAP_PROP_POS_FRAMES)
118+
# Compute runtime and average framerate
119+
total_runtime = float(cv2.getTickCount() - start_time) / cv2.getTickFrequency()
120+
avg_framerate = float(frame_count) / total_runtime
121+
122+
print "Read %d frames from video in %4.2f seconds (avg. %4.1f FPS)." % (
123+
frame_count, total_runtime, avg_framerate)
124+
125+
cap.release()
108126

109-
pass
110127

111-
112128
if __name__ == "__main__":
113129
main()

0 commit comments

Comments
 (0)