Skip to content

Commit e8fede7

Browse files
committed
[detectors] Shift range of histogram threshold to match other detectors
1 parent ecb462c commit e8fede7

File tree

8 files changed

+213
-100
lines changed

8 files changed

+213
-100
lines changed

docs/cli.rst

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,98 @@ Options
271271
Minimum length of any scene. Overrides global option :option:`-m/--min-scene-len <scenedetect -m>`. TIMECODE can be specified in frames (:option:`-m=100 <-m>`), in seconds with `s` suffix (:option:`-m=3.5s <-m>`), or timecode (:option:`-m=00:01:52.778 <-m>`).
272272

273273

274+
.. _command-detect-hash:
275+
276+
.. program:: scenedetect detect-hash
277+
278+
279+
``detect-hash``
280+
========================================================================
281+
282+
Find fast cuts using perceptual hashing.
283+
284+
The perceptual hash is taken of adjacent frames, and used to calculate the hamming distance between them. The distance is then normalized by the squared size of the hash, and compared to the threshold.
285+
286+
Saved as the `hash_dist` metric in a statsfile.
287+
288+
289+
Examples
290+
------------------------------------------------------------------------
291+
292+
``scenedetect -i video.mp4 detect-hash``
293+
294+
``scenedetect -i video.mp4 detect-hash --size 32 --lowpass 3``
295+
296+
297+
Options
298+
------------------------------------------------------------------------
299+
300+
.. option:: -t VAL, --threshold VAL
301+
302+
Max distance between hash values (0.0 to 1.0) of adjacent frames. Lower values are more sensitive to changes.
303+
304+
Default: ``0.395``
305+
306+
.. option:: -s SIZE, --size SIZE
307+
308+
Size of square of low frequency data to include from the discrete cosine transform.
309+
310+
Default: ``16``
311+
312+
.. option:: -l FRAC, --lowpass FRAC
313+
314+
How much high frequency information to filter from the DCT. 2 means keep lower 1/2 of the frequency data, 4 means only keep 1/4, etc...
315+
316+
Default: ``2``
317+
318+
.. option:: -m TIMECODE, --min-scene-len TIMECODE
319+
320+
Minimum length of any scene. Overrides global option :option:`-m/--min-scene-len <scenedetect -m>`. TIMECODE can be specified in frames (:option:`-m=100 <-m>`), in seconds with `s` suffix (:option:`-m=3.5s <-m>`), or timecode (:option:`-m=00:01:52.778 <-m>`).
321+
322+
323+
.. _command-detect-hist:
324+
325+
.. program:: scenedetect detect-hist
326+
327+
328+
``detect-hist``
329+
========================================================================
330+
331+
Find fast cuts by differencing YUV histograms.
332+
333+
Uses Y channel after converting each frame to YUV to create a histogram of each frame. Histograms between frames are compared to determine a score for how similar they are.
334+
335+
Saved as the `hist_diff` metric in a statsfile.
336+
337+
338+
Examples
339+
------------------------------------------------------------------------
340+
341+
``scenedetect -i video.mp4 detect-hist``
342+
343+
``scenedetect -i video.mp4 detect-hist --threshold 0.1 --bins 240``
344+
345+
346+
Options
347+
------------------------------------------------------------------------
348+
349+
.. option:: -t VAL, --threshold VAL
350+
351+
Max difference (0.0 to 1.0) between histograms of adjacent frames. Lower values are more sensitive to changes.
352+
353+
Default: ``0.05``
354+
355+
.. option:: -b NUM, --bins NUM
356+
357+
The number of bins to use for the histogram calculation
358+
359+
Default: ``16``
360+
361+
.. option:: -m TIMECODE, --min-scene-len TIMECODE
362+
363+
Minimum length of any scene. Overrides global option :option:`-m/--min-scene-len <scenedetect -m>`. TIMECODE can be specified in frames (:option:`-m=100 <-m>`), in seconds with `s` suffix (:option:`-m=3.5s <-m>`), or timecode (:option:`-m=00:01:52.778 <-m>`).
364+
365+
274366
.. _command-detect-threshold:
275367

276368
.. program:: scenedetect detect-threshold

