Les méthodes d'acquisition des images de profondeur incluent la lidar, la vision stéréoscopique par ordinateur, les machines à mesurer coordonnées, les franges de Moiré et la lumière structurée. Les recherches sur les images de profondeur se concentrent principalement sur les aspects suivants :
- Techniques de segmentation des images de profondeur
- Détection des contours dans les images de profondeur
- Alignement de multiples images de profondeur provenant de différents points de vue
- Reconstruction 3D basée sur les données de profondeur
- Reconnaissance d'objets 3D à partir d'images de profondeur
- Modélisation multi-résolution et compression géométrique des images de profondeur
Dans PCL, la différence principale entre une image de profondeur et un nuage de points réside dans la méthode de recherche des voisins, et elles peuvent être converties l'une en l'autre.
Une image de profondeur (ou image de distance) stocke la distance entre le capteur et chaque point de la scène sous forme de valeur de pixel. Elle reflète directement la géométrie des surfaces visibles et facilite la résolution de nombreux problèmes liés à la description 3D. En appliquant une transformation, une image de profondeur peut être convertie en données de point cloud, et inversement, des données de point cloud structurées peuvent être transformées en image de profondeur.
Création d'une image de profondeur à partir d'un nuage de points
La classe pcl::RangeImage, héritée de PointCloud, génère une image de profondeur pour une scène 3D depuis un point de vue spécifique.
Fonction et paramètres
Pour créer une image de profondeur à partir d'un nuage de points, on utilise la fonction suivante :
createFromPointCloud(nuageDePoints, resolutionAngulaire, largeurMaxAngle, hauteurMaxAngle,
poseCapteur, repereCoordonnees, niveauBruit, porteeMin, tailleBord)
#include <pcl/range_image/range_image.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/visualization/range_image_visualizer.h>
int main(int argc, char** argv) {
pcl::PointCloud<pcl::PointXYZ>::Ptr nuagePointsPtr(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>& nuagePoints = *nuagePointsPtr;
// Générer un nuage de points rectangulaire
for (float yPos = -0.5f; yPos <= 0.5f; yPos += 0.01f) {
for (float zPos = -0.5f; zPos <= 0.5f; zPos += 0.01f) {
pcl::PointXYZ pt;
pt.x = 2.0f - yPos;
pt.y = yPos;
pt.z = zPos;
nuagePoints.points.push_back(pt);
}
}
nuagePoints.width = static_cast<uint32_t>(nuagePoints.points.size());
nuagePoints.height = 1;
float resAng = static_cast<float>(1.0f * (M_PI / 180.0f)); // 1 degré en radians
float maxAngLargeur = static_cast<float>(360.0f * (M_PI / 180.0f));
float maxAngHauteur = static_cast<float>(180.0f * (M_PI / 180.0f));
Eigen::Affine3f poseCapteur = Eigen::Affine3f(Eigen::Translation3f(0.0f, 0.0f, 0.0f));
pcl::RangeImage::CoordinateFrame repere = pcl::RangeImage::CAMERA_FRAME;
float bruit = 0.0f;
float porteeMinimale = 0.0f;
int tailleBordure = 1;
boost::shared_ptr<pcl::RangeImage> imageProfPtr(new pcl::RangeImage);
pcl::RangeImage& imageProf = *imageProfPtr;
imageProf.createFromPointCloud(nuagePoints, resAng, maxAngLargeur, maxAngHauteur,
poseCapteur, repere, bruit, porteeMinimale, tailleBordure);
std::cout << imageProf << "\n";
pcl::visualization::PCLVisualizer viewer("Visualiseur 3D");
viewer.setBackgroundColor(1, 1, 1);
// Ajouter l'image de profondeur comme nuage de points
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> couleurProf(imageProfPtr, 0, 0, 0);
viewer.addPointCloud(imageProfPtr, couleurProf, "image_profondeur");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 4, "image_profondeur");
// Ajouter le nuage de points original
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> couleurOriginal(nuagePointsPtr, 255, 100, 0);
viewer.addPointCloud(nuagePointsPtr, couleurOriginal, "nuage_original");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "nuage_original");
viewer.initCameraParameters();
viewer.addCoordinateSystem(1.0);
while (!viewer.wasStopped()) {
viewer.spinOnce();
pcl_sleep(0.01);
}
return 0;
}
Sortie attendue
header:
seq: 0 stamp: 0 frame_id:
points[]: 1008
width: 48
height: 21
sensor_origin_: 0 0 0
sensor_orientation_: 0 0 0 1
is_dense: 0
angular resolution: 1deg/pixel in x and 1deg/pixel in y.
Extraction des contours à partir d'une image de profondeur
Trois types de points sont enalysés pour les contours :
- Contour d'obstacle : points les plus externes visibles d'un objet
- Contour d'ombre : points adjacents aux zones d'occultation en arrière-plan
- Points de voile : points interpolés entre les contours d'obstacle et d'ombre
Il est crucial de distinguer les points non observés (non captés) des points hors portée du capteur. Ces derniers peuvent marquer des contours, tandis que les premiers ne le sont généralement pas. Si les données de portée manquent, la fonction setUnseenToMaxRange peut être utilisée pour les traiter comme des valeurs maximales (option -m dans l'exemple).
Exemple de code pour l'extraction de contours
#include <iostream>
#include <boost/thread/thread.hpp>
#include <pcl/range_image/range_image.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/range_image_visualizer.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/features/range_image_border_extractor.h>
#include <pcl/console/parse.h>
typedef pcl::PointXYZ TypePoint;
float resolutionAng = 0.5f;
pcl::RangeImage::CoordinateFrame repereCoord = pcl::RangeImage::LASER_FRAME;
bool definirNonVusAMax = false;
void afficherAide(const char* nomProg) {
std::cout << "\nUtilisation : " << nomProg << [options] <scene.pcd>\n\n"
<< "Options :\n"
<< "-------------------------------------------\n"
<< "-r <float> résolution angulaire en degrés (défaut " << resolutionAng << ")\n"
<< "-c <int> cadre de coordonnées (défaut " << static_cast<int>(repereCoord) << ")\n"
<< "-m traiter tous les points non vus comme portée maximale\n"
<< "-h cette aide\n\n";
}
int main(int argc, char** argv) {
if (pcl::console::find_argument(argc, argv, "-h") >= 0) {
afficherAide(argv[0]);
return 0;
}
if (pcl::console::find_argument(argc, argv, "-m") >= 0) {
definirNonVusAMax = true;
std::cout << "Définition des valeurs non vues dans l'image de profondeur à la portée maximale.\n";
}
int tmpRepere;
if (pcl::console::parse(argc, argv, "-c", tmpRepere) >= 0) {
repereCoord = static_cast<pcl::RangeImage::CoordinateFrame>(tmpRepere);
std::cout << "Utilisation du cadre de coordonnées " << static_cast<int>(repereCoord) << ".\n";
}
if (pcl::console::parse(argc, argv, "-r", resolutionAng) >= 0)
std::cout << "Réolution angulaire définie à " << resolutionAng << " degrés.\n";
resolutionAng = pcl::deg2rad(resolutionAng);
pcl::PointCloud<TypePoint>::Ptr nuagePtr(new pcl::PointCloud<TypePoint>);
pcl::PointCloud<TypePoint>& nuage = *nuagePtr;
pcl::PointCloud<pcl::PointWithViewpoint> porteeEloignee;
Eigen::Affine3f poseCapteurScene = Eigen::Affine3f::Identity();
std::vector<int> indicesFichierPcd = pcl::console::parse_file_extension_argument(argc, argv, "pcd");
if (!indicesFichierPcd.empty()) {
std::string fichier = argv[indicesFichierPcd[0]];
if (pcl::io::loadPCDFile(fichier, nuage) == -1) {
std::cout << "Impossible d'ouvrir le fichier \"" << fichier << "\".\n";
afficherAide(argv[0]);
return 0;
}
poseCapteurScene = Eigen::Affine3f(Eigen::Translation3f(nuage.sensor_origin_[0],
nuage.sensor_origin_[1],
nuage.sensor_origin_[2])) *
Eigen::Affine3f(nuage.sensor_orientation_);
std::string fichierPorteeEloignee = pcl::getFilenameWithoutExtension(fichier) + "_far_ranges.pcd";
if (pcl::io::loadPCDFile(fichierPorteeEloignee.c_str(), porteeEloignee) == -1)
std::cout << "Le fichier de portée éloignée \"" << fichierPorteeEloignee << "\" n'existe pas.\n";
} else {
std::cout << "\nAucun fichier *.pcd fourni => Génération d'un nuage de points exemple.\n\n";
for (float xPos = -0.5f; xPos <= 0.5f; xPos += 0.01f) {
for (float yPos = -0.5f; yPos <= 0.5f; yPos += 0.01f) {
TypePoint pt; pt.x = xPos; pt.y = yPos; pt.z = 2.0f - yPos;
nuage.points.push_back(pt);
}
}
nuage.width = static_cast<int>(nuage.points.size());
nuage.height = 1;
}
float niveauBruit = 0.0f;
float porteeMin = 0.0f;
int tailleBord = 1;
boost::shared_ptr<pcl::RangeImage> imageProfPtr(new pcl::RangeImage);
pcl::RangeImage& imageProf = *imageProfPtr;
imageProf.createFromPointCloud(nuage, resolutionAng, pcl::deg2rad(360.0f), pcl::deg2rad(180.0f),
poseCapteurScene, repereCoord, niveauBruit, porteeMin, tailleBord);
imageProf.integrateFarRanges(porteeEloignee);
if (definirNonVusAMax)
imageProf.setUnseenToMaxRange();
pcl::visualization::PCLVisualizer viewer("Visualiseur 3D");
viewer.setBackgroundColor(1, 1, 1);
viewer.addCoordinateSystem(1.0f, "global");
pcl::visualization::PointCloudColorHandlerCustom<TypePoint> couleurNuage(nuagePtr, 0, 0, 0);
viewer.addPointCloud(nuagePtr, couleurNuage, "nuage_original");
// Extraction des contours
pcl::RangeImageBorderExtractor extracteurContours(&imageProf);
pcl::PointCloud<pcl::BorderDescription> descriptionsContours;
extracteurContours.compute(descriptionsContours);
// Séparer les points par type de contour
pcl::PointCloud<pcl::PointWithRange>::Ptr pointsContourPtr(new pcl::PointCloud<pcl::PointWithRange>);
pcl::PointCloud<pcl::PointWithRange>::Ptr pointsVoilePtr(new pcl::PointCloud<pcl::PointWithRange>);
pcl::PointCloud<pcl::PointWithRange>::Ptr pointsOmbrePtr(new pcl::PointCloud<pcl::PointWithRange>);
for (int yIdx = 0; yIdx < static_cast<int>(imageProf.height); ++yIdx) {
for (int xIdx = 0; xIdx < static_cast<int>(imageProf.width); ++xIdx) {
const auto& traits = descriptionsContours.points[yIdx * imageProf.width + xIdx].traits;
if (traits[pcl::BORDER_TRAIT__OBSTACLE_BORDER])
pointsContourPtr->points.push_back(imageProf.points[yIdx * imageProf.width + xIdx]);
if (traits[pcl::BORDER_TRAIT__VEIL_POINT])
pointsVoilePtr->points.push_back(imageProf.points[yIdx * imageProf.width + xIdx]);
if (traits[pcl::BORDER_TRAIT__SHADOW_BORDER])
pointsOmbrePtr->points.push_back(imageProf.points[yIdx * imageProf.width + xIdx]);
}
}
// Visualisation des contours
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> couleurContour(pointsContourPtr, 0, 255, 0);
viewer.addPointCloud(pointsContourPtr, couleurContour, "points_contour");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "points_contour");
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> couleurVoile(pointsVoilePtr, 255, 0, 0);
viewer.addPointCloud(pointsVoilePtr, couleurVoile, "points_voile");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "points_voile");
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> couleurOmbre(pointsOmbrePtr, 0, 255, 255);
viewer.addPointCloud(pointsOmbrePtr, couleurOmbre, "points_ombre");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "points_ombre");
// Afficher les contours sur l'image de profondeur
pcl::visualization::RangeImageVisualizer* widgetContours =
pcl::visualization::RangeImageVisualizer::getRangeImageBordersWidget(
imageProf, -std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity(), false,
descriptionsContours, "Image de profondeur avec contours");
while (!viewer.wasStopped()) {
widgetContours->spinOnce();
viewer.spinOnce();
pcl_sleep(0.01);
}
return 0;
}</int></float>
Pour exécuter l'exemple avec un fichier PCD : range_image_border_extraction -m ./data/table_scene_lms400_downsampled.pcd