
Generación de conjuntos de datos sintéticos (SDG) basados en escenas con Isaac Sim
Este blog resume el flujo de trabajo para la generación de conjuntos de datos sintéticos con Isaac Sim. El código proporcionado muestra cómo automatizar el movimiento y la rotación de objetos en un entorno simulado, capturar imágenes desde una cámara y generar anotaciones, como cuadros delimitadores. Este proceso es esencial para crear conjuntos de datos sintéticos para entrenar y probar modelos de visión artificial.
Visión general
Vamos a realizar las siguientes tareas principales:
- Configuración de escena: configura el entorno de simulación y carga los activos necesarios.
- Movimiento y rotación de objetos: mueve un objeto hacia adelante y lo gira alrededor de su eje.
- Captura de datos: Captura imágenes RGB, datos de profundidad y genera anotaciones.
- Visualización: visualiza las anotaciones superponiendo cuadros delimitadores en las imágenes capturadas.
Cargue un USD en Isaac Sim
Con Isaac Sim ya abierto, el primer paso para la generación de conjuntos de datos sintéticos es navegar hasta el Activos de Isaac sección, donde encontrarás varias escenas precargadas. Busca un activo llamado «almacén_con_carretilla elevadora», como se muestra en la imagen de abajo. Para abrir la escena, basta con hacer doble clic en ella.


Ahora, necesita importar otro activo llamado RSD455. Se trata de una cámara que debe arrastrarse y colocarse dentro de la carretilla elevadora, como se muestra en la imagen.


El RSD455 La cámara está incrustada dentro de la carretilla elevadora con el propósito de capturar todo lo que está delante de la carretilla elevadora. Esto es esencial para comenzar a recopilar el conjunto de datos para entrenar un modelo de visión artificial utilizando datos sintéticos. Sin embargo, cuando la cámara está integrada, no está correctamente alineada u orientada hacia la parte delantera de la carretilla elevadora. Por lo tanto, debe posicionarse manualmente.
En el Transformar sección asegúrese de que Conmutador de compensación en la esquina derecha está desactivó. Si está activado, aparecerá la palabra Offset junto a Transformar.
Para orientar correctamente la cámara, debe introducir los siguientes valores en el Transformar sección, específicamente bajo Traducir y Orientar/Rotar:
Transform XYZ Translate 0.04012-0.641791.06215 Orientar/Rotar 0.00.0-90.0


Para verificar la vista de la cámara, selecciona el icono con forma de cámara ubicado en la esquina superior izquierda de la escena. A continuación, haga clic en «Cámara» y selecciona «Cámara_Omnivision_0V9782_Color». Esto te permitirá ver algo similar a la siguiente imagen:

