sabato 21 novembre 2020

Realsense SDK con D435 in Debian

Dopo un po' di esperienza con i sensori a luce strutturata ho provata il sensore Intel RealSense D435 (attenzione manca la i finale....a differenza al D435i qui non e' presenta la IMU integrata) che funziona come sensore di profondita' usando la stereoscopia di due camere

Il sensore e' nato per applicazioni di robotica ed e' molto veloce nell'acquisizione/elaborazione (circa 30 fps) ma il risultato come dato di profondita' e' molto differente da quello che si ottiene da un sensore a luce strutturata


Oltre alle due camere per la stereoscopia e' presente un illuminatore IR per migliorare il dato di profondita' ed una camera RGB

La distanza massima operativa e' di circa 4m. Come si nota dal video il dato e' molto rumoroso



Un aspetto che mi ha lasciato molto perplesso e' che non risultano essere conservati gli angoli. Per esempio nella realta' l'angolo e' di 90 gradi mentre dalla mesh risulta essere di 105 gradi. Credo che questo sia dovuto al fatto che le lenti sono quasi dei fish-eye



Per quanto rigurda le lunghezza la porta in realta' e' larga 125 cm mentre il sensore la misura circa 105 cm

Diciamo che i dati al centro dell'immagine sono coerenti ma sui bordi 

l'SDK e' nato per Windows e Ubuntu ma si puo' installare su Debian utilizzando Snap

snap install librealsense

Oltre alle librerie si installa anche realsense-viewer da cui e' possibile anche effettuare l'upgrade del firmware della camera

Gli esempi si trovano a  https://dev.intelrealsense.com/docs/code-samples

Per compilare gli esempi ho dovuto installare anche la libreria StbEasyFont (e' presente nella directory third parts ma io la ho installata con apt per semplicita')

=================================================
cmake_minimum_required(VERSION 3.5)

project(realsense LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(realsense2 REQUIRED )


add_executable(realsense main.cpp)
target_link_libraries(realsense realsense2)

=================================================

=================================================
#include <iostream>
#include <librealsense2/rs.hpp> 

using namespace std;

int main()
{
    rs2::pipeline p;
    p.start();
    rs2::frameset frames = p.wait_for_frames();
    rs2::depth_frame depth = frames.get_depth_frame();
    float width = depth.get_width();
    float height = depth.get_height();
    float dist_to_center = depth.get_distance(width / 2, height / 2);
    std::cout << "The camera is facing an object " << 
dist_to_center << " meters away" << endl << endl;
    return 0;
}
=================================================

Multicam
Nell'esempio Multicam oltre alla libreria Realsense viene usata anche OpenGL con glfw


================================================
cmake_minimum_required(VERSION 3.5)

project(multicam LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# finds OpenGL, GLU and X11
find_package(OpenGL REQUIRED)
if(NOT OPENGL_FOUND)
    message("ERROR: OpenGL not found")
endif(NOT OPENGL_FOUND)
set(GL_LIBRARY GL GLU X11)


find_package(realsense2 REQUIRED )

add_executable(multicam main.cpp)
target_link_libraries(multicam realsense2 glfw ${GL_LIBRARY} m)
================================================


Iphone 12 Pro per la geologia

Avevo provato tempo fa ad usare il tablet Google Tango per scansionare un affioramento roccioso

Adesso che anche Iphone 12 pro (e Ipad Pro 2020) ha un lidar volevo vedere la possibilita' di usare il telefono di Apple per applicazione geologiche



 
Mettendo a confronto Tango con Iphone si nota che entrambi hanno un raggio di scansione massimo tra i 3 ed i 4 metri ma sicuramente la IMU di Iphone e' migliore in quanto spostandosi non decorrela in modo significativo anche su lunghezze superiori ai 10 m

Scansione con Iphone

Scansione con Google Tango Tablet


La cosa che mi impressiona sempre e' come sia possibile ottenere delle misure di lunghezza senza la necessita' di ricalibrare i dati ma questo aspetto e' comune sia a Tango che ad IPhone

Misura di strato : a sinistra di misura reale, a destra misura dal mesh


Non esiste una applicazione ufficiale per esportare i cloudpoint o la mesh da IPhone ma sono disponibili 3DScanner App, PolyCam e Heges



I dati comunque sono leggibili ed elaboraibili da CloudCompare 

giovedì 19 novembre 2020

Compilare VTK su WIndows

 Una volta spacchettato il file tar.gz per esempio c:\vtk\src. Si crea una directory build e con CMake-GUI si indica la sorgente in src e ovviamente la build con build usando come target il compilatore di Visual Studio 2019 (viene richiesta la scelta dopo aver premuto Configure)

Al termine si clicca Generate  e si aprono gli strumenti di Visual Studio/Developer Command Prompt, si entra nella directory dove e' stato creato il file progetto 



e si digita

msbuild /p:Configuration=Debug ALL_BUILD.vcxproj

la compilazione richiede abbastanza tempo

PCL Library e Visual Studio 2019

 Per installare ed usare PCL su Windows la cosa piu' comoda e' utilizzare il pacchetto PCL-1.11.1-AllInOne-msvc2019-win64 

Una volta installato il file exe per esempio in C:\Program Files\PCL 1.11.1


Per creare un progetto in QT con le PCL si devono aggiungere nella path OpenNI2 (stranamente l'installer non la ha aggiunta nella PATH)

Ho sostituito la versione di CMAKE da quella di default di QT con l'installer di CMake  (modificabile dai Kit di Qt) ed come compilatore ho selezionato MSVC2019



il file CMAKE e' il seguente

===============================================

cmake_minimum_required(VERSION 3.5)


project(t3 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(PCL 1.3 REQUIRED COMPONENTS common io features)
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})


add_executable(t3 main.cpp)
target_link_libraries(t3 ${PCL_LIBRARIES})

===============================================

mentre un programma di esempio e' 
===============================================
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>

#include <pcl/features/normal_3d.h>

using namespace std;

int main()
{
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);

     if (pcl::io::loadPCDFile<pcl::PointXYZ> ("c://gabriele.pcd", *cloud) == -1) //* load the file
     {
       PCL_ERROR ("Couldn't read file test_pcd.pcd \n");
       return (-1);
     }
     //std::cout << "Loaded " << cloud->width * cloud->height << " data points from test_pcd.pcd with the following fields: " << std::endl;
     /*for (size_t i = 0; i < cloud->points.size (); ++i)
       std::cout << "    " << cloud->points[i].x << " "    << cloud->points[i].y << " "    << cloud->points[i].z << std::endl;
    */
     // Create the normal estimation class, and pass the input dataset to it

     pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
     ne.setInputCloud (cloud);
    // Create an empty kdtree representation, and pass it to the normal estimation object.
       // Its content will be filled inside the object, based on the given input dataset (as no other search surface is given).
       pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());
       ne.setSearchMethod (tree);

       // Output datasets
       pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);

       // Use all neighbors in a sphere of radius 3cm
       ne.setRadiusSearch (0.03);

       // Compute the features
       ne.compute (*cloud_normals);

       //std::cout << endl << cloud_normals->size() << endl;


       // cloud_normals->size () should have the same size as the input cloud->size ()*

       int i = 0;
       for (pcl::Normal n : *cloud_normals) {
           //std::cerr << i << " n = " << n.normal_x << ", " << n.normal_y << ", " << n.normal_z << "\n";
           std::cout << n.normal_x << "," << n.normal_y << "," << n.normal_z << "\n";
           i++;
       }



    return 0;
}


