script

Détection de deux métronomes sur une planche, calcul de leurs trajectoires, de leurs fréquences et de leur déphasage

In [1]:
from __future__ import division, unicode_literals, print_function

%matplotlib inline

import matplotlib as mpl
import matplotlib.pyplot as plt
import cv2
import numpy as np
import pandas as pd
from pandas import DataFrame, Series
from scipy import ndimage
from skimage import morphology, util, filters
import pims
import trackpy as tp
import os
from matplotlib.pyplot import quiver
import skimage
import matplotlib.patches as mpatches

Recadrer l'image pour sélectionner la région qui nous intéresse

In [2]:
def crop(img):
    x_min = 0
    x_max = 719
    y_min = 0
    y_max = 1279
    return img[:,y_min:y_max,x_min:x_max]

Appliquer des fonctions de traitement d'image pour renvoyer une image binaire

In [3]:
def preprocess_video(img):
    img = crop(img)

    #créer un masque sur le vert de l'image  
    lower = np.array([19, 86, 6], dtype = "uint8")
    upper = np.array([90, 255, 255], dtype = "uint8")

    mask = cv2.inRange(img, lower, upper)
    mask = cv2.erode(mask, None, iterations=5)
    mask = cv2.dilate(mask, None, iterations=5)

    return mask

Passer en routine le preprocess_video sur chacunes des images de la video

In [4]:
datapath = '/Users/berardadelyne/Desktop/Projet_tuteure/Experience2/v171'
video = 'v171'
frames = pims.Video(os.path.join(datapath, video + '.h264'), process_func=preprocess_video)
rawframes = pims.Video(os.path.join(datapath, video + '.h264'), process_func=crop)

Détection des positions des centres des masques

In [5]:
features = pd.DataFrame()
for num, img in enumerate(frames):
    label_image = skimage.measure.label(img)
    for region in skimage.measure.regionprops(label_image, intensity_image=img):
        # Condition sur la taille du masque à détecter
        if region.area < 1000 or region.area > 20000:
            continue
        # Stockage des positions de chaque masque pour chaque image
        features = features.append([{'y': region.centroid[0],
                                     'x': region.centroid[1],
                                     'frame': num,
                                     },])

Lecture des différentes positions sur une même trajectoire

In [6]:
search_range = 50
t = tp.link_df(features, search_range, memory=1)
tp.plot_traj(t, superimpose=img)
Frame 12035: 3 trajectories present
Out[6]:
<matplotlib.axes._subplots.AxesSubplot at 0x11ae1c9e8>

Construire l'ensemble des données dans un fichier panda

In [7]:
data = pd.DataFrame()
for item in set(t.particle):
    sub = t[t.particle==item]
    dvx = np.diff(sub.x)
    dvy = np.diff(sub.y)
    for x, y, dx, dy, frame in zip(sub.x[:-1], sub.y[:-1], dvx, dvy, sub.frame[:-1],):
        data = data.append([{'dx': dx,
                             'dy': dy,
                             'x': x,
                             'y': y,
                             'frame': frame,
                             'particle': item,
                            }])

Données de chaque masque

In [8]:
data0 = data[data['particle'] == 2] #planche
data1 = data[data['particle'] == 0] #métronome noir
data2 = data[data['particle'] == 1] #métronome beige

Tracer des positions en fonction des images

In [9]:
ax1 = data0.plot(x='frame',y='x')
data0.plot(x='frame',y='y',ax=ax1)
plt.xlabel('image')
plt.ylabel('pixel')
ax1 = data1.plot(x='frame',y='x')
data1.plot(x='frame',y='y',ax=ax1)
plt.xlabel('image')
plt.ylabel('pixel')
ax1 = data2.plot(x='frame',y='x')
data2.plot(x='frame',y='y',ax=ax1)
plt.legend()
plt.xlabel('image')
plt.ylabel('pixel')
Out[9]:
<matplotlib.text.Text at 0x11bb8c978>

Le nombre d'images étant différent pour chaque trajectoire, réduire la taille des listes de points à celle de la plus courte

