Técnico

Generación de conjuntos de datos sintéticos (SDG) basados en escenas con Isaac Sim

Share

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:

  1. 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_path y camara_path.
  2. Personalización:
    • Trayectorias de objetos y cámaras: Ajustar prim_path y camara_path para que coincidan con las rutas de los objetos y de la cámara de la escena.
    • Posiciones iniciales y finales: Conjunto init_pos y post_final para definir la ruta del movimiento.
    • Número de pasos: Modificar número_de_pasos para controlar la granularidad del movimiento.
    • Parámetros de rotación: Actualizar lista_de_pasos de rotación para especificar los ángulos de rotación y los escalones.
  3. Guardar rutas:
    • Asegúrese de que los directorios especificados en save_paths existen o actualízalas para que sean rutas válidas.
    • El cuaderno guarda imágenes RGB, imágenes de profundidad y anotaciones en estas rutas.
  4. 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.
  5. 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:

  1. El camino hacia la montacargas.
  2. Camino a la cámara.
  3. 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.

Cada viaje de IA comienza con una conversación

Hablemos
Hablemos