venerdì 13 novembre 2020

Riparazione Soundic Pong

Questo modello di Pong Soundic anno 1979 a colori lo ho comprato come rotto in quanto i controller non rispondevano 


Di fatto pero' i controller sono dei semplici potenziometri


E' stato sufficiente un pulisci contatti a secco 


Pensavo che fosse rotto perche' ad un certo punto nonostante colpissi la pallina la racchetta era come trasparente. Un amico mi ha detto che e' un comportamento normale, a 15 punti la partita e' terminata e nonostante il gioco continui non si puo' piu' colpire la palla



domenica 8 novembre 2020

Misurazione lunghezze ed angoli con CloudCompare

 Per misurare le lunghezze e gli angoli di una nuvola di punti su CloudCompare si clicchi sul pulsante cerchiato in rosso nell'immagine successiva


Si apre il menu

La seconda icona permette di misurare le distanze, la terza di calcolare gli angoli



sabato 7 novembre 2020

PCL e Tango tablet

Ho ritirato fuori il mio tablet Google Yellowstone Tango per provare il trattamento dati delle nuvole dei punti con PCL

I dati reali sono stati presi presso la cava di Maiano (Fiesole) che e' la palestra di geologia per numerose generazioni di geologi fiorentini 

Ho ripreso con lo scanner del tablet questo dettaglio 


in particolare volevo vedere se riuscivo a misurare l'angolo tra i due piani indicati nella foto sottostante


(quello in blu e' il piano del fronte di scavo, quello in rosso e' relativo ad una frattura)

Usando la app Clino Fieldmove e Innstereo 


a posteriori la scelta delle superfici non e' stata felicissima perche' sono tutte ad alto angolo e si distinguono poco sullo stereoplot

Con il tablet la distanza massima a cui era possibile avere risposta dallo scanner era di circa 3 m. Per prova la superficie e' stata bagnata per vedere se il laser infrarosso era influenzato dall'umidita' della parete ma non si verifiche significative tra superficie asciutta e bagnata

I dati sono stati salvati in PLY e sono stati elaborati con PCL in QtCreator




