├── README.md ├── identical.png ├── hash-collision-1.png ├── identical-prediff.png ├── hash-collision-1-closeup.png ├── .gitignore └── analysis.py /README.md: -------------------------------------------------------------------------------- 1 | # video-loop-detection 2 | detecting loops in a video using python 3 | -------------------------------------------------------------------------------- /identical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunnybala/video-loop-detection/HEAD/identical.png -------------------------------------------------------------------------------- /hash-collision-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunnybala/video-loop-detection/HEAD/hash-collision-1.png -------------------------------------------------------------------------------- /identical-prediff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunnybala/video-loop-detection/HEAD/identical-prediff.png -------------------------------------------------------------------------------- /hash-collision-1-closeup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunnybala/video-loop-detection/HEAD/hash-collision-1-closeup.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Video Files 10 | *.mp4 11 | temp/ 12 | *.p 13 | *.png 14 | *.DS_Store 15 | *.xlsx 16 | 17 | # Distribution / packaging 18 | .Python 19 | env/ 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | .hypothesis/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # dotenv 91 | .env 92 | 93 | # virtualenv 94 | .venv 95 | venv/ 96 | ENV/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | .spyproject 101 | 102 | # Rope project settings 103 | .ropeproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | -------------------------------------------------------------------------------- /analysis.py: -------------------------------------------------------------------------------- 1 | import pylab 2 | import imageio 3 | import matplotlib.pyplot as plt 4 | import matplotlib.image as mpimg 5 | import numpy as np 6 | import time 7 | import pdb 8 | import collections 9 | import pandas as pd 10 | import imageio 11 | from PIL import Image 12 | 13 | filename = 'video.mp4' 14 | vid = imageio.get_reader(filename, 'ffmpeg') 15 | 16 | def show_frame(frame): 17 | image = vid.get_data(frame) 18 | fig = pylab.figure() 19 | fig.suptitle('frame #{}'.format(frame), fontsize=20) 20 | pylab.imshow(image) 21 | pylab.show() 22 | 23 | def compare_frames(frame1, frame2): 24 | fig = plt.figure() 25 | a=fig.add_subplot(1,2,1) 26 | img = vid.get_data(frame1) 27 | imgplot = plt.imshow(img) 28 | a.set_title('Frame #%s' % frame1) 29 | a=fig.add_subplot(1,2,2) 30 | img = vid.get_data(frame2) 31 | imgplot = plt.imshow(img) 32 | a.set_title('Frame #%s' % frame2) 33 | plt.show() 34 | 35 | def diff_frames(frame1, frame2): 36 | f1 = vid.get_data(frame1) 37 | f2 = vid.get_data(frame2) 38 | return f1 - f2 39 | 40 | def find_duplicates(res=32): 41 | # load in the video file 42 | filename = 'video.mp4' 43 | vid = imageio.get_reader(filename, 'ffmpeg') 44 | all_frames = vid.get_length() 45 | 46 | # we'll store the info on repeated frames here 47 | seen_frames = {} 48 | duplicate_frames = {} 49 | 50 | for x in range(all_frames): 51 | # get frame x 52 | frame = vid.get_data(x) 53 | 54 | if x % 1000 == 0: 55 | print("frame count: ",x,"\t",round(x*1.0/all_frames,3)*100,'%') 56 | 57 | # hash our frame 58 | hashed = ahash(frame, res) 59 | 60 | if seen_frames.get( hashed, None): 61 | # if we've seen this frame before, add it to the list of frames 62 | # that all have the same hashed value in duplicate_frames 63 | duplicate_frames[hashed].append(x) 64 | else: 65 | # if it's the first time seeing a frame, put it in seen_frames 66 | seen_frames[hashed] = x 67 | duplicate_frames[hashed] = [x] 68 | 69 | # return a list of lists of duplicate frames 70 | return [duplicate_frames[x] for x in duplicate_frames if len(duplicate_frames[x]) > 1] 71 | 72 | def ahash(frame, res = 64): 73 | i = Image.fromarray(frame) 74 | i = i.resize((res,res), Image.ANTIALIAS).convert('L') 75 | pixels = list(i.getdata()) 76 | avg = sum(pixels)/len(pixels) 77 | bits = "".join(map(lambda pixel: '1' if pixel < avg else '0', pixels)) 78 | hexadecimal = int(bits, 2).__format__('016x').upper() 79 | return hexadecimal 80 | 81 | def find_params(): 82 | res = {} 83 | for y in range(8,102,4): 84 | row = {} 85 | bads = find_duplicates(y) 86 | row["len"] = len(bads) 87 | row["avg"] = np.mean([len(x) for x in bads]) 88 | row["std"] = np.std([len(x) for x in bads]) 89 | row["max"] = max([len(x) for x in bads]) 90 | print("*"*25) 91 | print(y,row) 92 | print("*"*25) 93 | res[y] = row.copy() 94 | return res 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | --------------------------------------------------------------------------------