La classica esperienza in cui capisci che il mondo sta andando in direzione opposta al tuo
Per imparare un po' di librerie mi sono fatto il mio visualizzatore di nuvole di punti
Volevo includere ImGui o NanoGui per settare i valori delle variabili ma non riesco a trovare il modo di compilarli
https://github.com/c1p81/cloudpoint_glfw
La visualizzazione si modifica con i tasti freccia/pg up-down ed il mouse
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
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)
================================================
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
Scansione con Iphone
Scansione con Google Tango Tablet
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
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
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
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;
}
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
Per misurare le lunghezze e gli angoli di una nuvola di punti su CloudCompare si clicchi sul pulsante cerchiato in rosso nell'immagine successiva
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
Aggiornamento In realta' il Jtag USB funziona anche sui moduli cinesi Il problema risiede nell'ID USB della porta Jtag. Nel modulo...