scenedetect/_cli/__init__.py

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ def time_command(
452452
type=click.FloatRange(CONFIG_MAP["detect-content"]["threshold"].min_val,
453453
CONFIG_MAP["detect-content"]["threshold"].max_val),
454454
default=None,
455-
help="Threshold (float) that frame score must exceed to trigger a cut. Refers to \"content_val\" in stats file.%s"
455+
help="The max difference (0.0 to 255.0) that adjacent frames score must exceed to trigger a cut. Lower values are more sensitive to shot changes. Refers to \"content_val\" in stats file.%s"
456456
% (USER_CONFIG.get_help_string("detect-content", "threshold")),
457457
)
458458
@click.option(
@@ -723,56 +723,58 @@ def detect_threshold_command(
723723
ctx.obj.add_detector(ThresholdDetector(**detector_args))
724724

725725

726-
@click.command('detect-hist', cls=_Command)
726+
@click.command("detect-hist", cls=_Command)
727727
@click.option(
728-
'--threshold',
729-
'-t',
730-
metavar='VAL',
731-
type=click.FloatRange(CONFIG_MAP['detect-hist']['threshold'].min_val,
732-
CONFIG_MAP['detect-hist']['threshold'].max_val),
728+
"--threshold",
729+
"-t",
730+
metavar="VAL",
731+
type=click.FloatRange(CONFIG_MAP["detect-hist"]["threshold"].min_val,
732+
CONFIG_MAP["detect-hist"]["threshold"].max_val),
733733
default=None,
734-
help='Threshold value (float) that the YCbCr histogram difference must exceed to trigger'
735-
' a new scene. Refer to frame metric hist_diff in stats file.%s' %
736-
(USER_CONFIG.get_help_string('detect-hist', 'threshold')))
737-
@click.option(
738-
'--bins',
739-
'-b',
740-
metavar='NUM',
741-
type=click.INT,
734+
help="Max difference (0.0 to 1.0) between histograms of adjacent frames. Lower "
735+
"values are more sensitive to changes.%s" %
736+
(USER_CONFIG.get_help_string("detect-hist", "threshold")))
737+
@click.option(
738+
"--bins",
739+
"-b",
740+
metavar="NUM",
741+
type=click.IntRange(CONFIG_MAP["detect-hist"]["bins"].min_val,
742+
CONFIG_MAP["detect-hist"]["bins"].max_val),
742743
default=None,
743-
help='The number of bins to use for the histogram calculation.%s' %
744+
help="The number of bins to use for the histogram calculation.%s" %
744745
(USER_CONFIG.get_help_string("detect-hist", "bins")))
745746
@click.option(
746-
'--min-scene-len',
747-
'-m',
748-
metavar='TIMECODE',
747+
"--min-scene-len",
748+
"-m",
749+
metavar="TIMECODE",
749750
type=click.STRING,
750751
default=None,
751-
help='Minimum length of any scene. Overrides global min-scene-len (-m) setting.'
752-
' TIMECODE can be specified as exact number of frames, a time in seconds followed by s,'
753-
' or a timecode in the format HH:MM:SS or HH:MM:SS.nnn.%s' %
754-
('' if USER_CONFIG.is_default('detect-hist', 'min-scene-len') else USER_CONFIG.get_help_string(
755-
'detect-hist', 'min-scene-len')))
752+
help="Minimum length of any scene. Overrides global min-scene-len (-m) setting."
753+
" TIMECODE can be specified as exact number of frames, a time in seconds followed by s,"
754+
" or a timecode in the format HH:MM:SS or HH:MM:SS.nnn.%s" %
755+
("" if USER_CONFIG.is_default("detect-hist", "min-scene-len") else USER_CONFIG.get_help_string(
756+
"detect-hist", "min-scene-len")))
756757
@click.pass_context
757758
def detect_hist_command(ctx: click.Context, threshold: Optional[float], bins: Optional[int],
758759
min_scene_len: Optional[str]):
759760
"""Find fast cuts by differencing YUV histograms.
760761
761-
Uses Y channel after converting each frame to YUV to create a histogram of each frame.
762-
Histograms between frames are compared to determine a score for how similar they are.
762+
Uses Y channel after converting each frame to YUV to create a histogram of each frame. Histograms between frames are compared to determine a score for how similar they are.
763763
764-
Examples:
764+
Saved as the `hist_diff` metric in a statsfile.
765765
766-
detect-hist
766+
Examples:
767767
768-
detect-hist --threshold 0.8 --bins 128
768+
{scenedetect_with_video} detect-hist
769+
770+
{scenedetect_with_video} detect-hist --threshold 0.8 --size 64 --lowpass 3
769771
"""
770772
assert isinstance(ctx.obj, CliContext)
771773

772774
assert isinstance(ctx.obj, CliContext)
773775
detector_args = ctx.obj.get_detect_hist_params(
774776
threshold=threshold, bins=bins, min_scene_len=min_scene_len)
775-
logger.debug('Adding detector: HistogramDetector(%s)', detector_args)
777+
logger.debug("Adding detector: HistogramDetector(%s)", detector_args)
776778
ctx.obj.add_detector(HistogramDetector(**detector_args))
777779

778780

@@ -784,21 +786,24 @@ def detect_hist_command(ctx: click.Context, threshold: Optional[float], bins: Op
784786
type=click.FloatRange(CONFIG_MAP["detect-hash"]["threshold"].min_val,
785787
CONFIG_MAP["detect-hash"]["threshold"].max_val),
786788
default=None,
787-
help=("Represents maximum difference between hash values before a cut is triggered.%s" %
789+
help=("Max distance between hash values (0.0 to 1.0) of adjacent frames. Lower values are "
790+
"more sensitive to changes.%s" %
788791
(USER_CONFIG.get_help_string("detect-hash", "threshold"))))
789792
@click.option(
790793
"--size",
791794
"-s",
792795
metavar="SIZE",
793-
type=click.INT,
796+
type=click.IntRange(CONFIG_MAP["detect-hash"]["size"].min_val,
797+
CONFIG_MAP["detect-hash"]["size"].max_val),
794798
default=None,
795799
help="Size of square of low frequency data to include from the discrete cosine transform.%s" %
796800
(USER_CONFIG.get_help_string("detect-hash", "size")))
797801
@click.option(
798802
"--lowpass",
799803
"-h",
800804
metavar="FRAC",
801-
type=click.INT,
805+
type=click.IntRange(CONFIG_MAP["detect-hash"]["lowpass"].min_val,
806+
CONFIG_MAP["detect-hash"]["lowpass"].max_val),
802807
default=None,
803808
help=("How much high frequency information to filter from the DCT. 2 means keep lower 1/2 of "
804809
"the frequency data, 4 means only keep 1/4, etc....%s" %
@@ -819,11 +824,15 @@ def detect_hash_command(ctx: click.Context, threshold: Optional[float], size: Op
819824
lowpass: Optional[int], min_scene_len: Optional[str]):
820825
"""Find fast cuts using perceptual hashing.
821826
822-
Examples:
827+
The perceptual hash is taken of adjacent frames, and used to calculate the hamming distance between them. The distance is then normalized by the squared size of the hash, and compared to the threshold.
828+
829+
Saved as the `hash_dist` metric in a statsfile.
830+
831+
Examples:
823832
824-
detect-hist
833+
{scenedetect_with_video} detect-hash
825834
826-
detect-hist --threshold 0.8 --bins 128
835+
{scenedetect_with_video} detect-hash --size 32 --lowpass 3
827836
"""
828837
assert isinstance(ctx.obj, CliContext)
829838

scenedetect/_cli/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,11 @@ def format(self, timecode: FrameTimecode) -> str:
275275
"min-scene-len": TimecodeValue(0),
276276
"lowpass": RangeValue(2, min_val=1, max_val=256),
277277
"size": RangeValue(16, min_val=1, max_val=256),
278-
"threshold": RangeValue(0.395, min_val=0.0, max_val=1000000000.0),
278+
"threshold": RangeValue(0.395, min_val=0.0, max_val=1.0),
279279
},
280280
"detect-hist": {
281281
"min-scene-len": TimecodeValue(0),
282-
"threshold": RangeValue(0.95, min_val=0.0, max_val=1.0),
282+
"threshold": RangeValue(0.05, min_val=0.0, max_val=1.0),
283283
"bins": RangeValue(256, min_val=1, max_val=256),
284284
},
285285
"detect-threshold": {

scenedetect/detectors/__init__.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@
1515
This module contains the following scene detection algorithms:
1616
1717
* :mod:`ContentDetector <scenedetect.detectors.content_detector>`:
18-
Detects shot changes by considering pixel changes in the HSV colorspace.
18+
Detects shot changes using weighted average of pixel changes in the HSV colorspace.
1919
2020
* :mod:`ThresholdDetector <scenedetect.detectors.threshold_detector>`:
21-
Detects transitions below a set pixel intensity (cuts or fades to black).
21+
Detects slow transitions using average pixel intensity in RGB (fade in/fade out)
2222
2323
* :mod:`AdaptiveDetector <scenedetect.detectors.adaptive_detector>`:
24-
Two-pass version of `ContentDetector` that handles fast camera movement better in some cases.
24+
Performs rolling average on differences in HSV colorspace. In some cases, this can improve
25+
handling of fast motion.
26+
27+
* :mod:`HistogramDetector <scenedetect.detectors.histogram_detector>`:
28+
Uses histogram differences for Y channel in YUV space to find fast cuts.
29+
30+
* :mod:`HashDetector <scenedetect.detectors.hash_detector>`:
31+
Uses perceptual hashing to calculate similarity between adjacent frames.
2532
2633
Detection algorithms are created by implementing the
2734
:class:`SceneDetector <scenedetect.scene_detector.SceneDetector>` interface. Detectors are

0 commit comments

Comments
 (0)