Suivi des objets verts avec python + numpy (filtre à particules)

en premier

Plus tôt, j'ai écrit un article comme celui-ci (reconnaissons les objets rouges avec python) (http://qiita.com/odaman68000/items/ae28cf7bdaf4fa13a65b). C'est une méthode pour convertir simplement une image en HSV (Teinte, Saturation, Valeur) et trouver une région avec une forte composante rouge.

Cette fois, considérons un objet "vert" comme une application. Et puis je parlerai de "tracking" des objets dans un flux vidéo continu. Si vous regardez de près le flux vidéo continu, il s'agit d'une image fixe continue. Si vous continuez à reconnaître l'objet vert à partir de cette image fixe, vous pouvez le suivre naturellement. Ce n'est pas une grosse erreur de penser cela. Cependant, il existe une caméra différente de l'œil humain. Si l'angle de l'objet cible dévie un peu, il brillera en blanc en fonction de la quantité de lumière et s'écartera du jugement (le jugement échoue et vous le perdez de vue), le point de jugement vole, ou vous le perdez de vue même si la lumière ambiante change un peu. Il est. Que se passe-t-il si de mauvaises conditions se chevauchent et que vous la perdez de vue? L'ordinateur déterminera que l'objet est "absent". Mais dans l'instant suivant, les conditions peuvent s'améliorer et réapparaître. Il apparaît et disparaît, il réapparaît, et il n'y a aucun sentiment de stabilité, et une précision de détection de 100% ne peut pas toujours être attendue.

Par conséquent, il est nécessaire d'abandonner l'idée qu'une image fixe doit être analysée à chaque fois même s'il s'agit d'un flux vidéo, et de reconsidérer le flux vidéo comme une "image fixe continue". En d'autres termes, il devrait être prédit et recherché en utilisant pleinement l'idée de probabilité et de statistique selon laquelle "il devrait être ici parce que c'était ici la dernière fois".

Filtre à particule

Il existe de bonnes façons de résoudre ces problèmes. C'est ce qu'on appelle un "filtre à particules". Je n'en sais pas assez pour expliquer la définition académique et le contenu du filtre à particules, alors j'aimerais laisser cela à de nombreuses autres pages de commentaires. Cependant, sur le plan conceptuel, l'analyse d'image à l'aide d'un filtre à particules

  1. Disperser des particules (points) sur l'image
  2. Calculez la probabilité de l'environnement (yudo: poids, par exemple, rougeâtre ou bleuâtre) pour chaque particule.
  3. Utilisez des particules avec une probabilité élevée et retirez les particules avec une faible probabilité
  4. Trouvez le point où les particules les plus probables sont les plus concentrées (c'est la position de l'objet)
  5. Déplacez toutes les particules au hasard petit à petit pour préparer l'image suivante (→ allez à 2.)

Si vous continuez ces procédures 1 à 5, vous pourrez naturellement réaliser ce qui précède. L'état "J'étais ici la dernière fois" est rappelé par les particules dispersées, et pour chaque particule, "Est-ce de ce côté-ci?" Les particules pauvres qui continuent de nuire à leurs prédictions seront éliminées. Les particules prédisent désespérément l'image suivante et survivent.

Il est assez difficile de décrire ces séries de processus en C / C ++, mais principalement en raison de la puissance de numpy, c'est un grand tokoro de python + numpy qu'une petite quantité de code est nécessaire.

Mise en place d'un filtre à particules (basique)

Tout d'abord, à propos de l'importation. On suppose que vous avez importé comme suit.

import cv2
import numpy as np

Définition des particules

Tout d'abord, définissez la structure des particules. Faisons comme suit. «x» et «y» sont les positions des particules et «poids» est la probabilité (poids).

particle = [x, y, weight]

Fonction de calcul de probabilité

Ensuite, créez une fonction pour calculer la probabilité (poids) des particules. Cette fonction scanne une plage de 30x30 pixels autour des coordonnées spécifiées et renvoie le pourcentage de 900 pixels qui répondent aux critères. Par exemple, si la plage de 30 x 30 pixels est entièrement NG, une valeur de 0,0, si tout est OK, une valeur de 1,0 et si environ la moitié est OK, une valeur d'environ 0,5 est renvoyée. La fonction de décision func () est désactivée et permet à l'appelant de spécifier la fonction de décision.

def likelihood(x, y, func, image, w=30, h=30): 
  x1 = max(0, x - w / 2)
  y1 = max(0, y - h / 2)
  x2 = min(image.shape[1], x + w / 2)
  y2 = min(image.shape[0], y + h / 2)
  region = image[y1:y2, x1:x2]
  count = region[func(region)].size
  return (float(count) / image.size) if count > 0 else 0.0001

