29.97/59.94 fps 에서 ContentDetector 가 min_scene_len 만큼 떨어진 컷을 버리는 0.7 회귀
PySceneDetect: 29.97/59.94 fps 영상에서 ContentDetector 가 min_scene_len 프레임만큼 떨어진 컷을 버린다
링크: https://github.com/Breakthrough/PySceneDetect/issues/577
- 리포: Breakthrough/PySceneDetect (스타 약 5.2k, 기본 브랜치
main, HEAD81c414c, 버전 0.7.1) - 유형: Issue (버그 리포트, "Python API" 템플릿). 스타 1만 미만이라 PR 은 준비하지 않았고 클론에 코드 수정도 없다.
배경
worker-prep-vision 은 방송 영상(29.97/59.94 fps)을 ContentDetector(threshold, min_scene_len=round(min_sec * fps)) 로 샷 분할한다. prep_min_sec=1.0 이면 min_scene_len 은 30(29.97 fps) 또는 60(59.94 fps) 프레임이다.
0.7 부터 이 값들에서 "직전 컷으로부터 정확히 min_scene_len 프레임 뒤"에 있는 컷이 버려진다. API 기본값 15 도 같은 경우에 해당한다. 0.6.7 에서는 정상이었다.
원인
scenedetect/detector.py 의 FlashFilter (179-181행, 194-196행).
- 0.7 의 VFR 지원 작업(#540)에서
FlashFilter가 프레임 수 대신 시간(초)으로 비교하도록 바뀌었다. - 정수 프레임 길이는
self._filter_length / float(frame_rate)로 초로 바꾼 뒤, 경과 시간(timecode - self._last_above)과 float 로 비교한다. - NTSC 계열은
float(frame_rate)가 정확하지 않아 몫이 참값보다 1 ulp 크게 나올 수 있다. 30000/1001 fps 에서 15 프레임은 경과 시간이0.5005, 기준값이0.5005000000000001이라>=가 False 가 된다. - 정확한 PTS(PyAV) 기준으로 N = 3, 6, 12, 15, 24, 25, 30, 39, 48, 50, 59, 60 이 항상 해당한다 (N 60 이하에서 확인, 29.97 과 59.94 동일).
- OpenCV 백엔드는 위치를 마이크로초로 반올림하기 때문에, 프레임 길이가 정수 마이크로초가 아닌 프레임률(30, 23.976, 29.97, 59.94)에서 N 이 3의 배수가 아니면 시작 위치에 따라 1 µs 모자라 같은 현상이 난다. 예: 29.97 fps,
min_scene_len=17에서 OpenCV 는 17 프레임 뒤 컷을 버리고 PyAV 는 유지한다. HistogramDetector,HashDetector,AdaptiveDetector,ThresholdDetector는FrameTimecode >= int로 프레임 번호끼리 비교해서 영향이 없다.
재현 방법
영상 없이 재현된다 (repro_min.py ). 30000/1001 fps 스트림에서 PyAV 백엔드가 돌려주는 위치를 그대로 만들어 FlashFilter(length=15) 에 15 프레임마다 컷 신호를 넣는다.
gap.frame_num : 15
gap.seconds : 0.5005
15 / float(fps) : 0.5005000000000001
gap >= 15 / float(fps) : False <- what FlashFilter evaluates
gap >= 15 : True <- what the other detectors evaluate
MERGE cuts every 15 frames -> []
SUPPRESS cuts every 15 frames -> [30, 60, 90, 120, 150, 180, 210, 240, 270]
기대값은 두 모드 모두 15, 30, 45, ..., 285 이고, 0.6.7 의 FlashFilter 는 그렇게 돌려준다.
실제 파일로도 확인했다 (repro.py ). ffmpeg 로 샷 길이가 정확히 15 프레임인 150 프레임 클립을 다섯 가지 프레임률로 만들고 min_scene_len=15 로 검출했다.
| 프레임률 | ContentDetector (HEAD) | HistogramDetector / AdaptiveDetector (HEAD) | ContentDetector (0.6.7) |
|---|---|---|---|
| 25, 30, 24000/1001 | 9컷 | 9컷 | 9컷 |
| 30000/1001 | 0컷 | 9컷 | 9컷 |
| 60000/1001 | 0컷 | 9컷 | 9컷 |
HEAD 는 OpenCV, PyAV 두 백엔드 결과가 같다. PyPI 의 0.7 과 0.6.7 은 OpenCV 백엔드로만 돌렸고, 0.7 은 HEAD 와 같은 결과다. 0.6.7 의 HistogramDetector 는 첫 컷(15)을 내지 않아 8컷인데 이번 건과 무관하다.
검증
- HEAD
81c414c(editable 설치), PyPIscenedetect==0.7,scenedetect==0.6.7세 환경에서 같은 스크립트를 돌렸다. 원문 출력은repro.txt. repro_timing.py로 프레임률·백엔드별로 영향받는 N 목록을 뽑았다.- 돌리지 않은 것: 리포의 pytest 전체(테스트 영상 리소스 필요),
TransNetV2Detector(FlashFilter를 쓰므로 같은 동작일 것으로 보이나 실행하지 않음). - 수정 방향은 본문에 한 문단으로만 적었다 (정수 길이는
Fraction으로 정확히 계산하거나 프레임 수로 비교, OpenCV 의 마이크로초 반올림에는 프레임 미만의 허용 오차). #540 이 의도적으로 시간 단위로 바꾼 부분이라 메인테이너 판단이 필요하다.
중복 확인
- #540 (머지):
FlashFilter를 시간 단위로 바꾼 PR. 이번 건은 그때 들어간 float 변환 문제. - #408, #571 (머지), #557 (닫힘):
AdaptiveDetector가 현재 프레임 기준으로min_scene_len을 검사하던 문제. 다른 검출기, 다른 원인. - #569 (열림): VFR 에서
frame_num이 근사값인 문제. 이번 건은 CFR. - #477 (열림): CLI 기본값 0.6s 와 API 기본값 15 프레임의 차이에 대한 질문.
- #539 (열림): 0.6.7 에서 마지막 장면이 1 프레임인 문제.
- 검색어: min_scene_len, min-scene-len, FlashFilter, filter_secs, floating point, off-by-one, 29.97, NTSC, 30000/1001, time-based units, ContentDetector regression, missing cuts, fewer scenes 0.7