In [10]:
taille = min([len(data0.x), len(data1.x), len(data2.x)])

Positions dans le référentiel de la planche

In [11]:
x_1 = data0.x[0:taille] - data1.x[0:taille]
x_2 = data0.x[0:taille] - data2.x[0:taille]
y_1 = data0.y[0:taille] - data1.y[0:taille]
y_2 = data0.y[0:taille] - data2.y[0:taille]

Détection des centres des trajectoires

In [12]:
from skimage.measure import CircleModel, ransac
dat1 = np.column_stack([x_1, y_1])
model1 = CircleModel()
dat2 = np.column_stack([x_2, y_2])
model2 = CircleModel()
ransac_model1, inliers = ransac(dat1, CircleModel, 3, 1,is_data_valid=None,is_model_valid=None, max_trials=50)
ransac_model2, inliers = ransac(dat2, CircleModel, 3, 1,is_data_valid=None,is_model_valid=None, max_trials=50)
centre1 = ransac_model1.params[0:2]
rayon1 = ransac_model1.params[2]
centre2 = ransac_model2.params[0:2]
rayon2 = ransac_model2.params[2]

Passage en coordonnées polaires

In [13]:
theta1 = np.arctan((x_1 - centre1[0])/(y_1 - centre1[1]))
theta2 = np.arctan((x_2 - centre2[0])/(y_2 - centre2[1]))

Tracer les trajectoires

In [14]:
plt.plot(data1.frame[0:taille],theta1*180/np.pi, label="métromone noir")
plt.plot(data2.frame[0:taille],theta2*180/np.pi, label="métronome beige")
plt.legend()
plt.xlabel('image')
plt.ylabel('theta')
Out[14]:
<matplotlib.text.Text at 0x11a472ba8>

Tracer les trajectoires pour les 1500 premières images

In [15]:
plt.plot(data1.frame[0:taille],theta1*180/np.pi, label="métronome noir (témoin)")
plt.plot(data2.frame[0:taille],theta2*180/np.pi, label="métronome beige")
plt.xlim(0, 1500)
plt.legend()
plt.xlabel('image')
plt.ylabel('theta')
Out[15]:
<matplotlib.text.Text at 0x11a46e438>

Tracer les trajectoires pour les 1500 dernières images

In [16]:
plt.plot(data1.frame[0:taille],theta1*180/np.pi, label="métronome noir (témoin)")
plt.plot(data2.frame[0:taille],theta2*180/np.pi, label="métronome beige")
plt.xlim(taille-1500,taille)
plt.legend()
plt.xlabel('image')
plt.ylabel('theta')
Out[16]:
<matplotlib.text.Text at 0x11b312240>

Calcul des fréquences de chaque métronome

In [17]:
from scipy.signal import argrelextrema
max1 = argrelextrema(np.array(theta1), np.greater)
max2 = argrelextrema(np.array(theta2), np.greater)
max_1 = max1[-1]
max_2 = max2[-1]
tau1 = (max_1[-1]-max_1[0])/(len(max_1)-1)
tau2 = (max_2[-1]-max_2[0])/(len(max_2)-1)
nu1 = 60*120/tau1
nu2 = 60*120/tau2
print(nu1, nu2)
201.566405599 201.566405599

Calcul du déphasage en utilisant le maximum de la corrélation

In [18]:
corr = np.zeros(taille-200)
window = 200
for ind in range(0,taille-200) :
    corr_tmp = np.correlate((theta1[ind:ind+window]-np.mean(theta1[ind:ind+window]))/(np.max(theta1[ind:ind+window])),(theta2[ind:ind+window]-np.mean(theta2[ind:ind+window]))/np.max(theta1[ind:ind+window]),'full')
    wherearray = np.where(corr_tmp == np.max(corr_tmp))
    corr[ind] = window - wherearray[0] - 1

plt.plot(corr)
plt.title("Déphasage en image au cours du temps")
Out[18]:
<matplotlib.text.Text at 0x119eb2240>