├── .gitignore ├── CIMG4027.MOV ├── Multiple_Object_Tracking ├── Multiple_Object_Tracking │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── Web_App │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20170916_1208.py │ │ ├── 0003_auto_20170916_1300.py │ │ └── __init__.py │ ├── models.py │ ├── static │ │ └── files │ │ │ ├── CIMG4027.MOV │ │ │ ├── hardware.jpg │ │ │ ├── hardware_grey.png │ │ │ ├── small.avi │ │ │ └── small.mp4 │ ├── templates │ │ ├── base.html │ │ ├── main_page.html │ │ ├── movie_form.html │ │ └── movie_info.html │ ├── tests.py │ └── views.py └── manage.py ├── README.md └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python,django,pycharm 3 | 4 | .idea 5 | libfreenect 6 | 7 | ### Django ### 8 | *.log 9 | *.pot 10 | *.pyc 11 | __pycache__/ 12 | local_settings.py 13 | db.sqlite3 14 | media 15 | 16 | # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ 17 | # in your Git repository. Update and uncomment the following line accordingly. 18 | # /staticfiles/ 19 | 20 | ### PyCharm ### 21 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 22 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 23 | 24 | # User-specific stuff: 25 | .idea/**/workspace.xml 26 | .idea/**/tasks.xml 27 | .idea/dictionaries 28 | 29 | # Sensitive or high-churn files: 30 | .idea/**/dataSources/ 31 | .idea/**/dataSources.ids 32 | .idea/**/dataSources.xml 33 | .idea/**/dataSources.local.xml 34 | .idea/**/sqlDataSources.xml 35 | .idea/**/dynamic.xml 36 | .idea/**/uiDesigner.xml 37 | 38 | # Gradle: 39 | .idea/**/gradle.xml 40 | .idea/**/libraries 41 | 42 | # CMake 43 | cmake-build-debug/ 44 | 45 | # Mongo Explorer plugin: 46 | .idea/**/mongoSettings.xml 47 | 48 | ## File-based project format: 49 | *.iws 50 | 51 | ## Plugin-specific files: 52 | 53 | # IntelliJ 54 | /out/ 55 | 56 | # mpeltonen/sbt-idea plugin 57 | .idea_modules/ 58 | 59 | # JIRA plugin 60 | atlassian-ide-plugin.xml 61 | 62 | # Cursive Clojure plugin 63 | .idea/replstate.xml 64 | 65 | # Crashlytics plugin (for Android Studio and IntelliJ) 66 | com_crashlytics_export_strings.xml 67 | crashlytics.properties 68 | crashlytics-build.properties 69 | fabric.properties 70 | 71 | ### PyCharm Patch ### 72 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 73 | 74 | # *.iml 75 | # modules.xml 76 | # .idea/misc.xml 77 | # *.ipr 78 | 79 | # Sonarlint plugin 80 | .idea/sonarlint 81 | 82 | ### Python ### 83 | # Byte-compiled / optimized / DLL files 84 | *.py[cod] 85 | *$py.class 86 | 87 | # C extensions 88 | *.so 89 | 90 | # Distribution / packaging 91 | .Python 92 | env/ 93 | build/ 94 | develop-eggs/ 95 | dist/ 96 | downloads/ 97 | eggs/ 98 | .eggs/ 99 | lib/ 100 | lib64/ 101 | parts/ 102 | sdist/ 103 | var/ 104 | wheels/ 105 | *.egg-info/ 106 | .installed.cfg 107 | *.egg 108 | 109 | # PyInstaller 110 | # Usually these files are written by a python script from a template 111 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 112 | *.manifest 113 | *.spec 114 | 115 | # Installer logs 116 | pip-log.txt 117 | pip-delete-this-directory.txt 118 | 119 | # Unit test / coverage reports 120 | htmlcov/ 121 | .tox/ 122 | .coverage 123 | .coverage.* 124 | .cache 125 | nosetests.xml 126 | coverage.xml 127 | *,cover 128 | .hypothesis/ 129 | 130 | # Translations 131 | *.mo 132 | 133 | # Django stuff: 134 | 135 | # Flask stuff: 136 | instance/ 137 | .webassets-cache 138 | 139 | # Scrapy stuff: 140 | .scrapy 141 | 142 | # Sphinx documentation 143 | docs/_build/ 144 | 145 | # PyBuilder 146 | target/ 147 | 148 | # Jupyter Notebook 149 | .ipynb_checkpoints 150 | 151 | # pyenv 152 | .python-version 153 | 154 | # celery beat schedule file 155 | celerybeat-schedule 156 | 157 | # SageMath parsed files 158 | *.sage.py 159 | 160 | # dotenv 161 | .env 162 | 163 | # virtualenv 164 | .venv 165 | venv/ 166 | ENV/ 167 | 168 | # Spyder project settings 169 | .spyderproject 170 | .spyproject 171 | 172 | # Rope project settings 173 | .ropeproject 174 | 175 | # mkdocs documentation 176 | /site 177 | 178 | # End of https://www.gitignore.io/api/python,django,pycharm 179 | 180 | -------------------------------------------------------------------------------- /CIMG4027.MOV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-tomaszuk/Multiple-Object-Tracking/4b8488a6d6b136075608fbacc23bc78ef41b653c/CIMG4027.MOV -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Multiple_Object_Tracking/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-tomaszuk/Multiple-Object-Tracking/4b8488a6d6b136075608fbacc23bc78ef41b653c/Multiple_Object_Tracking/Multiple_Object_Tracking/__init__.py -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Multiple_Object_Tracking/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for Multiple_Object_Tracking project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'xqya_+datgn!vhdaxhhv%c=$#8i_c(pc!tg85e6tjwz3oaci_j' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'Web_App', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'Multiple_Object_Tracking.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'Multiple_Object_Tracking.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 106 | 107 | LANGUAGE_CODE = 'en-us' 108 | 109 | TIME_ZONE = 'UTC' 110 | 111 | USE_I18N = True 112 | 113 | USE_L10N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 120 | 121 | STATIC_URL = '/static/' 122 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Multiple_Object_Tracking/urls.py: -------------------------------------------------------------------------------- 1 | """Multiple_Object_Tracking URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.contrib import admin 18 | from Web_App.views import * 19 | 20 | urlpatterns = [ 21 | url(r'^admin/', admin.site.urls), 22 | url(r'^$', MainPage.as_view(), name='main-page'), 23 | url(r'^add_movie', AddMovie.as_view(), name='add-movie'), 24 | url(r'^info_movie/(?P(\d)+)', InfoMovie.as_view(), 25 | name='info-movie'), 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ] 34 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Multiple_Object_Tracking/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for Multiple_Object_Tracking project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Multiple_Object_Tracking.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-tomaszuk/Multiple-Object-Tracking/4b8488a6d6b136075608fbacc23bc78ef41b653c/Multiple_Object_Tracking/Web_App/__init__.py -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class WebAppConfig(AppConfig): 5 | name = 'Web_App' 6 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2017-09-16 12:05 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Detection', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('data', models.TextField()), 24 | ('creation_date', models.DateTimeField(auto_now=True)), 25 | ], 26 | ), 27 | migrations.CreateModel( 28 | name='Estimate', 29 | fields=[ 30 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 31 | ('data', models.TextField()), 32 | ('creation_date', models.DateTimeField(auto_now=True)), 33 | ], 34 | ), 35 | migrations.CreateModel( 36 | name='Movie', 37 | fields=[ 38 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 39 | ('name', models.CharField(max_length=128)), 40 | ('file', models.FileField(default='Server_path', upload_to='files/')), 41 | ('creation_date', models.DateTimeField(auto_now=True)), 42 | ('my_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 43 | ], 44 | ), 45 | migrations.AddField( 46 | model_name='estimate', 47 | name='movie', 48 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Web_App.Movie'), 49 | ), 50 | migrations.AddField( 51 | model_name='estimate', 52 | name='user', 53 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 54 | ), 55 | migrations.AddField( 56 | model_name='detection', 57 | name='movie', 58 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Web_App.Movie'), 59 | ), 60 | migrations.AddField( 61 | model_name='detection', 62 | name='user', 63 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 64 | ), 65 | ] 66 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/migrations/0002_auto_20170916_1208.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2017-09-16 12:08 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('Web_App', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='detection', 17 | name='user', 18 | ), 19 | migrations.RemoveField( 20 | model_name='estimate', 21 | name='user', 22 | ), 23 | migrations.RemoveField( 24 | model_name='movie', 25 | name='my_user', 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/migrations/0003_auto_20170916_1300.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2017-09-16 13:00 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('Web_App', '0002_auto_20170916_1208'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='movie', 17 | name='file', 18 | field=models.FileField(upload_to='static/files/'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-tomaszuk/Multiple-Object-Tracking/4b8488a6d6b136075608fbacc23bc78ef41b653c/Multiple_Object_Tracking/Web_App/migrations/__init__.py -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | 5 | class Movie(models.Model): 6 | name = models.CharField(max_length=128) 7 | file = models.FileField(upload_to='static/files/') 8 | creation_date = models.DateTimeField(auto_now=True) 9 | # my_user = models.ForeignKey(User) 10 | # 11 | # @property 12 | # def movie_info(self): 13 | # return "Added: {} by {}".format(self.creation_date 14 | # .strftime("%Y-%m-%d, %H:%M:%S"), 15 | # self.my_user.username) 16 | # 17 | # def __str__(self): 18 | # return "ID:" + str(self.id) + " " + self.movie_info 19 | 20 | 21 | class Detection(models.Model): 22 | # user = models.ForeignKey(User) 23 | movie = models.ForeignKey(Movie) 24 | data = models.TextField() 25 | creation_date = models.DateTimeField(auto_now=True) 26 | 27 | 28 | class Estimate(models.Model): 29 | # user = models.ForeignKey(User) 30 | movie = models.ForeignKey(Movie) 31 | data = models.TextField() 32 | creation_date = models.DateTimeField(auto_now=True) 33 | 34 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/static/files/CIMG4027.MOV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-tomaszuk/Multiple-Object-Tracking/4b8488a6d6b136075608fbacc23bc78ef41b653c/Multiple_Object_Tracking/Web_App/static/files/CIMG4027.MOV -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/static/files/hardware.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-tomaszuk/Multiple-Object-Tracking/4b8488a6d6b136075608fbacc23bc78ef41b653c/Multiple_Object_Tracking/Web_App/static/files/hardware.jpg -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/static/files/hardware_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-tomaszuk/Multiple-Object-Tracking/4b8488a6d6b136075608fbacc23bc78ef41b653c/Multiple_Object_Tracking/Web_App/static/files/hardware_grey.png -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/static/files/small.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-tomaszuk/Multiple-Object-Tracking/4b8488a6d6b136075608fbacc23bc78ef41b653c/Multiple_Object_Tracking/Web_App/static/files/small.avi -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/static/files/small.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-tomaszuk/Multiple-Object-Tracking/4b8488a6d6b136075608fbacc23bc78ef41b653c/Multiple_Object_Tracking/Web_App/static/files/small.mp4 -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block content %} 8 |