Il programma dopo aver caricato il file PLY fa un sottocampionamento dei dati con VoxelGrid (passando da oltre 200.000 superfici a poco oltre 50 superfici) e calcola le  normali. I dati sono visualizzati con il visualizzatore interno a PLC

leggermente modificato da https://github.com/jeffdelmerico/pointcloud_tutorial


========================================

cmake_minimum_required(VERSION 3.5)

project(maiano LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(PCL 1.3 REQUIRED COMPONENTS common io features visualization)
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})

add_executable(maiano main.cpp)

target_link_libraries(maiano ${PCL_LIBRARIES})

========================================

#include <iostream>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/ply_io.h>

#include <pcl/io/pcd_io.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <pcl/features/normal_3d.h>
#include <pcl/visualization/pcl_visualizer.h>


using namespace std;

void
downsample (pcl::PointCloud<pcl::PointXYZRGB>::Ptr &points, float leaf_size,
            pcl::PointCloud<pcl::PointXYZRGB>::Ptr &downsampled_out)
{
    cout << "PointCloud before filtering: " << points->width * points->height
           << " data points (" << pcl::getFieldsList (*points) << ")." << std::endl;

  pcl::VoxelGrid<pcl::PointXYZRGB> vox_grid;
  vox_grid.setLeafSize (leaf_size, leaf_size, leaf_size);
  vox_grid.setInputCloud (points);
  vox_grid.filter (*downsampled_out);
  cout << "PointCloud after filtering: " << downsampled_out->width * downsampled_out->height
         << " data points (" << pcl::getFieldsList (*downsampled_out) << ")." << std::endl;

}

void compute_surface_normals (pcl::PointCloud<pcl::PointXYZRGB>::Ptr &points, float normal_radius,
                                    pcl::PointCloud<pcl::Normal>::Ptr &normals_out)
{
  pcl::NormalEstimation<pcl::PointXYZRGB, pcl::Normal> norm_est;

  // Use a FLANN-based KdTree to perform neighborhood searches
  norm_est.setSearchMethod (pcl::search::KdTree<pcl::PointXYZRGB>::Ptr
                            (new pcl::search::KdTree<pcl::PointXYZRGB>));

  // Specify the size of the local neighborhood to use when computing the surface normals
  norm_est.setRadiusSearch (normal_radius);

  // Set the input points
  norm_est.setInputCloud (points);

  // Estimate the surface normals and store the result in "normals_out"
  norm_est.compute (*normals_out);
}

void visualize_normals (const pcl::PointCloud<pcl::PointXYZRGB>::Ptr points,
                        const pcl::PointCloud<pcl::PointXYZRGB>::Ptr normal_points,
                        const pcl::PointCloud<pcl::Normal>::Ptr normals)
{
  // Add the points and normals to the vizualizer
  pcl::visualization::PCLVisualizer viz;
  viz.addPointCloud (points, "points");
  viz.addPointCloud (normal_points, "normal_points");

  viz.addPointCloudNormals<pcl::PointXYZRGB, pcl::Normal> (normal_points, normals, 1, 0.01, "normals");

  // Give control over to the visualizer
  viz.spin ();
}


int main (int argc, char** argv)
{
    // Load data from pcd
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>);
    //if (pcl::io::loadPCDFile<pcl::PointXYZRGB> ("../data/robot1.pcd", *cloud) == -1) //* load the file
    if (pcl::io::loadPLYFile<pcl::PointXYZRGB> ("maiano.ply", *cloud) == -1) //* load the file

    {
        PCL_ERROR ("Couldn't read file robot1.pcd \n");
        return (-1);
    }

    // Point Clouds to hold output
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr downsampled (new pcl::PointCloud<pcl::PointXYZRGB>);
    pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);

    // Downsample the cloud
    const float voxel_grid_leaf_size = 1.0;
    downsample (cloud, voxel_grid_leaf_size, downsampled);

    // Compute surface normals
    const float normal_radius = 1.0;
    compute_surface_normals (downsampled, normal_radius, normals);

    visualize_normals(cloud, downsampled, normals);

    int i = 0;
    for (pcl::Normal n : *normals) {
        std::cout << n.normal_x << "," << n.normal_y << "," << n.normal_z <<"\n";
        i++;
    }

    return(0);
}

========================================

Plottando le normali si ha questo risultato



Al di la' dell'orientamento verso il Nord (che lo scanner non registra in quanto non calibra i dati con la bussola) il programma estra sia le suprfici ad alto angolo che alcun suborizzontali.. non so quanto i dati alla fine siano corretti....i dati delle normali vengono forniti come componenti x,y,z di un versore con origine 0,0,0....non so se ho fatto qualche sbaglio nella conversione in strike e dip



Debugger integrato ESP32S3

Aggiornamento In realta' il Jtag USB funziona anche sui moduli cinesi Il problema risiede  nell'ID USB della porta Jtag. Nel modulo...