viernes, 15 de septiembre de 2017

Tutorial para capturar imagenes

En tutoriales anteriores se mostró como se podían generar nubes de puntos, donde realizamos una nube de forma aleatoria. Si embargo, el gran potencial de PCL es realizar tratamiento de nubes de puntos capturadas por un sensor, los cuales pueden ser de muchos tipos pero en nuestro caso realizaremos la captura a partir de una Kinect de XBox.

Para empezar es necesario tener un sensor de profundidad en nuestro caso Kinect para Xbox 360, no se ha comprobado si la siguiente versión es compatible. Tener en cuenta que no funciona con los puertos USB 2.0 (vienen con la parte interior en azul).


En el código que mostraremos hay que tener en cuenta que hay una parte que es para la captura y guardado en archivo PCL y otra que es para visualizar la nube capturada.


 #include <pcl/io/openni_grabber.h>
 #include <pcl/visualization/cloud_viewer.h> 
 #include <pcl/io/pcd_io.h>
 #include <iostream>
 #include <fstream>
 #include <string.h>
 #include <cstring>
 #include <sstream>
 #include <ctime>
 #include <iostream>
 #include <locale>
 #include <time.h>   
 #include <stdio.h> 
 using std::string;
 static unsigned int pcd_count = 1;
  
 class SimpleOpenNIViewer{
   public:
     SimpleOpenNIViewer () : viewer ("PCL OpenNI Viewer") {}

     void cloud_cb_ (const pcl::PointCloud<pcl::PointXYZ>::ConstPtr &cloud)
     {
        std::locale::global(std::locale("es_ES.utf8"));
        std::time_t rawtime;
        struct tm *timeinfo;
        std::time ( &rawtime );
        timeinfo = std::localtime ( &rawtime );
        char archivo[50] ;
 sprintf(archivo, "%d_%d%d%d_%d%d%d_%d",
    timeinfo->tm_wday,1900 + timeinfo->tm_year,
    timeinfo->tm_mon+1,
             timeinfo->tm_mday, timeinfo->tm_hour,
    timeinfo->tm_min, timeinfo->tm_sec,pcd_count );
 std::strcat(archivo,".pcd");
        std::cout<<"estamos en:"<< archivo  <<std::endl;
 
 if (!viewer.wasStopped()){
  viewer.showCloud (cloud) //guardar archivo

  std::cout<<" en archivo"<<archivo <<std::endl;
  pcl::io::savePCDFileASCII (archivo, *cloud);
  std::cerr << "Saved " << cloud->points.size () 
                          << " data points to: "<< archivo  << std::endl;
                pcd_count++;
                boost::this_thread::sleep (boost::posix_time::seconds (3));
        }

 
 
     }

     void run ()
     {
       pcl::Grabber* interface = new pcl::OpenNIGrabber();

       boost::function<void (const pcl::PointCloud<pcl::PointXYZ>::ConstPtr&)> f =
         boost::bind (&SimpleOpenNIViewer::cloud_cb_, this, _1);
       interface->registerCallback (f);
       interface->start ();

       while (!viewer.wasStopped())
       {
         boost::this_thread::sleep (boost::posix_time::seconds (1));
       }

       interface->stop ();
     }

     pcl::visualization::CloudViewer viewer;
 };

 int main ()
 {
   SimpleOpenNIViewer v;
   v.run ();
   return 0;
 }

En la clase SimpleOpenNIViewer  tenemos dos llamadas la primera que es:

 
void cloud_cb_ 
 
nos servirá para visualizar la nube de puntos y guardarla en una archivo, que en nuestro caso será determinado con fecha y hora ademas de un contador global y la segunda que es:

void run ()
 
Será la llamada que nos permita comunicarnos con el sensor para recibir las nubes de puntos y guardarla en un puntero, y así poderla procesar en la llamada anterior. En esta llamada es importante tener en cuenta que para variar el tiempo entre capturas tendremos que realizarlo en la siguiente línea


boost::this_thread::sleep (boost::posix_time::seconds (tiempo_entre_capturas));

Como ejemplo de nube de puntos  mostramos una nube guardada con el nombre 3_2016518_184530_43.pcd



miércoles, 31 de diciembre de 2014

KEYPOINTS

 
La reciente aparición de sensores capaces de tomar imágenes 3D, hace que el estado de arte de este tipo de imágenes tenga muy pocas herramientas, y en muchos casos no siempre funcionen con la garantía que deseáramos.

