Open3d: Multitraitement Python et Visualiseur

Créé le 11 juin 2018  ·  3Commentaires  ·  Source: intel-isl/Open3D

Je veux avoir une application dans laquelle je modifie la géométrie dans un thread et met à jour le Visualizer dans un autre thread. Est-ce possible?
Je suppose que je recherche une version de l' exemple de visualisation non bloquante où l'ICP s'exécute dans un thread et met à jour la géométrie, tandis que les mises à jour du visualiseur sont exécutées dans un thread séparé.

Voici ce que j'ai pour l'instant :

"""
Multi-threaded open3D viewer
"""
import open3d
import multiprocessing as mp

class Consumer(mp.Process):
  def __init__(self, vis_q):
    super(Consumer, self).__init__()
    self.vis_q = vis_q

  def run(self):
    while True:
      next_vis = self.vis_q.get()
      if next_vis is None:
        break

      next_vis.run()
      next_vis.destroy_window()

    return


class Viewer(object):
  def __init__(self):
    self.vis = {}

  def add_geometry(self, g, window_name):
    if window_name in self.vis:
      self.vis[window_name].add_geometry(g)
    else:
      self.vis[window_name] = open3d.Visualizer()
      self.vis[window_name].create_window(window_name, width=640, height=480)
      self.vis[window_name].add_geometry(g)

  def show(self, block=True):

    q = mp.JoinableQueue()
    num_workers = mp.cpu_count() * 2
    workers = [Consumer(q) for _ in xrange(num_workers)]
    for w in workers:
      w.start()

    for vis in self.vis.values():
      q.put(vis)

    for _ in xrange(num_workers):
      q.put(None)

    if block:
      q.join()

if __name__ == '__main__':
  pc1 = open3d.read_point_cloud('../data/camera/000.pcd')
  pc2 = open3d.read_point_cloud('../data/camera/001.pcd')

  v = Viewer()
  v.add_geometry(pc1, 'window1')
  v.add_geometry(pc2, 'window2')
  v.show()

Il crée les deux fenêtres mais elles sont vides et se bloquent.

Mon idée était qu'une fois que cela fonctionne, je peux modifier de manière externe la géométrie ajoutée g et ces changements devraient être automatiquement reflétés dans la visualisation.

help wanted

Commentaire le plus utile

@qianyizh , merci pour votre aide et désolé pour la réponse tardive. Voilà ce qui a finalement fonctionné pour moi :

import open3d
import multiprocessing as mp
import time
import numpy as np
import transforms3d.euler as txe 
from Queue import Empty as queue_empty

class Viewer(object):
  def __init__(self):
    self.q = mp.Queue()

  def worker(self, q): 
    for _ in range(5):
      time.sleep(1)
      T = np.eye(4)
      T[:3, :3] = txe.euler2mat(np.deg2rad(20), 0, 0)
      q.put(T)
      print('put')
    q.put(None)  # poison pill

  def run(self):
    pc = open3d.read_point_cloud('narf/lidar_processed.pcd')
    vis = open3d.Visualizer()
    vis.create_window('cloud', width=640, height=480)
    vis.add_geometry(pc)

    p = mp.Process(target=self.worker, args=(self.q, ))
    p.start()

    keep_running = True
    while keep_running:
      try:
        T = self.q.get(block=False)
        if T is not None:
          print('got T')
          pc.transform(T)
        else:
          print('got poison. dying')
          keep_running = False
        vis.update_geometry()
      except queue_empty:
        pass
      vis.update_renderer()
      keep_running = keep_running and vis.poll_events()
    vis.destroy_window()
    p.join()

if __name__ == '__main__':
  v = Viewer()
  v.run()

J'ai essayé de faire de pc un membre de la classe et de le transformer en self.worker , mais ce changement n'a pas été reflété dans le visualiseur. Je pense que lorsque vous faites un mp.Process cela peut faire une copie de pc et que le worker peut modifier sa propre copie.
J'ai donc fini par envoyer des transformations à partir du worker utilisant un Queue . Cela élimine le besoin d'un verrou ou d'un indicateur cloud_updated .

Tous les 3 commentaires

C'est faisable mais c'est un peu compliqué.

Premièrement, en raison d' une limitation de glfw , la classe Visualizer doit être gérée dans le thread principal. Vous devez donc structurer votre programme en thread principal pour le rendu et en threads de travail pour la mise à jour de la géométrie.

Deuxièmement, assurez-vous de bien comprendre le fonctionnement de la boucle de rendu. Voir les commentaires dans https://github.com/IntelVCL/Open3D/pull/343 et https://github.com/IntelVCL/Open3D/issues/357. En bref, la boucle de rendu agit comme :

  1. Appelez poll_events() pour (a) rendre si nécessaire, et (b) vérifier s'il y a un événement souris/clavier qui doit être répondu
  2. Liez la géométrie si nécessaire (appelez vis.update_geometry() pour lier à nouveau)
  3. Liez le shader OpenGL si nécessaire (appelez vis.update_renderer() pour lier à nouveau et rendre)
  4. Revenir à 1

Cela doit être dans le fil principal.
Si poll_events() n'est pas appelé, les fenêtres seront bloquées comme cela se produit dans votre cas. Si vous avez besoin de plusieurs fenêtres, elles doivent être dans le même fil de discussion, voir mon exemple dans https://github.com/IntelVCL/Open3D/issues/357

Enfin, puisque vous souhaitez éditer la géométrie tout en rendant en même temps, il y a bien une condition de course entre l'édition et la re-liaison de la géométrie. Un verrou doit être introduit.

Un exemple de pseudo-code est le suivant :

# in worker thread
while 1:
    obtain_lock(pc_i)
    do_something(pc_i)
    is_dirty_pc_i = True
    release_lock(pc_i)

# in main thread
vis1 = Visualizer()
vis1.create_window()
vis1.add_geometry(pc_1)
vis2 = Visualizer()
vis2.create_window()
vis2.add_geometry(pc_2)
while True:
    obtain_lock(pc_1)
    obtain_lock(pc_2)
    if is_dirty_pc_1:
        vis1.update_geometry()
    if is_dirty_pc_2:
        vis2.update_geometry()
    vis1.update_renderer()
    vis2.update_renderer()
    vis1.poll_events()
    vis2.poll_events()
    is_dirty_pc_1 = False
    is_dirty_pc_2 = False
    release_lock(pc_1)
    release_lock(pc_2)
vis1.destroy_window()
vis2.destroy_window()

Il peut cependant y avoir des problèmes. Et le fil principal peut être beaucoup bloqué. Je suggérerais toujours de restructurer l'ensemble afin que le thread de travail puisse fonctionner dans le thread principal et de mettre à jour la fenêtre de temps en temps. Quelque chose comme:

vis1 = Visualizer()
vis1.create_window()
vis1.add_geometry(pc_1)
vis2 = Visualizer()
vis2.create_window()
vis2.add_geometry(pc_2)
while True:
    worker_run()
    vis1.update_geometry()
    vis2.update_geometry()
    vis1.update_renderer()
    vis2.update_renderer()
    vis1.poll_events()
    vis2.poll_events()
vis1.destroy_window()
vis2.destroy_window()

@qianyizh , merci pour votre aide et désolé pour la réponse tardive. Voilà ce qui a finalement fonctionné pour moi :

import open3d
import multiprocessing as mp
import time
import numpy as np
import transforms3d.euler as txe 
from Queue import Empty as queue_empty

class Viewer(object):
  def __init__(self):
    self.q = mp.Queue()

  def worker(self, q): 
    for _ in range(5):
      time.sleep(1)
      T = np.eye(4)
      T[:3, :3] = txe.euler2mat(np.deg2rad(20), 0, 0)
      q.put(T)
      print('put')
    q.put(None)  # poison pill

  def run(self):
    pc = open3d.read_point_cloud('narf/lidar_processed.pcd')
    vis = open3d.Visualizer()
    vis.create_window('cloud', width=640, height=480)
    vis.add_geometry(pc)

    p = mp.Process(target=self.worker, args=(self.q, ))
    p.start()

    keep_running = True
    while keep_running:
      try:
        T = self.q.get(block=False)
        if T is not None:
          print('got T')
          pc.transform(T)
        else:
          print('got poison. dying')
          keep_running = False
        vis.update_geometry()
      except queue_empty:
        pass
      vis.update_renderer()
      keep_running = keep_running and vis.poll_events()
    vis.destroy_window()
    p.join()

if __name__ == '__main__':
  v = Viewer()
  v.run()

J'ai essayé de faire de pc un membre de la classe et de le transformer en self.worker , mais ce changement n'a pas été reflété dans le visualiseur. Je pense que lorsque vous faites un mp.Process cela peut faire une copie de pc et que le worker peut modifier sa propre copie.
J'ai donc fini par envoyer des transformations à partir du worker utilisant un Queue . Cela élimine le besoin d'un verrou ou d'un indicateur cloud_updated .

De plus, il vaut probablement la peine de mentionner quelque part dans la documentation que poll_events() renvoie un indicateur qui indique si la fenêtre doit être fermée. Cela peut être très pratique lorsque quelqu'un veut gérer lui-même la boucle de rendu.

Cette page vous a été utile?
0 / 5 - 0 notes