Fonction d'initialisation des particules

Vient ensuite la fonction d'initialisation des particules. Le paramètre func est censé spécifier la fonction de décision à sortir comme auparavant. Cette fonction crée beaucoup (500) de ces particules. Comme valeur initiale, les particules sont positionnées près de la plus grande zone de la zone déterminée par la fonction func (). Le "500" de "(500, 3)" spécifié dans l'appel de "np.ndarray ()" est le nombre de particules, et "3" est le nombre d'éléments des "x", "y", "poids" ci-dessus. est.

def init_particles(func, image): 
  mask = image.copy()
  mask[func(mask) == False] = 0
  contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
  if len(contours) <= 0:
    return None
  max_contour = max(contours, key=cv2.contourArea)
  max_rect = np.array(cv2.boundingRect(max_contour))
  max_rect = max_rect[:2] + max_rect[2:] / 2
  weight = likelihood(max_rect[0], max_rect[1], func, image)
  particles = np.ndarray((500, 3), dtype=np.float32)
  particles[:] = [max_rect[0], max_rect[1], weight]
  return particles

Implémentation de filtre à particules (algorithme)

C'est la partie du mouvement du filtre à particules dans le vrai sens du terme. Le filtre à particules passe par les quatre procédures suivantes.

  1. Rééchantillonnage des particules
  2. Prévisions
  3. Jugement de probabilité (poids)
  4. Mesure

Rééchantillonnage de particules

Lors du rééchantillonnage, en utilisant des nombres aléatoires, les particules de mauvaise qualité sont éliminées et remplacées par des particules de bonne qualité. La méthode cumsum () utilisée pour créer le tableau weights calcule la somme cumulée. De plus, en définissant (poids> poids) .argmax (), le tableau poids sera analysé et l'index du tableau sera renvoyé lorsqu'une valeur supérieure à poids apparaît pour la première fois.

def resample(particles): 
  tmp_particles = particles.copy()
  weights = particles[:, 2].cumsum()
  last_weight = weights[weights.shape[0] - 1]
  for i in xrange(particles.shape[0]):
    weight = np.random.rand() * last_weight
    particles[i] = tmp_particles[(weights > weight).argmax()]
    particles[i][2] = 1.0

Prévoir

En fait, déplacez les particules vers l'image suivante. Ajoutez le coefficient spécifié par «variance» multiplié par le résultat de «numpy.random.randn ()». Ce coefficient de «variance» est une valeur numérique qui doit être réglée en fonction de l'intensité du mouvement de la cible. Le mouvement aléatoire des particules est appelé «prédiction». Les particules qui se déplacent dans la bonne direction survivent, et les particules qui se déplacent dans la mauvaise direction sont destinées à être éliminées (écrasées).

def predict(particles, variance=13.0): 
  particles[:, 0] += np.random.randn((particles.shape[0])) * variance
  particles[:, 1] += np.random.randn((particles.shape[0])) * variance

Jugement de probabilité (poids)

Déterminez la probabilité (poids) de chaque particule. Ce sera le matériau pour juger du résultat de la prédiction précédente. Cette vraisemblance (poids) est calculée en appelant la fonction «vraisemblance ()» créée précédemment.

def weight(particles, func, image): 
  for i in xrange(particles.shape[0]): 
    particles[i][2] = likelihood(particles[i][0], particles[i][1], func, image)
  sum_weight = particles[:, 2].sum()
  particles[:, 2] *= (particles.shape[0] / sum_weight)

La mesure

Mesurez les particules pour savoir où sont concentrées les particules les plus performantes.

def measure(particles): 
  x = (particles[:, 0] * particles[:, 2]).sum()
  y = (particles[:, 1] * particles[:, 2]).sum()
  weight = particles[:, 2].sum()
  return x / weight, y / weight

terminer

Enfin, le traitement mis en œuvre jusqu'à présent se résume à une fonction d'utilité. Pendant le max_frame spécifié par l'argument, si aucun composant vert n'est trouvé, les particules sont réinitialisées.

particle_filter_cur_frame = 0

def particle_filter(particles, func, image, max_frame=10):
  global particle_filter_cur_frame
  if image[func(image)].size <= 0:
    if particle_filter_cur_frame >= max_frame:
      return None, -1, -1
    particle_filter_cur_frame = min(particle_filter_cur_frame + 1, max_frame)
  else:
    particle_filter_cur_frame = 0
    if particles is None:
      particles = init_particles(func, image)

  if particles is None:
    return None, -1, -1

  resample(particles)
  predict(particles)
  weight(particles, func, image)
  x, y = measure(particles)
  return particles, x, y

Utilisons-le!