La resolución de muchos problemas en el campo de las imágenes 3D, tenemos la dificultad de tener un gran volumen de datos que en algunos casos su orden puede estar corrupto por algunos proceso de filtrado anterior. Aunque, la cantidad de datos a nivel humano puede ser intratable, como en otros muchos procesos de la industria o la ciencia, a nivel computacional puede ser poco razonable computar dato a dato para resolver cada problema.

Ante tanta dificultad de procesamiento la librería PCL tiene en su biblioteca el módulo Keypoints, este módulo tiene por misión establecer clases que entreguen nubes de puntos de menor tamaño, tales puntos deben ser detectados con algoritmos bien definidos. Además, si estos puntos son estudiados con respecto a sus vecinos mediante descriptores podríamos obtener una nube de menor tamaños que nos ofrece tanta información como la nube inicial pero con un menor tamaño de datos.

Como es tan reciente la capacidad de obtener imágenes de este tipo, se ha optado por heredar algoritmo que se utilizan generalmente para imágenes en 2D, ya que son bastante robustos y tienen un gasto computacional mucho menor, aunque tengamos que convertir previamente la imagen con dimensiones de profundidad a una imagen plana.Esto es lo que se ha optado en algunas clases del módulo Keypoints.

Principalmente se busca una reducción de datos para obtener menor cantidad de datos para procesar, pero sin perder la información relevante para que una maquina pueda entender la imagen.

Para llegar al objetivo de obtener una nube reducida con dichas características, es necesario que las nubes de puntos denominadas keypoints, un punto de interés es un punto de la imagen que en general tiene las siguientes características:
  1. Tienen una clara definición, preferiblemente con base matemática, bien fundada.
  2. Tiene una posición bien definida en el espacio.
  3. La estructura local de la imagen alrededor del punto de interés es rica en términos de información local, de manera que el uso de los puntos de interés simplifica su posterior procesamiento en el sistema de visión.
  4. Es estable bajo perturbaciones locales y globales en el dominio de la imagen como las variaciones de iluminación o brillo, tal que los puntos de interés puedan ser calculados de forma fiable con alto grado de reproducción.
Para las nubes de keypoint debe cumplir las siguientes condiciones:
  1. Escasez: por lo general un pequeño conjunto de puntos son keypoint.
  2. Repetibilidad: si un punto ha sido determinado como keypoint en un conjunto de puntos, debe ser en otra nube de puntos similar (estos puntos son llamados estables).
  3. Distinción: el área que rodea cada punto debe tener una forma o apariencia única que se refleja en los descriptores. 
En esta fase tan reciente de la técnica de obtención de keypoints, los algoritmos para su obtención se han heredado de las tecnicas utilizadas en 2D, pero en algunos casos la obtención de una nube de tipo matricial que puede ser convertida en 2D de forma consistente, ha permitido que se sigan manteniendo en esta librería algunos algoritmos de 2D.

Los detectores de keypoint o puntos de interés se clasifican principalmente, según el tipo de datos que se dispongan. Cada uno de ellos utiliza diferentes bases matemáticas para la obtención de los puntos. En adelante, se desarrolla la base matemática de cada uno de los detectores.


Detectoes de escala fija

Los detectores de escala fija encuentran los puntos claves a una escala constante, para esto hay que proveer al algoritmo de parámetro con una métrica adecuada. Como se explica en la figura 1 estos se pueden resumir en dos pasos:
Un primer paso inicial, opcional, es la poda de datos de entrada por umbralización. Una parte para reforzar la selección de los puntos más significativos, por un criterio adicional con respecto a las principales mediciones destacadas, desplegado en las etapas subsiguientes. Además de mejorar la eficiencia del algoritmo, mediante la reducción del número de puntos previstos, como entrada para los pasos siguientes.




El segundo paso, consiste en un procedimiento de Supresión de Falsos Máximos, sobre una medida calculada en cada punto, no descartados en la poda inicial. La medición asociada puede ser de un punto en juicio ( una propiedad de un vértice de la malla) o regiones en juicio ( una propiedad de una región alrededor de cada vértice). En el caso de puntos destacados en juicios, la escala de entrada es usada para definir el tamaño de soporte de los falsos máximos. Con regiones destacadas en juicio, la escala define el soporte sobre las mediciones de prominencia. El soporte de falsos máximos se define mediante un parámetro adicional.
figura 1:método de extracción de
puntos de interés para escala fija

