En el procesamiento de nubes de puntos 3D, la identificación de pares de puntos similares entre dos conjuntos de datos es un paso crítico para algoritmos de registro y reconocimiento. La clase pcl::registration::CorrespondenceEstimation de la bilbioteca PCL permite realizar esta búsqueda utilizando descriptores de características geométricas, lo que proporciona una mayor robustez en comparación con la búsqueda basada únicamente en coordenadas espaciales.
Uso de Descriptores para la Correspondencia
Para emplear descriptores en la búsqueda de correspondencias, se debe instanciar la clase de estimación utilizando el tipo de descriptor específico como parámetro de plantilla. Un ejemplo común es el uso de FPFH (Fast Point Feature Histograms):
pcl::registration::CorrespondenceEstimation<pcl::FPFHSignature33, pcl::FPFHSignature33> est;
El procedimiento general consta de tres etapas fundamentales:
- Cálculo de geometría local: Estimación de las normales de la superficie para ambas nubes de puntos.
- Extracción de características: Generación de descriptores (como FPFH, PFH o VFH) que codifican la información geométrica del entorno de cada punto.
- Determinación de pares: Búsqueda de los descriptores más cercanos en el espacio de características para establecer las correspondencias.
Implementación Técnica
El siguiente ejemplo demuestra cómo extraer descriptores FPFH y utilizarlos para encontrar pares de puntos correspondientes entre una nube de origen y una de destino.
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/features/normal_3d_omp.h>
#include <pcl/features/fpfh_omp.h>
#include <pcl/registration/correspondence_estimation.h>
#include <pcl/visualization/pcl_visualizer.h>
// Función para obtener descriptores FPFH a partir de una nube de puntos
pcl::PointCloud<pcl::FPFHSignature33>::Ptr extraerDescriptores(pcl::PointCloud<pcl::PointXYZ>::Ptr nube)
{
pcl::search::KdTree<pcl::PointXYZ>::Ptr arbol(new pcl::search::KdTree<pcl::PointXYZ>());
// 1. Estimación de Normales
pcl::PointCloud<pcl::Normal>::Ptr normales(new pcl::PointCloud<pcl::Normal>);
pcl::NormalEstimationOMP<pcl::PointXYZ, pcl::Normal> estimador_normales;
estimador_normales.setInputCloud(nube);
estimador_normales.setNumberOfThreads(4);
estimador_normales.setSearchMethod(arbol);
estimador_normales.setKSearch(15);
estimador_normales.compute(*normales);
// 2. Estimación de FPFH
pcl::PointCloud<pcl::FPFHSignature33>::Ptr descriptores(new pcl::PointCloud<pcl::FPFHSignature33>);
pcl::FPFHEstimationOMP<pcl::PointXYZ, pcl::Normal, pcl::FPFHSignature33> fpfh;
fpfh.setNumberOfThreads(4);
fpfh.setInputCloud(nube);
fpfh.setInputNormals(normales);
fpfh.setSearchMethod(arbol);
fpfh.setKSearch(15);
fpfh.compute(*descriptores);
return descriptores;
}
int main()
{
pcl::PointCloud<pcl::PointXYZ>::Ptr nube_fuente(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr nube_destino(new pcl::PointCloud<pcl::PointXYZ>);
// Carga de archivos PCD
if (pcl::io::loadPCDFile("datos_fuente.pcd", *nube_fuente) == -1 ||
pcl::io::loadPCDFile("datos_destino.pcd", *nube_destino) == -1)
{
PCL_ERROR("No se pudieron cargar los archivos.\n");
return -1;
}
// Obtención de características para ambas nubes
auto fpfh_fuente = extraerDescriptores(nube_fuente);
auto fpfh_destino = extraerDescriptores(nube_destino);
// Estimación de correspondencias basada en descriptores
pcl::registration::CorrespondenceEstimation<pcl::FPFHSignature33, pcl::FPFHSignature33> estimador;
estimador.setInputSource(fpfh_fuente);
estimador.setInputTarget(fpfh_destino);
pcl::Correspondences coincidencias;
// Se determinan las correspondencias con un umbral de distancia máxima
estimador.determineCorrespondences(coincidencias, 0.5);
// Visualización de resultados
pcl::visualization::PCLVisualizer::Ptr visor(new pcl::visualization::PCLVisualizer("Visor de Correspondencias"));
visor->setBackgroundColor(0, 0, 0);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> color_fuente(nube_fuente, 0, 255, 0);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> color_destino(nube_destino, 255, 0, 0);
visor->addPointCloud<pcl::PointXYZ>(nube_fuente, color_fuente, "fuente");
visor->addPointCloud<pcl::PointXYZ>(nube_destino, color_destino, "destino");
// Dibujar líneas entre puntos correspondientes
visor->addCorrespondences<pcl::PointXYZ>(nube_fuente, nube_destino, coincidencias, "lineas_correspondencia");
while (!visor->wasStopped())
{
visor->spinOnce(100);
}
return 0;
}
Parámetros Clave
Al utilizar determineCorrespondences, el segundo parámetro define la distancia máxima en el espacio de descriptores para considerar que dos puntos son candidatos a ser pares. Ajustar este valor es crucial: un valor demasiado bajo resultará en pocas correspondencias, mientras que uno demasiado alto aumentará el número de falsos positivos.
Además, el uso de implementaciones OMP (OpenMP) en el cálculo de normales y FPFH permite aprovechar la computación multihilo, reduciendo significativamente el tiempo de procesamiento en nubes de puntos densas.