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.
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 :
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.
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 :
J'ai essayé de faire de
pc
un membre de la classe et de le transformer enself.worker
, mais ce changement n'a pas été reflété dans le visualiseur. Je pense que lorsque vous faites unmp.Process
cela peut faire une copie depc
et que leworker
peut modifier sa propre copie.J'ai donc fini par envoyer des transformations à partir du
worker
utilisant unQueue
. Cela élimine le besoin d'un verrou ou d'un indicateurcloud_updated
.