Procesamiento Integral de Machine Learning con Nubes de Puntos (I) - Creación de un Dataset SharpNet con CloudCompare

Introducción

Para un proyecto de encofrado de alta resistencia, se necesitaba automatizar el proceso de corte de barras horizontales y verticales, el cual era extremadamente complejo y consumía mucho tiempo utilizando algoritmos tradicionales. Por ello, se buscó implementar una solución basada en machine learning.

Antes de comenzar con el desarrollo, es fundamental comprender cómo preparar y dividir los datasets necesarios para el entrenamiento.

El código de la red neuronal utilizado en esta serie de artículos está basado en el procesamiento de nubes de puntos: Implementación de PointNet para Segmentación de Nubes de Puntos.

El tipo de dataset empleado es principalmente SharpNet. Este artículo explica cómo utilizar CloudCompare para extraer objetos específicos y convertirlos al formato requerido por SharpNet.

¿Qué es un Dataset SharpNet?

Los datasets de SharpNet pueden descargarse desde el sitio web LARGE-SCALE 3D SHAPE RECONSTRUCTION AND SEGMENTATION FROM SHAPENET CORE55. Al解压ar los archivos descargados, se puede observar la estructura interna.

A continuación se muestra la estructura de archivos de entrenamiento:

  • Archivos de nube de puntos (extensión .pts)
  • Archivos de etiquetas correspondientes (extensión .seg)

Cada archivo .pts tiene un archivo .seg correspondiente con el mismo nombre.

El formato del archivo .pts contiene las coordenadas de los puntos en texto plano:


x y z
7.099000 7.473000 4.869000
...

El archivo .seg contiene una etiqueta por cada línea del archivo .pts homónimo. El número representa la clase del punto correspondiente: 1 representa fondo, mientras que 2, 3, 4 representan diferentes objetos específicos.

Proceso de Etiquetado de Nubes de Puntos

Una vez comprendida la estructura de SharpNet, se detalla el procedimiento para etiquetar nubes de puntos. El proceso principal puede Consultarse en el artículoCómo utilizar CloudCompare para etiquetar datos de nubes de puntos.

Como ejemplo, se tiene una nube de puntos de encofrado alto. Si se desea detectar barras horizontales, es necesario extraer todas ellas.

Paso 1: Corte

Seleccionar el archivo de nube de puntos y activar la herramienta de corte (icono de tijeras).

Utilizar la herramienta de selección con clic izquierdo para delimitar una barra horizontal. Una vez completado el contorno, hacer clic derecho para confirmar la selección. Luego, hacer clic en el icono de polígono rojo para seleccionar el contenido del área elegida y completar el corte. Finalmente, confirmar con la marca de verificación verde.

El resultado del corte se puede visualizar inmediatamente.

Paso 2: Clasificación

Após completar el corte, es necesario asignar un nombre al fragmento extraído. Hacer clic en el símbolo más y proporcionar un nombre para el objeto junto con un valor numérico.

Como se trata de un soporte horizontal, se puede denominar "Support" (Soporte). El valor puede ser cualquier número; en este caso se utiliza 1.00. Este valor es relevante para el procesamiento posterior.

Seleccionar todos los fragmentos de nube de puntos y fusionarlos.

Después de la fusión, el fragmento extraído se diferencia de la nube de puntos original. En las propiedades,找到 Active puede verse la clasificación de las nubes de puntos separadas.

Guardar la nube de puntos en formato ASCII para poder procesarla posteriormente como texto:


7.099000 7.473000 4.869000 0 59 255 7.000000 1.000000 1.000000 0.000000
x y z r g b scalar_intensity scalar_hSupport scalar_support scalar_original_cloud_index

Cada atributo corresponde, de arriba a bajo, a los valores después de "end_header", de izquierda a derecha. Por ejemplo, la primera línea representa:

  • Coordenada x: 7.099000
  • Coordenada y: 7.473000
  • Coordenada z: 4.869000
  • Componente rojo: 0
  • Componente verde: 59
  • Componente azul: 255
  • scalar_Intensity
  • scalar_HSupport
  • scalar_Support
  • scalar_Original_cloud_index

Para generar los archivos .pts y .seg, solo es necesario verificar el valor de Support y determinar si corresponde al objeto deseado, generando así los archivos línea por línea.

A continuación se proporciona un ejemplo de código. Es importante destacar que este código no identifica automáticamente todas las etiquetas y debe ajustarse según las necesidades específicas del proyecto.

Archivo de encabezado ProcessPointCloud.h:


#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_ProcessPointCloud.h"
#include "qpoint.h"
#include "qvector.h"
#include "qfile.h"
#include "qfileinfo.h"
#include "qtextstream.h"
#include "qdir.h"
#include "qdebug.h"

// Enumeración para tipos de elementos en la nube de puntos
enum class CloudElement {
    Indeterminado = 0,
    SoporteHorizontal = 1,
    SoporteVertical = 2
};

// Estructura que representa un punto procesado
struct PointData {
    float coordX = 0.0f;
    float coordY = 0.0f;
    float coordZ = 0.0f;
    CloudElement categoria = CloudElement::Indeterminado;
};

class ProcessPointCloud
{
    Q_OBJECT

public:
    ProcessPointCloud();
    ~ProcessPointCloud();

    /// <summary>
    /// Lee un archivo de nube de puntos y lo procesa en el directorio especificado
    /// </summary>
    void ProcessFile(const QString& inputFile, const QString& targetDirectory);
    QVector<PointData> puntosAlmacenados;
};

Archivo de implementación ProcessPointCloud.cpp:


#include "ProcessPointCloud.h"

ProcessPointCloud::ProcessPointCloud()
{
    // Ejemplo de procesamiento con una ruta predefinida
    this->ProcessFile("J:\\output\\GF3_7.ply", "J:\\output");
}

ProcessPointCloud::~ProcessPointCloud()
{
}

void ProcessPointCloud::ProcessFile(const QString& inputFile, const QString& targetDirectory)
{
    QFile archivoOrigen(inputFile);
    QString nombreArchivo = QFileInfo(archivoOrigen).baseName();
    
    if (!archivoOrigen.exists()) {
        qDebug() << "El archivo no existe";
        return;
    }
    
    this->puntosAlmacenados.clear();
    qDebug() << archivoOrigen.open(QIODevice::ReadWrite | QIODevice::Text);

    QTextStream flujoLectura(&archivoOrigen);
    bool cabeceraCompletada = false;
    
    while (!flujoLectura.atEnd()) {
        QString lineaActual = flujoLectura.readLine();
        
        if (lineaActual.contains("end_header")) {
            cabeceraCompletada = true;
            continue;
        }

        if (!cabeceraCompletada) continue;

        QStringList campos = lineaActual.split(" ");
        PointData puntoProcesado;
        puntoProcesado.coordX = campos[0].toFloat();
        puntoProcesado.coordY = campos[1].toFloat();
        puntoProcesado.coordZ = campos[2].toFloat();

        // Identificación basada en la séptima columna
        if (campos[6].toFloat() == 1.0000f) {
            puntoProcesado.categoria = CloudElement::SoporteHorizontal;
        }
        else {
            puntoProcesado.categoria = CloudElement::Indeterminado;
        }
        this->puntosAlmacenados.append(puntoProcesado);
    }

    // Generación de archivos de salida en el directorio especificado
    QDir directorioDestino(targetDirectory);
    if (!directorioDestino.exists()) {
        directorioDestino.mkpath(directorioDestino.absolutePath());
    }
    
    QFile archivoDatos(targetDirectory + QString("/Data/%1.pts").arg(nombreArchivo));
    QTextStream flujoEscritura(&archivoDatos);

    QFile archivoEtiquetas(targetDirectory + QString("/Label/%1.seg").arg(nombreArchivo));
    QTextStream flujoEtiquetas(&archivoEtiquetas);

    archivoDatos.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
    archivoEtiquetas.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
    
    for (auto& elemento : this->puntosAlmacenados) {
        // Escritura de todos los puntos en el directorio de salida
        
        // Los puntos válidos se escriben normalmente
        QString contenidoArchivo = QString("%1 %2 %3")
            .arg(elemento.coordX)
            .arg(elemento.coordY)
            .arg(elemento.coordZ);
        flujoEscritura << contenidoArchivo << endl;

        QString etiqueta = QString("%1").arg(static_cast<qint32>(elemento.categoria));
        flujoEtiquetas << etiqueta << endl;
    }
    
    archivoDatos.close();
    archivoEtiquetas.close();
}

El resultado del procesamiento genera los siguientes archivos:

  • Archivos de datos en formato .pts
  • Archivos de etiquetas en formato .seg

De esta manera, se completa el proceso de creación de un dataset personalizado para SharpNet.

Etiquetas: point-cloud machine-learning cloudcompare sharpnet pointnet

Publicado el 7-10 00:11