Detectores con adaptación de escala

 La estructura común de los detectores adaptados a escala incluye la construcción de un espacio de escala definida en la superficie, extendiendo así directamente al caso de los datos 3D, del concepto conocido definido para imágenes 2D. Alternativamente, en lugar de extender la teoría de escala-espacio para datos 3D, se puede realizar una incrustación de los datos sobre un plano 2D, con el fin de llevar a cabo el análisis escala-espacio tradicional. Con todos los puntos en una escala asociada, se asocia un parámetro a una función adecuada a la escala, esta escala servirá de apoyo a procesos subsiguientes. A menudo, la presencia de máximos locales múltiples para puntos significativos con diferentes escalas, se detectan en la misma ubicación. Si la función muestra una tendencia monótona, entonces hay una escala característica que puede ser definida y el punto se descarta desde el conjunto de puntos claves candidatos.

Los puntos significativos son recogidos por medio de un método de supresión de falsos máximos en la escala característica de cada punto. Este método de la etapa de supresión de falsos máximos se lleva a cabo tanto espacial como a lo largo de la dimensión de la escala, aunque podría hacerse solo espacialmente. Utilizando la escala característica para definir el soporte sobre el qué se calcula los valores destacados.

Por último, se pueden ejecutar con fines similares a los de los métodos de escala fija, una etapa de poda opcional mediante los puntos adicionales que son eliminados en base a diferentes limitaciones.

figura 2:Flujo para la extracción de puntos de interés con
variación de escala

 En siguientes post se describirán los algoritmos que se puede utilizar en Point Cloud Library para la extración de Keypoints. Ahora, mostraremos una imagen como adelanto de cual seria el resultado de una nube de puntos clave.


figura 3: Nube de puntos con puntos resaltados en verde para indicar sus keypoints.

sábado, 1 de marzo de 2014

Filtros



Introducción

El filtrado de imágenes es tal vez una de las operaciones mas importantes en el procesamiento de imágenes. Debido a los errores de medición, las nubes de puntos presentan un gran número de puntos de sombra. Esto complica la estimación de las funciones 3D de nubes de puntos locales. Algunos de estos valores extremos se pueden filtrar mediante la realización de un análisis estadístico en el vecindario de cada punto, y se eliminan los que no cumplen con determinados criterios.
A la hora de implementar un filtro los resultados que deseamos conseguir pueden ser diferentes, y en muchos casos deseamos investigar como trabaja el filtro, para comparar diferentes parámetros que nos servirá para investigar las bondades de cada filtro. En esta librería se encuentran todas las funciones para realizarlo.

Módulo de filtros de PCL

Primero vamos a ver los diferentes tipos de filtros que disponemos y sus características.

tipos de filtros.

Remove NaN from point cloud.

Es una llamada que tiene por objetivo copiar todos los puntos que no están definidos por valores en la nube de entrada(cloud_in) y copiarlos en otra nube de salida(cloud_out), por lo que no elimina todos los valores atípicos pero si aquellos que los son por no estar definidos. La función se muestra:
void pcl::removeNaNFromPointCloud(const pcl::PointCloud< PointT > &cloud_in,pcl::PointCloud< PointT > &cloud_out, std::vector< int > &index)

Donde:
[in] cloud_in la nube de puntos de entrada
[out] cloud_out ella
nube de puntos de salida
[out]index:vector de asignación de índices de entrada:cloud_out.points[i]=cloud_in.points[Índice[i]]

Approximate Voxel Grid.

 Este filtro disminuye la resolución de una nube de puntos ya que divide en cuadriculas(voxel) la nube, filtrando los datos atípicos, también tenemos la posibilidad de restringir las cuadriculas que no tienen un número mínimo de puntos. Cuando se ejecuta este filtro se corrompe la matriz de datos en que son ordenados. La clase:
pcl::ApproximateVoxelGrid< PointT > Class Template Reference

Implementacion sencilla:
pcl::ApproximateVoxelGrid<pcl::PointXYZRGBA> AVG;
//tamaño de cubo= l
AVG.setLeafSize(l,l,l); 
AVG.setMinimumPointsNumberPerVoxel (2);
AVG.setInputCloud(cloud_filtered);
AVG.filter(*cloud);

 Si deseamos restringir la cuadricula con numero mínimo de puntos tenemos la función :
setMinimumPointsNumberPerVoxel (unsigned int puntos_mínimos_por_voxel)

Bilateral Filter.

Es un filtro donde los datos atípicos son corregidos a través de los valores de sus vecinos mas cercanos, bajo la premisa de que la imagen varia lentamente en el espacio. Este filtro no elimina parte de los datos de la nube si no que los modifica, a la vez que mantiene la resolución y la estructura de datos inicial. La clase:
class pcl::BilateralFilter< PointT >