Alors, créons un programme qui suit les objets verts en utilisant les filtres à particules que nous avons implémentés jusqu'à présent. Ici, les données de trame (image BGR) acquises par cv2.VideoCapture () sont converties en HSV, et S (Saturation) et V (Valeur) sont multipliées par le seuil de OTSU, respectivement, et la profondeur de couleur et la luminosité sont multipliées. Dans les deux cas, le processus consistant à n'utiliser que suffisamment de pixels est exécuté (la composante H est masquée par des pixels de passage de S et V et remplie de 0). Je veux trouver la plage de vert (50-85), donc effacer 0 est raisonnable. (Par contre, lors de la détection du rouge, il sera étrange que vous ne le remplissiez pas avec une valeur autre que 0.)

Ce qui est réellement passé à la fonction particule_filter () est le composant H qui a été filtré ci-dessus. Après cela, je pense que la fonction particule_filter () gère les particules et suit bien la partie verte.

import cv2
import numpy as np

if __name__ == "__main__": 
  def is_green(region): 
    return (region >= 50) | (region < 85)

  cap = cv2.VideoCapture(0)
  particles = None

  while cv2.waitKey(30) < 0:
    _, frame = cap.read()
	frame_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV_FULL)
	frame_h = frame_hsv[:, :, 0]
	_, frame_s = cv2.threshold(frame_hsv[:, :, 1], 0, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
	_, frame_v = cv2.threshold(frame_hsv[:, :, 2], 0, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
    frame_h[(frame_s == 0) | (frame_v == 0)] = 0
	
    particles, x, y = particle_filter(particles, is_green, frame_h)
	
    if particles is not None:
      valid_particles = particles[(particles[:, 0] >= 0) & (particles[:, 0] < frame.shape[1]) &
                                  (particles[:, 1] >= 0) & (particles[:, 1] < frame.shape[0])]
      for i in xrange(valid_particles.shape[0]): 
        frame[valid_particles[i][1], valid_particles[i][0]] = [255, 0, 0]
	  p = np.array([x, y], dtype=np.int32)
	  cv2.rectangle(frame, tuple(p - 15), tuple(p + 15), (0, 0, 255), thickness=2)
	
	cv2.imshow('green', frame)

  cap.release()
  cv2.destroyAllWindows()

Ce filtre à particules est extrêmement résistant au bruit et aux mouvements et chassera un objet de manière assez persistante. De plus, la position de détection obtenue en conséquence est également très stable. Une fois que vous avez implémenté le filtre à particules, je pense que la clé est de savoir comment implémenter la fonction likelihood (). La vie et la mort des particules dépendent de cette «vraisemblance ()».

Observez le mouvement désespéré des particules pour survivre. Peu à peu, le désespoir de chaque particule, qui est impitoyablement évalué par «vraisemblance ()», devient intéressant. C'est un filtre à particules qui suit avec précision, mais dans l'ombre de celui-ci, d'innombrables particules naissent et meurent, et vous pouvez ressentir une telle douleur.

Recommended Posts

Suivi des objets verts avec python + numpy (filtre à particules)
[Python] Méthode de calcul avec numpy
Reconnaissons les objets rouges avec python
Suivre les balles de baseball avec Python + OpenCV
Traitement d'image par Python 100 knock # 10 filtre médian
Traitement d'image par Python 100 knock # 12 motion filter
Traitement d'image par Python 100 knock # 9 Filtre Gaussien
Extraction de bords avec python + OpenCV (filtre Sobel, filtre laplacien)
Déboguer avec VS Code en utilisant Boost Python Numpy
1. Statistiques apprises avec Python 1-2. Calcul de diverses statistiques (Numpy)
[Rust / Python] Gérer numpy avec PyO3 (version d'août 2020)
[Fenwick_Tree] Lecture de la bibliothèque AtCoder avec un codeur vert ~ Implémentation en Python ~
Mon Numpy (Python)
FizzBuzz en Python3
Grattage avec Python
Statistiques avec python
Grattage avec Python
Python avec Go
Twilio avec Python
Intégrer avec Python
Jouez avec 2016-Python
AES256 avec python
Testé avec Python
python commence par ()
Les bases de #Python (#Numpy 1/2)
avec syntaxe (Python)
Les bases de #Python (#Numpy 2/2)
Bingo avec python
Zundokokiyoshi avec python
Suivre les programmes Python
Principes de base de Python #Numpy
Excel avec Python
[Python] Mémo Numpy
Micro-ordinateur avec Python
Cast avec python
[Python] Créer un tableau structuré (stocker des données hétérogènes avec NumPy)
Traitement FFT avec filtre numpy et scipy vers passe-bas