content

9 | {% endblock %} 10 | 11 | 12 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/templates/main_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |

Main Page!

4 |

List of videos:

5 |
  • 6 | {% for movie in movie_list %} 7 | 12 | {% endfor %} 13 |
  • 14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/templates/movie_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
    4 | {% csrf_token %} 5 | {{ form.as_p }} 6 | 7 | 8 | 9 | 10 |
    11 | {% endblock %} 12 | 13 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/templates/movie_info.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | {{ movie_info.name }} 4 | {{ movie_info.file }} 5 | {% endblock %} 6 | 7 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/Web_App/views.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun Aug 27 10:26:59 2017 5 | 6 | @author: Daniel 'dghy' Tomaszuk 7 | """ 8 | 9 | # non django imports 10 | import numpy as np 11 | import cv2 12 | import sys 13 | import math 14 | import matplotlib.pyplot as plt 15 | from numpy import ma # masked arrays 16 | from scipy.spatial.distance import pdist 17 | from scipy.spatial.distance import squareform 18 | from scipy.linalg import inv 19 | from numpy import dot 20 | from filterpy.kalman import KalmanFilter 21 | from filterpy.common import Q_discrete_white_noise 22 | from munkres import Munkres, DISALLOWED 23 | from scipy.optimize import linear_sum_assignment 24 | 25 | 26 | # django imports 27 | from .models import * 28 | from django.core.urlresolvers import reverse_lazy 29 | from django.core.exceptions import * 30 | from django.http import HttpResponse 31 | from django.shortcuts import render 32 | from django.views import * 33 | from django.views.generic.edit import * 34 | 35 | 36 | def otsu_binary(img): 37 | """ 38 | Otsu binarization function. 39 | :param img: Image to binarize - should be in greyscale. 40 | :return: Image after binarization. 41 | """ 42 | # check if input image is in grayscale (2D) 43 | try: 44 | if img.shape[2]: 45 | # if there is 3rd dimension 46 | sys.exit('otsu_binary(img) input image should be in grayscale!') 47 | except IndexError: 48 | pass # image doesn't have 3rd dimension - proceed 49 | 50 | # plt.close('all') 51 | blur = cv2.GaussianBlur(img, (5, 5), 0) 52 | # find normalized_histogram, and its cumulative distribution function 53 | hist = cv2.calcHist([blur], [0], None, [256], [0, 256]) 54 | hist_norm = hist.ravel() / hist.max() 55 | Q = hist_norm.cumsum() 56 | bins = np.arange(256) 57 | fn_min = np.inf 58 | thresh = -1 59 | 60 | for i in range(1, 255): 61 | p1, p2 = np.hsplit(hist_norm, [i]) # probabilities 62 | q1 = Q[i] 63 | q2 = Q[255] - q1 # cum sum of classes 64 | b1, b2 = np.hsplit(bins, [i]) # weights 65 | # finding means and variances 66 | m1 = np.sum(p1 * b1) / q1 67 | m2 = np.sum(p2 * b2) / q2 68 | v1, v2 = np.sum(((b1 - m1) ** 2) * p1) / q1, np.sum( 69 | ((b2 - m2) ** 2) * p2) / q2 70 | # calculates the minimization function 71 | fn = v1 * q1 + v2 * q2 72 | if fn < fn_min: 73 | fn_min = fn 74 | thresh = i 75 | # find otsu's threshold value with OpenCV function 76 | ret, otsu = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + 77 | cv2.THRESH_OTSU) 78 | # print("{} {}".format(thresh, ret)) 79 | 80 | ret, img_thresh1 = cv2.threshold(img, thresh, 255, cv2.THRESH_BINARY) 81 | return img_thresh1 82 | 83 | 84 | def select_frames(video, frame_start, frame_stop): 85 | """ 86 | Function that return selected frames from video. 87 | :param video: string, video whom frames are selected, 88 | :param frame_start: integer, frame from selection should be started 89 | :param frame_stop: integer, ending frame of selected section 90 | :return: video fragment 91 | """ 92 | cap = cv2.VideoCapture(video) 93 | # font = cv2.FONT_HERSHEY_SIMPLEX 94 | video_fragment = [] 95 | cap.set(1, frame_start) 96 | 97 | while cap.isOpened(): 98 | ret, frame = cap.read() 99 | video_fragment.append(frame) 100 | if cv2.waitKey(1) & 0xFF == ord('q') or not ret \ 101 | or (cap.get(1)) == frame_stop + 1: 102 | break 103 | # # img, text, (x,y), font, size, color, thickens 104 | # cv2.putText(frame, str(round(cap.get(0)/1000, 2)) + 's', 105 | # (10, 15), font, 0.5, (255, 255, 255), 1) 106 | # cv2.putText(frame, 'f.nr:' + str(cap.get(1)), 107 | # (100, 15), font, 0.5, (255, 255, 255), 1) 108 | # cv2.imshow('frame', frame) 109 | 110 | cap.release() 111 | cv2.destroyAllWindows() 112 | return video_fragment 113 | 114 | 115 | def read_image(path, name, ext, amount): 116 | """ 117 | Function for reading images from folder. Name of images should be: 118 | name_index.extension so function can work automatic. 119 | Indexes should be in order! If they are not, function stops if image 120 | with next index is not found. 121 | Example: image_5.jpg -> read_image('path', 'image_', 'jpg', 50) 122 | :param path: string, path of images to read 123 | :param name: string, name of image without index 124 | :param ext: string, extension of image to read with ".", ex: '.jpg' 125 | :param amount: integer, 126 | :return: selected images as table if image exist or omits the image 127 | if it doesn't exist 128 | """ 129 | images = [] 130 | for i in range(amount): 131 | # try: 132 | print(path + '/' + name + str(i) + ext) 133 | img = cv2.imread(path + '/' + name + str(i) + ext, 1) 134 | # check if image was read 135 | try: 136 | if img.shape[0]: 137 | images.append(img) 138 | except AttributeError: 139 | pass 140 | return images 141 | 142 | 143 | def blob_detect(img_with_blobs): 144 | params = cv2.SimpleBlobDetector_Params() 145 | # # Change thresholds 146 | # params.minThreshold = 10 147 | # params.maxThreshold = 200 148 | 149 | # Filter by Area. 150 | params.filterByArea = True 151 | params.minArea = 5 152 | 153 | # Filter by Circularity 154 | params.filterByCircularity = True 155 | params.minCircularity = 0.0 156 | 157 | # Filter by Convexity 158 | params.filterByConvexity = True 159 | params.minConvexity = 0.0 160 | 161 | # Filter by Inertia 162 | params.filterByInertia = True 163 | params.minInertiaRatio = 0.0 164 | 165 | # Create a detector with the parameters 166 | detector = cv2.SimpleBlobDetector_create(params) 167 | 168 | # get positions of blobs 169 | keypoints = detector.detect(img_with_blobs) 170 | 171 | # Draw detected blobs as red circles. 172 | # cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures the size of the 173 | # circle corresponds to the size of blob 174 | im_with_keypoints = cv2.drawKeypoints(img_with_blobs, keypoints, 175 | np.array([]), (0, 0, 255), 176 | cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) 177 | return im_with_keypoints 178 | 179 | 180 | def get_log_kernel(siz, std): 181 | """ 182 | LoG(x,y) = 183 | (1/(pi*sigma^4)) * (1 - (x^2+y^2)/(sigma^2)) * (e ^ (- (x^2 + y^2) / 2sigma^2) 184 | 185 | :param siz: 186 | :param std: 187 | :return: 188 | """ 189 | x = np.linspace(-siz, siz, 2 * siz + 1) 190 | y = np.linspace(-siz, siz, 2 * siz + 1) 191 | x, y = np.meshgrid(x, y) 192 | arg = -(x ** 2 + y ** 2) / (2 * std ** 2) 193 | h = np.exp(arg) 194 | h[h < sys.float_info.epsilon * h.max()] = 0 195 | h = h / h.sum() if h.sum() != 0 else h 196 | h1 = h * (x ** 2 + y ** 2 - 2 * std ** 2) / (std ** 4) 197 | return h1 - h1.mean() 198 | 199 | 200 | def img_inv(img): 201 | """ 202 | Return inversion of an image. 203 | :param img: Input image. 204 | :return: Inverted image. 205 | """ 206 | return cv2.bitwise_not(img) 207 | 208 | 209 | def local_maxima(gray_image): 210 | """ 211 | Finds local maxima in grayscale image. 212 | source: 213 | https://dsp.stackexchange.com/questions/17932/finding-local- 214 | brightness-maximas-with-opencv 215 | :param gray_image: Input 2D image. 216 | :return: Coordinates of local maxima points. 217 | """ 218 | 219 | square_diameter_log_3 = 3 # 27x27 220 | 221 | total = gray_image 222 | for axis in range(2): 223 | d = 1 224 | for k in range(square_diameter_log_3): 225 | total = np.maximum(total, np.roll(total, d, axis)) 226 | total = np.maximum(total, np.roll(total, -d, axis)) 227 | d *= 3 228 | # if total == gray_iamge, maxima = total 229 | maxima = total == gray_image 230 | h, w = gray_image.shape 231 | result = [] 232 | for j in range(h): 233 | for k in range(w): 234 | # gray_image[j][k] has float values! 235 | if maxima[j][k] and gray_image[j][k] * 255 > 1: 236 | # watch pixel coordinates output! (w, h) 237 | result.append((k, j)) 238 | return result 239 | 240 | 241 | def munkres(matrix): 242 | """ 243 | Implementation of Hungarian algorithm for solving the Assignment Problem 244 | between measurements and estimates in multivariate linear kalman filter 245 | Example of usage: 246 | indexes = munkres(matrix) 247 | :param matrix: input matrix - should be a square cost matrix 248 | :return: index_list of tuples with assigned indexes, 249 | cost_list of assignment between indexes 250 | """ 251 | 252 | # print_matrix(cost_matrix, msg='Cost matrix:') 253 | m = Munkres() 254 | 255 | indexes = m.compute(matrix) 256 | # print_matrix(matrix, msg='Highest profit through this matrix:') 257 | total = 0 258 | index_list = [] 259 | cost_list = [] 260 | for row, column in indexes: 261 | value = matrix[row][column] 262 | cost_list.append(value) 263 | total += value 264 | index_list.append((row, column)) 265 | # print('({}, {}) -> {}'.format(row, column, value)) 266 | # print('total profit={}'.format(total)) 267 | return index_list, cost_list 268 | 269 | 270 | def pair(prior, measurements): 271 | """ 272 | Creates pairs between priors and measurement so each lays as close as 273 | possible to each other. 274 | Example of use: 275 | index = pair((60, 0), [(60, 0), (219, 37), (357, 55), (78, 82), 276 | (301, 103), (202, 109), (376, 110)])) 277 | :param prior: prior state prediction (position) from Kalman filter, tuple 278 | :param measurements: positions from blob detection - measurements (x, y), 279 | list of tuples 280 | :return: optimal pairs between estimate - measurement and cost of 281 | assigement between them 282 | """ 283 | array = [] 284 | array.append([prior[0][0], prior[0][1]]) 285 | for measurement in measurements: 286 | array.append([measurement[0], measurement[1]]) 287 | # count euclidean metric between priors and measurements 288 | metric = pdist(array, metric='euclidean') 289 | square = squareform(metric) 290 | min_index = [] 291 | min_cost = [] 292 | for index in munkres(square): 293 | # do not match to itself (distance = 0) and match only when distance 294 | # is low enough 295 | if square[index] != 0.0 and square[index] < 80: 296 | min_index.append(index) 297 | min_cost.append(square[index]) 298 | # distance between indexes 299 | # print(square[index]) 300 | return min_index, min_cost 301 | 302 | 303 | # list of all VideoCapture methods and attributes 304 | # [print(method) for method in dir(cap) if callable(getattr(cap, method))] 305 | def video_analise(video, start_f, stop_f): 306 | """ 307 | Function that finds objects in the video image. 308 | :param video: path to the video to be analysed 309 | :param start_f: integer, number of frame from with video should be analysed 310 | :param stop_f: integer, number of frame to with video should be analysed 311 | :return: maxima_points - positions of detected objects are returned as 312 | (x, y) tuples in the fallowing format: 313 | maxima_points[frame_nr][index_of_tuple] 314 | vid_fragment - selected fragment of given video in the format: 315 | vid_fragment[frame_nr][3D pixel matrix, BGR] 316 | """ 317 | vid_frag = select_frames(video, start_f, stop_f) 318 | try: 319 | height = vid_frag[0].shape[0] 320 | width = vid_frag[0].shape[1] 321 | except IndexError: 322 | height = 0 323 | width = 0 324 | # kernel for morphological operations 325 | # check cv2.getStructuringElement() doc for more info 326 | kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (19, 19)) 327 | erosion_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) 328 | dilate_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) 329 | 330 | i = 0 331 | bin_frames = [] 332 | # preprocess image loop 333 | for frame in vid_frag: 334 | if cv2.waitKey(15) & 0xFF == ord('q'): 335 | break 336 | gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 337 | for m in range(height): # height 338 | for n in range(width): # width 339 | if n > 385 or m > 160: 340 | gray_frame[m][n] = 120 341 | 342 | # create a CLAHE object (Arguments are optional) 343 | # clahe = cv2.createCLAHE(clipLimit=8.0, tileGridSize=(8, 8)) 344 | # cl1 = clahe.apply(gray_frame) 345 | ret, th1 = cv2.threshold(gray_frame, 60, 255, cv2.THRESH_BINARY) 346 | # frame_thresh1 = otsu_binary(cl1) 347 | bin_frames.append(th1) 348 | if i % 10 == 0: 349 | print(i) 350 | i += 1 351 | i = 0 352 | maxima_points = [] 353 | # gather measurements loop 354 | for frame in bin_frames: 355 | # prepare image - morphological operations 356 | erosion = cv2.erode(frame, erosion_kernel, iterations=1) 357 | opening = cv2.morphologyEx(erosion, cv2.MORPH_OPEN, kernel) 358 | dilate = cv2.dilate(opening, dilate_kernel, iterations=2) 359 | 360 | # create LoG kernel for finding local maximas 361 | log_kernel = get_log_kernel(30, 15) 362 | log_img = cv2.filter2D(dilate, cv2.CV_32F, log_kernel) 363 | # get local maximas of filtered image per frame 364 | maxima_points.append(local_maxima(log_img)) 365 | if i % 10 == 0: 366 | print(i) 367 | i += 1 368 | return maxima_points, vid_frag 369 | 370 | 371 | def kalman(max_points): 372 | """ 373 | Kalman Filter function. Takes measurements from video analyse function 374 | and estimates positions of detected objects. Munkres algorithm is used for 375 | assignments between estimates (states) and measurements. 376 | :param max_points: measurements. 377 | :return: x_est, y_est - estimates of x and y positions in the following 378 | format: x_est[index_of_object][frame] gives x position of object 379 | with index = [index_of_object] in the frame = [frame]. The same 380 | goes with y positions. 381 | """ 382 | index_error = 0 383 | value_error = 0 384 | # step of filter 385 | dt = 1. 386 | R_var = 1 # measurements variance between x-x and y-y 387 | # Q_var = 0.1 # model variance 388 | # state covariance matrix - no initial covariances, variances only 389 | # [10^2 px, 10^2 px, ..] - 390 | P = np.diag([100, 100, 10, 10, 1, 1]) 391 | # state transition matrix for 6 state variables 392 | # (position - velocity - acceleration, 393 | # x, y) 394 | F = np.array([[1, 0, dt, 0, 0.5 * pow(dt, 2), 0], 395 | [0, 1, 0, dt, 0, 0.5 * pow(dt, 2)], 396 | [0, 0, 1, 0, dt, 0], 397 | [0, 0, 0, 1, 0, dt], 398 | [0, 0, 0, 0, 1, 0], 399 | [0, 0, 0, 0, 0, 1]]) 400 | # x and y coordinates only - measurements matrix 401 | H = np.array([[1., 0., 0., 0., 0., 0.], 402 | [0., 1., 0., 0., 0., 0.]]) 403 | # no initial corelation between x and y positions - variances only 404 | R = np.array([[R_var, 0.], [0., R_var]]) # measurement covariance matrix 405 | # Q must be the same shape as P 406 | Q = np.diag([100, 100, 10, 10, 1, 1]) # model covariance matrix 407 | 408 | # create state vectors, max number of states - as much as frames 409 | x = np.zeros((stop_frame, 6)) 410 | # state initialization - initial state is equal to measurements 411 | m = 0 412 | try: 413 | for i in range(len(max_points[0])): 414 | if max_points[0][i][0] > 0 and max_points[0][i][1] > 0: 415 | x[m] = [max_points[0][i][0], max_points[0][i][1], 416 | 0, 0, 0, 0] 417 | m += 1 418 | # required for django runserver tests 419 | except IndexError: 420 | index_error = 1 421 | 422 | est_number = 0 423 | # number of estimates at the start 424 | try: 425 | for point in max_points[::][0]: 426 | if point[0] > 0 and point[1] > 0: 427 | est_number += 1 428 | except IndexError: 429 | index_error = 1 430 | 431 | # history of new objects appearance 432 | new_obj_hist = [[]] 433 | # difference between position of n-th object in m-1 frame and position of 434 | # the same object in m frame 435 | diff_2 = [[]] 436 | # for how many frames given object was detected 437 | frames_detected = [] 438 | # x and y posterior positions (estimates) for drawnings 439 | x_est = [[] for i in range(stop_frame)] 440 | y_est = [[] for i in range(stop_frame)] 441 | 442 | # variable for counting frames where object has no measurement 443 | striked_tracks = np.zeros(stop_frame) 444 | removed_states = [] 445 | new_detection = [] 446 | ff_nr = 0 # frame number 447 | # kalman filter loop 448 | for frame in range(stop_frame): 449 | # measurements in one frame 450 | try: 451 | frame_measurements = max_points[::][frame] 452 | except IndexError: 453 | index_error = 1 454 | 455 | measurements = [] 456 | # make list of lists, not tuples; don't take zeros, assuming it's image 457 | if not index_error: 458 | for meas in frame_measurements: 459 | if meas[0] > 0 and meas[1] > 0: 460 | measurements.append([meas[0], meas[1]]) 461 | # count prior 462 | for i in range(est_number): 463 | x[i][::] = dot(F, x[i][::]) 464 | P = dot(F, P).dot(F.T) + Q 465 | S = dot(H, P).dot(H.T) + R 466 | K = dot(P, H.T).dot(inv(S)) 467 | ###################################################################### 468 | # prepare for update phase -> get (prior - measurement) assignment 469 | posterior_list = [] 470 | for i in range(est_number): 471 | if not np.isnan(x[i][0]) and not np.isnan(x[i][1]): 472 | posterior_list.append(i) 473 | print(i) 474 | print(posterior_list) 475 | 476 | print('state\n', x[0:est_number, 0:2]) 477 | print('\n') 478 | # temp_matrix = np.array(x[0:est_number, 0:2]) 479 | try: 480 | temp_matrix = np.array(x[posterior_list, 0:2]) 481 | temp_matrix = np.append(temp_matrix, measurements, axis=0) 482 | except ValueError: 483 | value_error = 1 484 | 485 | # print(temp_matrix) 486 | distance = pdist(temp_matrix, 'euclidean') # returns vector 487 | 488 | # make square matrix out of vector 489 | distance = squareform(distance) 490 | temp_distance = distance 491 | # remove elements that are repeated - (0-1), (1-0) etc. 492 | # distance = distance[est_number::, 0:est_number] 493 | distance = distance[0:len(posterior_list), len(posterior_list)::] 494 | 495 | # munkres 496 | row_index, column_index = linear_sum_assignment(distance) 497 | final_cost = distance[row_index, column_index].sum() 498 | unit_cost = [] 499 | index = [] 500 | for i in range(len(row_index)): 501 | # index(object, measurement) 502 | index.append([row_index[i], column_index[i]]) 503 | unit_cost.append(distance[row_index[i], column_index[i]]) 504 | 505 | ###################################################################### 506 | # index correction - take past states into account 507 | removed_states.sort() 508 | for removed_index in removed_states: 509 | for i in range(len(index)): 510 | if index[i][0] >= removed_index: 511 | index[i][0] += 1 512 | ###################################################################### 513 | # find object to reject 514 | state_list = [index[i][0] for i in range(len(index))] 515 | reject = np.ones(len(posterior_list)) 516 | i = 0 517 | for post_index in posterior_list: 518 | if post_index not in state_list: 519 | reject[i] = 0 520 | i += 1 521 | # check if distance (residual) isn't to high for assignment 522 | for i in range(len(unit_cost)): 523 | if unit_cost[i] > 20: 524 | print('cost to high, removing', i) 525 | reject[i] = 0 526 | 527 | ##################################################################### 528 | 529 | # update phase 530 | for i in range(len(index)): 531 | # find object that should get measurement next 532 | # count residual y: measurement - state 533 | if index[i][1] >= 0: 534 | y = np.array([measurements[index[i][1]] - 535 | dot(H, x[index[i][0], ::])]) 536 | # posterior 537 | x[index[i][0], ::] = x[index[i][0], ::] + dot(K, y.T).T 538 | # append new positions 539 | # if x[i][0] and x[i][1]: 540 | x_est[index[i][0]].append([x[index[i][0], 0]]) 541 | y_est[index[i][0]].append([x[index[i][0], 1]]) 542 | # posterior state covariance matrix 543 | P = dot(np.identity(6) - dot(K, H), P) 544 | print('posterior\n', x[0:est_number, 0:2]) 545 | ###################################################################### 546 | # find new objects and create new states for them 547 | new_index = [] 548 | measurement_indexes = [] 549 | for i in range(len(index)): 550 | if index[i][1] >= 0.: 551 | # measurements that have assignment 552 | measurement_indexes.append(index[i][1]) 553 | 554 | for i in range(len(measurements)): 555 | if i not in measurement_indexes: 556 | # find measurements that don't have assignments 557 | new_index.append(i) 558 | new_detection.append([measurements[new_index[i]] 559 | for i in range(len(new_index))]) 560 | # for every detections in the last frame 561 | for i in range(len(new_detection[len(new_detection) - 1])): 562 | if new_detection[frame][i] and new_detection[frame][i][0] > 380: 563 | x[est_number, ::] = [new_detection[frame][i][0], 564 | new_detection[frame][i][1], 0, 0, 0, 0] 565 | est_number += 1 566 | print('state added', est_number) 567 | print('new posterior\n', x[0:est_number, 0:2]) 568 | ###################################################################### 569 | # find states without measurements and remove them 570 | no_track_list = [] 571 | for i in range(len(reject)): 572 | if not reject[i]: 573 | no_track_list.append(posterior_list[i]) 574 | # print('no_trk_list', no_track_list) 575 | for track in no_track_list: 576 | if track >= 0: 577 | striked_tracks[track] += 1 578 | print('track/strikes', track, striked_tracks[track]) 579 | for i in range(len(striked_tracks)): 580 | if striked_tracks[i] >= 1: 581 | x[i, ::] = [None, None, None, None, None, None] 582 | if i not in removed_states: 583 | removed_states.append(i) 584 | print('state_removed', i) 585 | 586 | ###################################################################### 587 | if not index_error or not value_error: 588 | # draw measurements point loop 589 | if cv2.waitKey(30) & 0xFF == ord('q'): 590 | break 591 | # img, text, (x,y), font, size, color, thickens 592 | cv2.putText(vid_fragment[frame], 'f.nr:' + str(ff_nr), 593 | (100, 15), font, 0.5, (254, 254, 254), 1) 594 | 595 | # mark local maximas for every frame 596 | measurement_number = 0 597 | for point in measurements: 598 | cv2.circle(vid_fragment[frame], (point[0], point[1]), 5, 599 | (0, 0, 255), 1) 600 | cv2.putText(vid_fragment[frame], str(measurement_number), 601 | (point[0], point[1]), font, 0.5, (0, 0, 254), 1) 602 | measurement_number += 1 603 | 604 | for j in range(len(x)): 605 | if x[j][0] > 0 and x[j][1] > 0: 606 | # positions.append((x_est[i][j], y_est[i][j])) 607 | cv2.circle(vid_fragment[frame], (int(x[j][0]), 608 | int(x[j][1])), 3, 609 | (0, 255, 0), 1) 610 | cv2.putText(vid_fragment[frame], str(j), 611 | (int(x[j][0] + 10), int(x[j][1] + 20)), 612 | font, 0.5, (0, 254, 0), 1) 613 | 614 | cv2.imshow('bin', vid_fragment[frame]) 615 | cv2.waitKey(10) 616 | 617 | print(ff_nr, '--------------------------------------') 618 | ff_nr += 1 619 | print(removed_states) 620 | print(index) 621 | return x_est, y_est, est_number 622 | 623 | 624 | def plot_points(vid_frag, max_points, x_est, y_est, est_number): 625 | index_error = 0 626 | # plot raw measurements 627 | for frame_positions in max_points: 628 | for pos in frame_positions: 629 | plt.plot(pos[0], pos[1], 'r.') 630 | try: 631 | plt.axis([0, vid_frag[0].shape[1], vid_frag[0].shape[0], 0]) 632 | except IndexError: 633 | index_error = 1 634 | plt.xlabel('width [px]') 635 | plt.ylabel('height [px]') 636 | plt.title('Objects raw measurements') 637 | ########################################################################## 638 | # plot estimated trajectories 639 | for ind in range(est_number): 640 | if len(x_est[ind]): 641 | # for pos in range(len(x_est[ind])): 642 | # if not np.isnan(x_est[ind][pos]): 643 | # plt.plot(x_est[ind][pos], y_est[ind][pos], 'g.') 644 | plt.plot(x_est[ind][::], y_est[ind][::], 'g-') 645 | # print(frame) 646 | # [xmin xmax ymin ymax] 647 | try: 648 | plt.axis([0, vid_frag[0].shape[1], vid_frag[0].shape[0], 0]) 649 | except IndexError: 650 | index_error = 1 651 | plt.xlabel('width [px]') 652 | plt.ylabel('height [px]') 653 | plt.title('Objects estimated trajectories') 654 | plt.grid() 655 | plt.show() 656 | 657 | # ########################################################################## 658 | # start_frame = 0 659 | # stop_frame = 200 660 | # my_video = 'static/files/CIMG4027.MOV' 661 | # font = cv2.FONT_HERSHEY_SIMPLEX 662 | # ########################################################################## 663 | # maxima_points, vid_fragment = video_analise(my_video, start_frame, stop_frame) 664 | # x_est, y_est, est_number = kalman(maxima_points) 665 | # plot_points(vid_fragment, maxima_points, x_est, y_est, est_number) 666 | # print('\nFinal estimates number:', est_number) 667 | # print('\nTrajectories drawing...') 668 | # print('EOF - DONE') 669 | 670 | 671 | class MainPage(View): 672 | def get(self, request): 673 | movie_list = Movie.objects.all().order_by('id') 674 | context = { 675 | "movie_list": movie_list 676 | } 677 | return render(request, 'main_page.html', context) 678 | 679 | 680 | class AddMovie(CreateView): 681 | model = Movie 682 | template_name = "movie_form.html" 683 | fields = ['name', 'file'] 684 | success_url = reverse_lazy('main-page') 685 | 686 | 687 | class InfoMovie(View): 688 | def get(self, request, movie_id): 689 | movie_info = Movie.objects.get(pk=movie_id) 690 | context = { 691 | "movie_info": movie_info 692 | } 693 | return render(request, 'movie_info.html', context) 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | -------------------------------------------------------------------------------- /Multiple_Object_Tracking/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Multiple_Object_Tracking.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multiple-Object-Tracking 2 | Blob detection of multiple objects with object indexing and drawning trajectories. 3 | Plan: 4 | - detect objects (blobs) [DONE] 5 | - find their positions in the image, [DONE] 6 | - assume that blobs can touch with each other, [DONE] 7 | - use linear Kalman filter with Munkres algorithm for indexing detected objects, [DONE] 8 | - use Monte Carlo method for indexing detected objects, 9 | - return indexed trajectories. [DONE] 10 | 11 | # Check my other repo for more information: https://github.com/dghy/GUI_Blob_Tracker 12 | Both these projects require OpenCV as well as modules from requirements.txt 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cycler==0.10.0 2 | Cython==0.26 3 | Django==1.11.3 4 | matplotlib==2.0.2 5 | numpy==1.13.1 6 | olefile==0.44 7 | opencv-contrib-python==3.2.0.8 8 | opencv-python==3.2.0.8 9 | Pillow==4.2.1 10 | pkg-resources==0.0.0 11 | pyparsing==2.2.0 12 | python-dateutil==2.6.1 13 | pytz==2017.2 14 | scipy==0.19.1 15 | six==1.10.0 16 | --------------------------------------------------------------------------------