Instrucciones de uso
Para usar este script:
- Prerrequisitos:
- Asegúrese de que NVIDIA Isaac Sim y todos los módulos necesarios están instalados correctamente.
- Verifique que la escena contenga los objetos y las cámaras especificados por
prim_pathycamara_path.
- Personalización:
- Trayectorias de objetos y cámaras: Ajustar
prim_pathycamara_pathpara que coincidan con las rutas de los objetos y de la cámara de la escena. - Posiciones iniciales y finales: Conjunto
init_posypost_finalpara definir la ruta del movimiento. - Número de pasos: Modificar
número_de_pasospara controlar la granularidad del movimiento. - Parámetros de rotación: Actualizar
lista_de_pasos de rotaciónpara especificar los ángulos de rotación y los escalones.
- Trayectorias de objetos y cámaras: Ajustar
- Guardar rutas:
- Asegúrese de que los directorios especificados en
save_pathsexisten o actualízalas para que sean rutas válidas. - El cuaderno guarda imágenes RGB, imágenes de profundidad y anotaciones en estas rutas.
- Asegúrese de que los directorios especificados en
- Ejecución:
- Ejecute el cuaderno en el entorno de Isaac Sim o como una extensión.
- Es posible que el script requiera la integración con el bucle de eventos de Isaac Sim para funcionar correctamente.
- Resultados:
- Las imágenes y anotaciones capturadas se guardarán en los directorios especificados.
- Las imágenes visualizadas con recuadros delimitadores estarán disponibles para su revisión.
Importación de los módulos necesarios
Abra un cuaderno de Omniverse en JupyterLab (consulte el blog anterior); el cuaderno comienza importando varios módulos necesarios para la simulación y el procesamiento de datos:
importar dice
importar os
importar asíncrono
importar json
importar lleno de nudos tan np
importar Imagen PIL
importar cv2
importar omni.kit.commandos tan cmd
de pxr importar GF, Usd, Sdf, USDgeom
importar omni.usd
de omni.kit.viewport.utility importar get_active_viewport, capture_viewport_to_file
importar omni.replicator.core tan representante
de sensor omni.isaac. importar Cámara
de omni.isaac.core.utils importar prismas
importar omni.kit.app
Estos módulos proporcionan funcionalidades para:
- Manejo de geometrías y transformaciones 3D (pxr, omni.usd).
- Procesamiento de imágenes y manejo de datos (numpy, PIL.image, cv2).
- Ejecución asincrónica (asyncio).
- Interactuar con la API de Isaac Sim (módulos omni.*).
Funciones auxiliares
Objetos en movimiento
Mueve un objeto a una nueva posición especificada:
def move_prim_to_pos (stage, prim_path, new_pos, debug=Falso):
prim = stage.getPrimatPath (prim_path)
éxito = prim.getAttribute («xFormOp:Translate») .Set (new_pos, 0)
si depurar:
new_translation = prim.getAttribute («xFormOp:Translate») .Get (0)
print («Nueva posición:», new_translation)
print («Éxito:», éxito)
regresar éxito
Mueve un prim según un vector de pasos especificado:
def move_prim_step (stage, prim_path, step=Ninguna, debug=Falso):
prim = stage.getPrimatPath (prim_path)
old_pos = prim.getAttribute («xFormOp: traducir») .Get (0)
si depurar:
print («Posición anterior:», old_pos)
new_pos = old_pos + paso
éxito = prim.getAttribute («xFormOp:Translate») .Set (new_pos, 0)
si depurar:
new_translation = prim.getAttribute («xFormOp:Translate») .Get (0)
print («Nueva posición:», new_translation)
print («Éxito:», éxito)
regresar éxito
Calcula el tamaño del paso necesario para pasar del punto a al punto b en N pasos:
def paso_de_a_a_b (a, b, N):
regresar (b - a)/N
Primadores giratorios
Gira un prim según un vector de grados especificado:
def rotate_prim (stage, prim_path, rotation_deg, debug=Falso):
prim = stage.getPrimatPath (prim_path)
xFormable = usdGeom.Xformable (primero)
xFormOps = xFormable.getOrderedxFormOps ()
# Busque una operación de rotación existente o cree una nueva
Rotación OP = Ninguna
por op en X para Ops:
si op.geTopType () == USDGEOM.XFormop.TypeRotateXYZ:
RotaciónOp = arriba
romper
si Rotación OP es Ninguna:
RotationOp = xFormable.addXFormOp (
USDGEOM.X para MOP.TypeRotateXYZ, USDGEOM.X para MOP.PrecisionFloat)
si depurar:
print («Creó una nueva rotación xFormOp.»)
# Obtenga la rotación actual
current_rot = rotationOP.get (usd.timecode.default ())
si podredumbre actual es Ninguna:
current_rot = gf.Vec3f (0, 0, 0)
si depurar:
print («Rotación actual:», current_rot)
# Calcular la nueva rotación acumulada
new_rot = current_rot + rotation_deg
RotationOp.set (new_rot)
si depurar:
print («Nueva rotación:», new_rot)
Normalización de datos de profundidad
Normaliza los datos de profundidad para prepararlos para la representación de imágenes:
def normalizar_depth_data (depth_data):
# Sustituir valores infinitos o NaN por cero
depth_data = np.nan_to_num (depth_data, nan=0.0, posinf=0.0, neginf=0.0)
profundidad_min = np.min (profundidad_datos)
profundidad_máx = np.máx (profundidad_datos)
# Evita la división por cero
si profundidad_máx = profundidad_mínima:
normalizado = np.zeros_like (depth_data)
más:
normalizado = (depth_data - depth_min)/(depth_max - depth_min)
normalizado = (normalizado * 255) .astype ('uint8')
regresar normalizado
Configuración de escena
Configure la escena obteniendo el escenario y cargando el prim especificado:
def setup_scene (prim_path, camera_path):
etapa = omni.usd.get_context () .get_stage ()
object_prim = stage.getPrimatPath (prim_path)
camera_prim = stage.getPrimatPath (cama_ruta_cámara)
regresar stage, object_prim, camera_prim
Establece la posición inicial del objeto. Si no se proporciona ninguna posición, se utiliza la posición actual o se establece por defecto en el origen:
def set_initial_position (stage, prim_path, init_pos):
si init_pos es Ninguna:
# Obtenga la posición actual
prim = stage.getPrimatPath (prim_path)
init_pos = prim.getAttribute («xFormOp: traducir») .Get (0)
si init_pos es Ninguna:
init_pos = gf.Vec3d (0, 0, 0)
más:
move_prim_to_pos (stage, prim_path, gf.vec3d (*init_pos), debug=Falso)
regresar gf.vec3d (*init_pos)
Configuración de anotadores
Configura los anotadores para capturar diferentes tipos de datos, como imágenes RGB, datos de profundidad y cuadros delimitadores:
def configure_annotators (camera_path, resolution= (1024, 1024)):
render_product = rep.create.render_product (camara_path, resolución)
rgb = rep.annotatorRegistry.get_annotator («rgb»)
distancia_a_cámara = rep.annotatorRegistry.get_annotator («distancia_a_cámara»)
distance_to_image_plane = rep.annotatorRegistry.get_annotator («distancia_to_image_plane»)
bbox_2d_loose = rep.annotatorRegistry.get_annotator («bounding_box_2d_loose»)
rgb.attach ([render_product])
distancia_a_cámara.attach ([render_product])
distancia_al_plane_imagen.attach ([render_product])
bbox_2d_loose.attach ([render_product])
regresar rgb, distancia_camara, distancia_al_plano_imagen, bbox_2d_loose
Funciones de simulación
Ejecución de la simulación
Mueve el objeto a lo largo de una ruta y captura datos en cada paso:
asincrónico def run_simulation (escenario),
prim_path,
paso,
número_de_pasos,
RGB,
distancia_a_cámara,
distancia_al_plano_imagen,
bbox_2d_loose,
save_paths,
frame_idx=0):
# Genera los marcos y mueve el objeto en cada paso.
por i en rango (número_de_pasos):
# Mueva el objeto.
move_prim_step (stage, prim_path, step, debug=Falso)
# Actualiza la escena.
por _ en gama (10):
esperar omni.kit.app.get_app () .next_update_async ()
# Ejecuta el orquestador.
esperar rep.orchestrator.step_async ()
# Capture los datos de los anotadores.
rgb_data = rgb.get_data ()
distancia_datos_de la cámara_= distancia_a_cámara.get_data ()
distancia_a_image_plane_data = distancia_a_image_plane.get_data ()
bbox_2d_loose_data = bbox_2d_loose.get_data ()
# Defina las rutas de guardado de las imágenes.
path_to_rgb = save_paths ['rgb'] + str (frame_idx) + «.png»
path_to_cam = save_paths ['dcam'] + str (frame_idx) + «.png»
path_to_dplane = save_paths ['dplane'] + str (frame_idx) + «.png»
path_to_bbox_2d = save_paths ['bbox_2d'] + str (frame_idx) + «.npy»
path_to_bbox_2d_info = save_paths ['bbox_2d'] + str (frame_idx) + «_info.json»
# Procesa y guarda rgb_data.
rgba_image = pil.image.fromArray (rgb_data.astype ('uint8'))
rgb_image = rgba_image.convert ('RGB')
rgb_image.save (ruta_a_rgb)
# Procesa y guarda distance_to_camera_data.
dcam_normalized = normalizar_depth_data (distancia_datos_de_cámara)
dcam_image = pil.image.fromArray (dcam_normalized, mode='L')
dcam_image.save (path_to_dcam)
# Procese y guarde distance_to_image_plane_data.
dplane_normalized = normalize_depth_data (distancia_a_image_plane_data)
dplane_image = pil.image.fromArray (dplane_normalized, mode='L')
dplane_image.save (ruta_to_dplane)
# Guarde la información de la matriz y las etiquetas de bbox.
con abrir (path_to_bbox_2d, 'wb') tan f:
np.save (f, bbox_2d_loose_data ["datos"])
con abrir (path_to_bbox_2d_info, «w») tan fp:
json.dump (bbox_2d_loose_data ["info"] ["idToLabels"], fp)
# Incrementa el contador de fotogramas.
frame_idx += 1
regresar frame_idx
Rotación y captura de datos
Gira el objeto y captura datos en cada paso de rotación:
asincrónico def rotate_and_capture (escenario),
prim_path,
escalones de rotación,
ángulo de rotación,
RGB,
distancia_a_cámara,
distancia_al_plano_imagen,
bbox_2d_loose,
save_paths,
frame_idx):
paso de rotación = gf.vec3f (0, 0, ángulo de rotación/pasos de rotación)
por j en rango (pasos de rotación):
# Gire el objeto.
rotate_prim (stage, prim_path, rotation_step, debug=Falso)
# Actualiza la escena.
por _ en gama (5):
esperar omni.kit.app.get_app () .next_update_async ()
# Ejecuta el orquestador.
esperar rep.orchestrator.step_async ()
# Capture los datos de los anotadores.
rgb_data = rgb.get_data ()
distancia_datos_de la cámara_= distancia_a_cámara.get_data ()
distancia_a_image_plane_data = distancia_a_image_plane.get_data ()
bbox_2d_loose_data = bbox_2d_loose.get_data ()
# Defina las rutas de guardado de las imágenes.
path_to_rgb = save_paths ['rgb'] + str (frame_idx) + «.png»
path_to_cam = save_paths ['dcam'] + str (frame_idx) + «.png»
path_to_dplane = save_paths ['dplane'] + str (frame_idx) + «.png»
path_to_bbox_2d = save_paths ['bbox_2d'] + str (frame_idx) + «.npy»
path_to_bbox_2d_info = save_paths ['bbox_2d'] + str (frame_idx) + «_info.json»
# Procesa y guarda las imágenes y anotaciones.
rgba_image = pil.image.fromArray (rgb_data.astype ('uint8'))
rgb_image = rgba_image.convert ('RGB')
rgb_image.save (ruta_a_rgb)
dcam_normalized = normalizar_depth_data (distancia_datos_de_cámara)
dcam_image = pil.image.fromArray (dcam_normalized, mode='L')
dcam_image.save (path_to_dcam)
dplane_normalized = normalize_depth_data (distancia_a_image_plane_data)
dplane_image = pil.image.fromArray (dplane_normalized, mode='L')
dplane_image.save (ruta_to_dplane)
# Guarde la información de la matriz y las etiquetas de bbox.
con abrir (path_to_bbox_2d, 'wb') tan f:
np.save (f, bbox_2d_loose_data ["datos"])
con abrir (path_to_bbox_2d_info, «w») tan fp:
json.dump (bbox_2d_loose_data ["info"] ["idToLabels"], fp)
# Incrementa el contador de fotogramas.
frame_idx += 1
regresar frame_idx
Visualización de anotaciones
Superpone cuadros delimitadores en las imágenes capturadas para su visualización:
def visualize_annotations (num_frames, save_paths):
por i en rango (num_frames):
# Rutas a los archivos bbox y de etiquetas.
path_to_bbox_2d = save_paths ['bbox_2d'] + f "{i} .npy»
path_to_bbox_2d_info = save_paths ['bbox_2d'] + f "{i} _info.json»
image_path = save_paths ['rgb'] + f "{i} .png»
# Lea la imagen en formato BGR (predeterminado en OpenCV).
imagen = cv2.imread (image_path)
# Cargar datos de bbox
bbox_data = np.load (path_to_bbox_2d)
con abrir (path_to_bbox_2d_info, «r») tan fp:
id_to_labels = json.load (fp)
# Convierte bbox_data en una matriz 2D.
bbox_array = np.array ([lista (b) por b en bbox_data])
# Repite sobre cada cuadro delimitador y dibújalo en la imagen.
por caja en bbox_array:
obj_id = int (caja [0])
xmin, ymin, xmax, ymax = bbox [1:5] .astype (int)
confianza = bbox [5]
label = id_to_labels.get (str (obj_id), «Desconocido»)
# Defina el texto de la etiqueta.
text = f "{label}: {confianza: .2f}»
# Elija un color para el cuadro delimitador (BGR).
color = (0, 255, 0) # Verde
# Dibuja el rectángulo del cuadro delimitador.
cv2.rectángulo (imagen, (xmin, ymin), (xmax, ymax), color, 2)
# Obtenga el tamaño del texto.
(text_width, text_height), _ = cv2.getTextSize (texto, cv2.font_hershey_simplex, 0.5, 1)
# Dibuja un rectángulo detrás del texto para una mejor legibilidad.
cv2.rectangle (imagen, (xmin, ymin - text_height - 4), (xmin + text_width, ymin), color, -1)
# Coloque el texto de la etiqueta en la parte superior del cuadro delimitador.
cv2.putText (imagen, texto, (xmin, ymin - 2), cv2.font_hershey_simplex, 0.5, (0, 0, 0), 1)
# Guarde la imagen con los cuadros delimitadores.
output_path = save_paths ['bboxes'] + f"image_with_bboxes_ {i} .png»
cv2.imwrite (output_path, imagen)
Flujo de ejecución principal
Coordina todo el flujo de trabajo para la generación de conjuntos de datos sintéticos mediante la configuración de la escena, la ejecución de la simulación y la visualización de los resultados.
Antes de definir el flujo de trabajo principal, primero debe definir tres cosas:
- El camino hacia la montacargas.
- Camino a la cámara.
- La ruta en la que se guardarán cada fotograma y anotación extraídos de Isaac Sim.
Para determinar la ruta hasta la carretilla elevadora, haga clic en Prim, y en la sección de propiedades de la parte inferior, puedes ver esta ruta. El mismo proceso se aplica a la búsqueda de la ruta a la cámara. Para nuestro ejemplo, estas rutas son las siguientes:
prim_path = «/world/Almacén_con_montacargas/montacargas»
camera_path = «/world/Warehouse_with_montacargas/montacargas/RSD455/RSD455/camera_omnivision_OV9782_color»
tu_ruta=» tu_ruta/»
def main (prim_path=» /world/Warehouse_with_forklift) /montacargas»,
camera_path=» /world/Warehouse_with_montacargas/montacargas/RSD455/RSD455/camera_omnivision_OV9782_color»,
init_pos =Ninguna,
pos_final =Ninguna,
número_de_pasos = 20,
lista_pasos de rotación = [(10, -90), (20, 180)],
save_paths=Ninguna):
# Inicializar rutas.
si save_paths es Ninguna:
# Aquí debes definir cada ruta:
SAVE_PATH_RGB = os.path.join (tu_ruta, "imagen_rgb_»)
SAVE_PATH_DCAM = os.path.join (tu_ruta, "imagen_dcam_»)
SAVE_PATH_DPLANE = os.path.join (tu_ruta, "imagen_dplane_»)
SAVE_PATH_BBOX_2D = os.path.join (tu_ruta, "bbox_2d_loose_»)
SAVE_PATH_BBOXES = os.path.join (tu_ruta, «bboxes»)
os.makedirs (SAVE_PATH_RGB, exist_ok=Cierto)
os.makedirs (SAVE_PATH_DCAM, exist_ok=Cierto)
os.makedirs (SAVE_PATH_DPLANE, exist_ok=Cierto)
os.makedirs (SAVE_PATH_BBOX_2D, exist_ok=Cierto)
os.makedirs (SAVE_PATH_BBOXES, exist_ok=Cierto)
save_paths = {
'rgb': os.path.join (SAVE_PATH_RGB, «image_rgb_»),
'dcam': os.path.join (SAVE_PATH_DCAM, «image_cam_»),
'dplane': os.path.join (SAVE_PATH_DPLANE, «image_dplane_»),
'bbox_2d': os.path.join (SAVE_PATH_BBOX_2D, «bbox_2d_loose_»),
'bboxes': os.path.join (SAVE_PATH_BBOXES, «bboxes_»)
}
# Prepara la escena.
etapa = omni.usd.get_context () .get_stage ()
# Establezca la posición inicial.
init_pos_vec = set_initial_position (stage, prim_path, init_pos)
# Seleccione la posición final y el número de pasos.
si post_final es Ninguna:
final_pos_vec = init_pos_vec + gf.vec3d (0, 10, 0)
más:
final_pos_vec = gf.vec3d (*final_pos)
# Calcule el paso requerido.
paso = step_from_a_to_b (init_pos_vec, final_pos_vec, number_of_steps)
# Calcule el paso requerido.
rgb, distancia_camara, distancia_al_image_plane, bbox_2d_loose = configure_annotators (cama_path)
# Ejecute la simulación.
asincrónico def secuencia_de simulación ():
frame_idx = 0
# Movimiento lineal.
id_marco = esperar run_simulation (escenario),
prim_path,
paso,
número_de_pasos,
RGB,
distancia_a_cámara,
distancia_al_plano_imagen,
bbox_2d_loose,
save_paths,
frame_idx)
# Rotaciones especificadas en rotation_steps_list.
por escalones de rotación, ángulo de rotación en lista_de_pasos de rotación:
id_marco = esperar rotate_and_capture (escenario),
prim_path,
escalones de rotación,
ángulo de rotación,
RGB,
distancia_a_cámara,
distancia_al_plano_imagen,
bbox_2d_loose,
save_paths,
frame_idx)
regresar frame_idx
# Ejecute la secuencia de simulación asincrónica.
frame_idx = asyncio.ensure_future (secuencia_de simulación ())
# Visualice las anotaciones.
visualize_annotations (50, save_paths)
# Mantenga el script en ejecución para permitir que se completen las tareas asincrónicas.
Al final del cuaderno, se llama a la función principal con posiciones iniciales y finales específicas:
principal (
init_pos = [-2.28838, -3.61546, 0],
final_pos = [-2.28838, 10, 0]
)
Una vez finalizada la ejecución de la función principal, el siguiente paso es revisar las rutas donde se guardaron los marcos y las anotaciones para garantizar que todo se ejecutó correctamente. A continuación, proporcionamos una imagen que muestra el aspecto que debería tener un marco con los cuadros delimitadores de los objetos presentes en la escena.

Conclusión
Este cuaderno automatiza la generación de conjuntos de datos sintéticos moviendo y rotando objetos dentro de un entorno simulado, capturando imágenes y generando anotaciones. Este proceso es valioso para entrenar modelos de aprendizaje automático, especialmente en escenarios en los que la adquisición de datos del mundo real es un desafío.
Al personalizar los parámetros de movimiento, rotación y captura, los usuarios pueden generar diversos conjuntos de datos adaptados a sus necesidades específicas. El script sirve de base para flujos de trabajo de generación de datos sintéticos más complejos en Isaac Sim.