Fragmento del código:

pcl::BilateralFilter<pcl::PointXYZI> BF;
pcl::search::KdTree<pcl::PointXYZI>::Ptr tree (new     pcl::search::KdTree<pcl::PointXYZI>);

BF.setSearchMethod(tree);
BF.setHalfSize(l);//
el tamaño medio de la ventana de filtro bilateral de Gauss para usar.
BF.setStdDev (0.01);//
el nuevo parámetro de desviación estándar.
BF.setInputCloud(cloud_filtered);

BF.filter(*cloud)
;
Debemos observar que este filtro trabaja con valores de intensidad y no de colores RGB, por lo que es necesario modificar la nube si las tenemos con datos que no son del tipo XYZI.



Pass Through.

El objetivo de este filtro no es la eliminación de datos atípicos, aunque aquellos que no tienen valor numérico son descartado. Consiste en filtrar por campo, condicionando para ciertos valores, por ejemplo si queremos que los puntos estén entre , 1m y 2m de distancia sobre la "x", seleccionamos el campo "x" y limitaos los valores. La clase:


class pcl::PassThrough< PointT >

Fragmento del código:
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud (cloud);
pass.setFilterFieldName ("z");
pass.setFilterLimits (1.5, 2.50);
pass.filter (*cloud_filtered);


GridMinimum

Este tipo de filtro pretende reducir la resolución de la imagen a través de una cuadricula en 2D, de los datos que están en la celda se seleccionan el que tenga el valor mínimo de la dimensión Z. Este algoritmo que sigue teniendo una base estadística, puede ser mucho más rápido ya que solo centra en labores de comparación, pero perdemos todo el orden original de los datos.Una forma de implementacion podría ser la siguiente:

class pcl::GridMinimum< PointT >

 float l;
 l=5*computeCloudResolution(cloud);
 pcl::GridMinimum <pcl::PointXYZRGBA> GridMini(l);
        GridMini.setResolution(l);
        GridMini.setInputCloud(cloud);
        GridMini.filter(*output);

miércoles, 22 de enero de 2014

Lectura de nubes de puntos desde archivos PCD

En la librería PCL podemos acceder a los archivos de nubes de puntos de diferentes formatos, aunque lo mas usual es que la lectura se haga desde un formato PCD que es el mas completo para nuestra librería.

Primero vamos a escribir el código mediante el cual acedemos a un archivo .pcd y lo guardamos en la memoria de la máquina para su posterior visualización.

#include <stdio.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/cloud_viewer.h>
 
int user_data;

int main (int argc, char** argv){


pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>);


pcl::io::loadPCDFile("test.pcd",*cloud);


pcl::visualization::CloudViewer viewer("Cloud Viewer");
    viewer.showCloud(cloud);
    while (!viewer.wasStopped ())
    {
      }

 return(0);
 }


En el encabezado es importante para la lectura de archivos la librería de IO , donde se encuentran las diferentes funciones de escritura, lectura y escritura de los diferentes formatos con los que podemos trabajar, aunque lo recomendable es trabajar con el formato PCD. Por eso incluimos el siguiente archivo en el encabezado:

#include <pcl/io/pcd_io.h>

 Los demás archivos incluidos son necesarios para reconocer el tipo de datos que se utilizan, funciones de la biblioteca estándar y la visualización para comprobar que hemos leído el archivo que pretendíamos.
 

Luego indicamos los datos que vamos a recoger del archivo, que en nuestros caso serán las las coordenadas cartesianas x,y,z además de las componentes del color R(rojo),G(verde),B(azul).  Y además en este caso será de tipo  puntero para poder entregar los datos a la función de visualización.



pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>);

Luego tenemos la función de lectura del archivo, que nos pide un archivo en formato pcd, por lo que su extensión no es pcd saldremos de la función con error.Si indicamos solo el nombre del archivo como es en nuestro caso, dicho archivo tendrá que estar dentro de la misma carpeta donde se encuentra el archivo de ejecución, en caso contrario tendremos que especificar la ruta y el archivo en una misma cadenas de caracteres de tipo string.

Luego en el segundo dato que tenemos que especificar es la variable que hemos declarado previamente en nuestro caso "  cloud " y que es un puntero pero no tiene por que ser puntero.

Luego tendremos la función de visualización que en nuestro caso es algo extra para comprobar lo que hemos leído. Donde tendremos un resultado como el siguente: