Files
ptrans/ColorimetricSegmenter.cpp
T

1207 lines
41 KiB
C++
Raw Normal View History

//##########################################################################
//# #
//# CLOUDCOMPARE PLUGIN: ColorimetricSegmenter #
//# #
//# This program is free software; you can redistribute it and/or modify #
//# it under the terms of the GNU General Public License as published by #
//# the Free Software Foundation; version 2 of the License. #
//# #
//# This program is distributed in the hope that it will be useful, #
//# but WITHOUT ANY WARRANTY; without even the implied warranty of #
//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
//# GNU General Public License for more details. #
//# #
//# COPYRIGHT: Tri-Thien TRUONG, Ronan COLLIER, Mathieu LETRONE #
//# #
//##########################################################################
2020-02-06 18:58:02 +01:00
#include <iostream>
#include <QtGui>
2020-02-26 13:43:06 +01:00
#include <algorithm>
2020-05-02 10:27:30 +02:00
#include <map>
#include "ColorimetricSegmenter.h"
2020-04-19 15:57:39 +02:00
#include "ccLog.h"
#include "ccPointCloud.h"
#include "ccScalarField.h"
2020-02-27 19:52:52 +01:00
#include "DistanceComputationTools.h"
// Default constructor:
2020-05-02 10:27:30 +02:00
// - pass the Qt resource path to the info.json file (from <yourPluginName>.qrc file)
// - constructor should mainly be used to initialize actions and other members
2020-02-06 18:58:02 +01:00
2020-05-02 10:27:30 +02:00
/*
2020-03-20 16:52:31 +01:00
Default constructor:
2020-05-02 10:27:30 +02:00
- pass the Qt resource path to the info.json file (from <yourPluginName>.qrc file)
2020-03-20 16:52:31 +01:00
- constructor should mainly be used to initialize actions and other members
*/
/*
Class that will apply all the algorithm of our plugin
*/
2020-05-02 11:04:24 +02:00
ColorimetricSegmenter::ColorimetricSegmenter(QObject* parent)
: QObject(parent)
, ccStdPluginInterface(":/CC/plugin/ColorimetricSegmenter/info.json")
, m_action_filterRgb(nullptr)
, m_action_filterRgbWithSegmentation(nullptr)
, m_action_filterHSV(nullptr)
, m_action_filterScalar(nullptr)
, m_action_ToonMapping_Hist(nullptr)
, m_action_ToonMapping_KMeans(nullptr)
{
}
void ColorimetricSegmenter::handleNewEntity(ccHObject* entity)
{
assert(entity && m_app);
m_app->addToDB(entity);
}
void ColorimetricSegmenter::handleEntityChange(ccHObject* entity)
{
assert(entity && m_app);
entity->prepareDisplayForRefresh_recursive();
m_app->refreshAll();
m_app->updateUI();
}
void ColorimetricSegmenter::handleErrorMessage(QString message)
{
if (m_app)
m_app->dispToConsole(message, ccMainAppInterface::ERR_CONSOLE_MESSAGE);
}
// This method should enable or disable your plugin actions
// depending on the currently selected entities ('selectedEntities').
2020-05-02 11:04:24 +02:00
void ColorimetricSegmenter::onNewSelection(const ccHObject::Container& selectedEntities)
{
if (m_action_filterRgb == nullptr)
{
return;
2020-05-02 11:04:24 +02:00
}
2020-02-29 18:00:36 +01:00
if (m_action_filterHSV == nullptr)
{
return;
}
2020-03-02 11:10:44 +01:00
2020-05-02 11:04:24 +02:00
if (m_action_filterRgbWithSegmentation == nullptr)
{
return;
}
2020-05-02 10:27:30 +02:00
2020-05-02 11:04:24 +02:00
if (m_action_filterScalar == nullptr)
{
2020-05-02 10:27:30 +02:00
return;
2020-05-02 11:04:24 +02:00
}
if (m_action_ToonMapping_Hist == nullptr)
{
return;
}
2020-05-02 10:27:30 +02:00
if (m_action_ToonMapping_KMeans == nullptr)
2020-05-02 11:04:24 +02:00
{
2020-05-02 10:27:30 +02:00
return;
}
2020-04-29 19:19:46 +02:00
// If you need to check for a specific type of object, you can use the methods
// in ccHObjectCaster.h or loop and check the objects' classIDs like this:
//
// for ( ccHObject *object : selectedEntities )
// {
// if ( object->getClassID() == CC_TYPES::VIEWPORT_2D_OBJECT )
// {
// // ... do something with the viewports
// }
// }
2020-05-02 10:27:30 +02:00
// For example - only enable our action if something is selected.
2020-03-18 17:37:51 +01:00
// Only enable our action if something is selected.
2020-04-25 18:29:13 +02:00
bool activateColorFilters = false;
bool activateScalarFilter = false;
2020-05-02 11:04:24 +02:00
for (ccHObject* entity : selectedEntities)
2020-04-25 18:29:13 +02:00
{
if (entity->isKindOf(CC_TYPES::POINT_CLOUD))
{
if (entity->hasColors()) {
activateColorFilters = true;
}
else if (entity->hasDisplayedScalarField()) {
activateScalarFilter = true;
}
}
}
m_action_filterRgb->setEnabled(false);
m_action_filterHSV->setEnabled(false);
2020-05-02 11:04:24 +02:00
m_action_filterRgbWithSegmentation->setEnabled(false);
m_action_filterScalar->setEnabled(false);
m_action_ToonMapping_Hist->setEnabled(false);
2020-05-02 10:27:30 +02:00
m_action_ToonMapping_KMeans->setEnabled(false);
2020-04-25 18:29:13 +02:00
2020-05-02 11:04:24 +02:00
//Activate only if only one of them is activated
2020-04-25 18:29:13 +02:00
if ((activateColorFilters != activateScalarFilter) && !selectedEntities.empty()) {
m_action_filterRgb->setEnabled(activateColorFilters);
2020-05-02 11:04:24 +02:00
m_action_filterHSV->setEnabled(activateColorFilters);
m_action_filterRgbWithSegmentation->setEnabled(activateColorFilters);
m_action_filterScalar->setEnabled(activateScalarFilter);
m_action_ToonMapping_Hist->setEnabled(activateColorFilters);
m_action_ToonMapping_KMeans->setEnabled(activateColorFilters);
2020-05-02 10:27:30 +02:00
2020-04-25 18:29:13 +02:00
}
2020-03-02 11:10:44 +01:00
}
// This method returns all the 'actions' your plugin can perform.
// getActions() will be called only once, when plugin is loaded.
2020-03-18 17:37:51 +01:00
QList<QAction*> ColorimetricSegmenter::getActions()
{
2020-03-18 17:37:51 +01:00
// RGB Filter
if (!m_action_filterRgb)
{
2020-03-18 17:37:51 +01:00
m_action_filterRgb = new QAction("Filter RGB", this);
m_action_filterRgb->setToolTip("Filter the points on the selected cloud by RGB color");
m_action_filterRgb->setIcon(getIcon());
// Connect appropriate signal
connect(m_action_filterRgb, &QAction::triggered, this, &ColorimetricSegmenter::filterRgb);
connect(m_action_filterRgb, SIGNAL(newEntity(ccHObject*)), this, SLOT(handleNewEntity(ccHObject*)));
connect(m_action_filterRgb, SIGNAL(entityHasChanged(ccHObject*)), this, SLOT(handleEntityChange(ccHObject*)));
connect(m_action_filterRgb, SIGNAL(newErrorMessage(QString)), this, SLOT(handleErrorMessage(QString)));
2020-02-01 03:59:24 +01:00
}
2020-05-02 11:04:24 +02:00
if (!m_action_filterRgbWithSegmentation)
{
// Here we use the default plugin name, description, and icon,
// but each action should have its own.
m_action_filterRgbWithSegmentation = new QAction("Filter RGB using segmentation", this);
m_action_filterRgbWithSegmentation->setToolTip("Filter the points on the selected cloud by RGB color using segmentation");
m_action_filterRgbWithSegmentation->setIcon(getIcon());
2020-03-02 11:10:44 +01:00
2020-05-02 11:04:24 +02:00
// Connect appropriate signal
connect(m_action_filterRgbWithSegmentation, &QAction::triggered, this, &ColorimetricSegmenter::filterRgbWithSegmentation);
2020-03-02 11:10:44 +01:00
2020-05-02 11:04:24 +02:00
connect(m_action_filterRgbWithSegmentation, SIGNAL(newEntity(ccHObject*)), this, SLOT(handleNewEntity(ccHObject*)));
connect(m_action_filterRgbWithSegmentation, SIGNAL(entityHasChanged(ccHObject*)), this, SLOT(handleEntityChange(ccHObject*)));
connect(m_action_filterRgbWithSegmentation, SIGNAL(newErrorMessage(QString)), this, SLOT(handleErrorMessage(QString)));
}
2020-03-02 11:10:44 +01:00
2020-03-18 17:37:51 +01:00
// HSV Filter
2020-02-29 18:00:36 +01:00
if (!m_action_filterHSV)
{
m_action_filterHSV = new QAction("Filter HSV", this);
m_action_filterHSV->setToolTip("Filter the points on the selected cloud by HSV color");
m_action_filterHSV->setIcon(getIcon());
// Connect appropriate signal
connect(m_action_filterHSV, &QAction::triggered, this, &ColorimetricSegmenter::filterHSV);
connect(m_action_filterHSV, SIGNAL(newEntity(ccHObject*)), this, SLOT(handleNewEntity(ccHObject*)));
connect(m_action_filterHSV, SIGNAL(entityHasChanged(ccHObject*)), this, SLOT(handleEntityChange(ccHObject*)));
connect(m_action_filterHSV, SIGNAL(newErrorMessage(QString)), this, SLOT(handleErrorMessage(QString)));
2020-05-02 11:04:24 +02:00
}
2020-04-29 19:19:46 +02:00
2020-05-02 11:04:24 +02:00
// Scalar filter
if (!m_action_filterScalar)
{
m_action_filterScalar = new QAction("Filter scalar", this);
m_action_filterScalar->setToolTip("Filter the points on the selected cloud using scalar field");
m_action_filterScalar->setIcon(getIcon());
2020-04-29 19:19:46 +02:00
2020-05-02 11:04:24 +02:00
// Connect appropriate signal
connect(m_action_filterScalar, &QAction::triggered, this, &ColorimetricSegmenter::filterScalar);
2020-05-02 11:04:24 +02:00
connect(m_action_filterScalar, SIGNAL(newEntity(ccHObject*)), this, SLOT(handleNewEntity(ccHObject*)));
connect(m_action_filterScalar, SIGNAL(entityHasChanged(ccHObject*)), this, SLOT(handleEntityChange(ccHObject*)));
connect(m_action_filterScalar, SIGNAL(newErrorMessage(QString)), this, SLOT(handleErrorMessage(QString)));
2020-04-29 19:19:46 +02:00
2020-05-02 11:04:24 +02:00
}
if (!m_action_ToonMapping_Hist)
{
// Here we use the default plugin name, description, and icon,
// but each action should have its own.
m_action_ToonMapping_Hist = new QAction("Histogram Clustering", this);
m_action_ToonMapping_Hist->setToolTip("Quantified cloud generator");
m_action_ToonMapping_Hist->setIcon(getIcon());
2020-05-02 10:27:30 +02:00
2020-05-02 11:04:24 +02:00
// Connect appropriate signal
connect(m_action_ToonMapping_Hist, &QAction::triggered, this, &ColorimetricSegmenter::HistogramClustering);
2020-05-02 10:27:30 +02:00
2020-05-02 11:04:24 +02:00
connect(m_action_ToonMapping_Hist, SIGNAL(newEntity(ccHObject*)), this, SLOT(handleNewEntity(ccHObject*)));
connect(m_action_ToonMapping_Hist, SIGNAL(entityHasChanged(ccHObject*)), this, SLOT(handleEntityChange(ccHObject*)));
connect(m_action_ToonMapping_Hist, SIGNAL(newErrorMessage(QString)), this, SLOT(handleErrorMessage(QString)));
2020-05-02 10:27:30 +02:00
2020-05-02 11:04:24 +02:00
}
2020-05-02 10:27:30 +02:00
if (!m_action_ToonMapping_KMeans)
{
// Here we use the default plugin name, description, and icon,
// but each action should have its own.
m_action_ToonMapping_KMeans = new QAction("Kmeans Clustering", this);
m_action_ToonMapping_KMeans->setToolTip("Quantified cloud generator");
m_action_ToonMapping_KMeans->setIcon(getIcon());
// Connect appropriate signal
connect(m_action_ToonMapping_KMeans, &QAction::triggered, this, &ColorimetricSegmenter::KmeansClustering);
connect(m_action_ToonMapping_KMeans, SIGNAL(newEntity(ccHObject*)), this, SLOT(handleNewEntity(ccHObject*)));
connect(m_action_ToonMapping_KMeans, SIGNAL(entityHasChanged(ccHObject*)), this, SLOT(handleEntityChange(ccHObject*)));
connect(m_action_ToonMapping_KMeans, SIGNAL(newErrorMessage(QString)), this, SLOT(handleErrorMessage(QString)));
}
2020-05-02 11:04:24 +02:00
return { m_action_filterRgb, m_action_filterHSV, m_action_filterRgbWithSegmentation, m_action_filterScalar,m_action_ToonMapping_Hist, m_action_ToonMapping_KMeans };
}
2020-03-18 17:37:51 +01:00
// Get all point clouds that are selected in CC
// return a vector with ccPointCloud objects
std::vector<ccPointCloud*> ColorimetricSegmenter::getSelectedPointClouds()
{
if (m_app == nullptr)
{
// m_app should have already been initialized by CC when plugin is loaded
Q_ASSERT(false);
return std::vector<ccPointCloud*>{};
}
ccHObject::Container selectedEntities = m_app->getSelectedEntities();
std::vector<ccPointCloud*> clouds;
for (size_t i = 0; i < selectedEntities.size(); ++i)
{
if (selectedEntities[i]->isKindOf(CC_TYPES::POINT_CLOUD)) {
clouds.push_back(static_cast<ccPointCloud*> (selectedEntities[i]));
}
}
return clouds;
}
2020-03-18 17:37:51 +01:00
// Algorithm for the RGB filter
// It uses a color range with RGB values, and keeps the points with a color within that range.
void ColorimetricSegmenter::filterRgb()
2020-05-02 10:27:30 +02:00
{
2020-05-02 11:04:24 +02:00
if (m_app == nullptr)
{
// m_app should have already been initialized by CC when plugin is loaded
2020-05-02 11:04:24 +02:00
Q_ASSERT(false);
2020-05-02 10:27:30 +02:00
return;
}
2020-02-01 03:59:24 +01:00
//check valid window
if (!m_app->getActiveGLWindow())
{
m_app->dispToConsole("[ccCompass] Could not find valid 3D window.", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
return;
}
// Retrieve parameters from dialog
2020-02-06 18:58:02 +01:00
if (m_app->pickingHub()) {
m_pickingHub = m_app->pickingHub();
}
2020-05-02 10:27:30 +02:00
2020-05-02 11:04:24 +02:00
rgbDlg = new RgbDialog(m_pickingHub, (QWidget*)m_app->getMainWindow());
2020-02-06 18:58:02 +01:00
rgbDlg->show();
2020-02-06 18:58:02 +01:00
if (!rgbDlg->exec())
return;
2020-02-06 18:58:02 +01:00
// Start timer
auto start = std::chrono::high_resolution_clock::now();
2020-01-28 21:38:52 +01:00
2020-03-18 17:37:51 +01:00
// Get margin value (percent)
2020-02-06 18:58:02 +01:00
double marginError = static_cast<double>(rgbDlg->margin->value()) / 100.0;
2020-01-28 21:38:52 +01:00
2020-03-18 17:37:51 +01:00
// Get all values to make the color range with RGB values
2020-05-02 11:04:24 +02:00
int redInf = std::min(rgbDlg->red_first->value(), rgbDlg->red_second->value()) - (marginError * std::min(rgbDlg->red_first->value(), rgbDlg->red_second->value()));
int redSup = std::max(rgbDlg->red_first->value(), rgbDlg->red_second->value()) + (marginError * std::max(rgbDlg->red_first->value(), rgbDlg->red_second->value()));
2020-04-30 16:34:44 +02:00
int greenInf = std::min(rgbDlg->green_first->value(), rgbDlg->green_second->value()) - (marginError * std::min(rgbDlg->green_first->value(), rgbDlg->green_second->value()));
int greenSup = std::max(rgbDlg->green_first->value(), rgbDlg->green_second->value()) + (marginError * std::max(rgbDlg->green_first->value(), rgbDlg->green_second->value()));
2020-05-02 11:04:24 +02:00
int blueInf = std::min(rgbDlg->blue_first->value(), rgbDlg->blue_second->value()) - (marginError * std::min(rgbDlg->blue_first->value(), rgbDlg->blue_second->value()));
int blueSup = std::max(rgbDlg->blue_first->value(), rgbDlg->blue_second->value()) + (marginError * std::max(rgbDlg->blue_first->value(), rgbDlg->blue_second->value()));
2020-04-29 16:11:14 +02:00
2020-05-02 11:04:24 +02:00
redInf = (redInf < MIN_VALUE ? MIN_VALUE : redInf);
2020-04-29 00:18:47 +02:00
greenInf = (greenInf < MIN_VALUE ? MIN_VALUE : greenInf);
2020-05-02 11:04:24 +02:00
blueInf = (blueInf < MIN_VALUE ? MIN_VALUE : blueInf);
2020-04-29 00:18:47 +02:00
2020-05-02 11:04:24 +02:00
redSup = (redSup > MAX_VALUE ? MAX_VALUE : redSup);
2020-04-29 00:18:47 +02:00
greenSup = (greenSup > MAX_VALUE ? MAX_VALUE : greenSup);
2020-05-02 11:04:24 +02:00
blueSup = (blueSup > MAX_VALUE ? MAX_VALUE : blueSup);
std::vector<ccPointCloud*> clouds = getSelectedPointClouds();
for (ccPointCloud* cloud : clouds) {
if (cloud->hasColors())
{
// Use only references for speed reasons
2020-04-29 00:18:47 +02:00
CCLib::ReferenceCloud* filteredCloudInside = new CCLib::ReferenceCloud(cloud);
CCLib::ReferenceCloud* filteredCloudOutside = new CCLib::ReferenceCloud(cloud);
for (unsigned j = 0; j < cloud->size(); ++j)
{
const ccColor::Rgb& rgb = cloud->getPointColor(j);
2020-05-02 11:04:24 +02:00
(rgb.r > redInf&& rgb.r < redSup &&
rgb.g > greenInf&& rgb.g < greenSup &&
rgb.b > blueInf&& rgb.b < blueSup) ? addPoint(filteredCloudInside, j) : addPoint(filteredCloudOutside, j);
2020-01-29 18:42:32 +01:00
}
2020-04-29 00:18:47 +02:00
std::string name = "Rmin:" + std::to_string(redInf) + "/Gmin:" + std::to_string(greenInf) + "/Bmin:" + std::to_string(blueInf) +
2020-05-02 11:04:24 +02:00
"/Rmax:" + std::to_string(redSup) + "/Gmax:" + std::to_string(greenSup) + "/Bmax:" + std::to_string(blueSup);
2020-01-29 18:42:32 +01:00
2020-04-29 16:11:14 +02:00
createClouds<RgbDialog*>(rgbDlg, cloud, filteredCloudInside, filteredCloudOutside, name);
m_app->dispToConsole("[ColorimetricSegmenter] Cloud successfully filtered ! ", ccMainAppInterface::STD_CONSOLE_MESSAGE);
}
2020-05-02 11:04:24 +02:00
}
// Stop timer
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
QString s = QString::number(duration);
//Print time of execution
ccLog::Print("Time to execute : " + s + " milliseconds.");
2020-04-29 19:19:46 +02:00
}
void ColorimetricSegmenter::filterScalar()
{
2020-05-02 11:04:24 +02:00
if (m_app == nullptr)
{
// m_app should have already been initialized by CC when plugin is loaded
Q_ASSERT(false);
return;
}
//check valid window
if (!m_app->getActiveGLWindow())
{
m_app->dispToConsole("[ccCompass] Could not find valid 3D window.", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
return;
}
// Retrieve parameters from dialog
if (m_app->pickingHub()) {
m_pickingHub = m_app->pickingHub();
}
scalarDlg = new ScalarDialog(m_pickingHub, (QWidget*)m_app->getMainWindow());
scalarDlg->show();
if (!scalarDlg->exec())
return;
auto start = std::chrono::high_resolution_clock::now();
double marginError = static_cast<double>(scalarDlg->margin->value()) / 100.0;
2020-05-06 14:09:40 +02:00
ScalarType min = std::min(scalarDlg->first->value(), scalarDlg->second->value());
ScalarType max = std::max(scalarDlg->first->value(), scalarDlg->second->value());
min -= (marginError * min);
max += (marginError * max);
2020-05-02 11:04:24 +02:00
std::vector<ccPointCloud*> clouds = getSelectedPointClouds();
for (ccPointCloud* cloud : clouds) {
// Use only references for speed reasons
CCLib::ReferenceCloud* filteredCloudInside = new CCLib::ReferenceCloud(cloud);
CCLib::ReferenceCloud* filteredCloudOutside = new CCLib::ReferenceCloud(cloud);
for (unsigned j = 0; j < cloud->size(); ++j)
{
const ScalarType val = cloud->getPointScalarValue(j);
(val > min&& val < max)
? addPoint(filteredCloudInside, j) : addPoint(filteredCloudOutside, j);
}
std::string name = "min:" + std::to_string(min) + "/max:" + std::to_string(max);
createClouds<ScalarDialog*>(scalarDlg, cloud, filteredCloudInside, filteredCloudOutside, name);
m_app->dispToConsole("[ColorimetricSegmenter] Cloud successfully filtered ! ", ccMainAppInterface::STD_CONSOLE_MESSAGE);
}
// Stop timer
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
QString s = QString::number(duration);
//Print time of execution
ccLog::Print("Time to execute : " + s + " milliseconds.");
2020-02-18 17:25:36 +01:00
}
2020-04-27 11:27:40 +02:00
/**
* @brief knnRegions Determines the neighboring regions of a region.
* @param basePointCloud The base cloud containing the points.
* @param regions The list containing all the regions in the base cloud point.
* @param region The region to compare with others.
* @param k Max number of neighbours to find.
* @param neighbours The resulting nearest regions.
* @param thresholdDistance The maximum distance to search for neighbors.
*/
2020-02-27 19:52:52 +01:00
void knnRegions(ccPointCloud* basePointCloud, std::vector<CCLib::ReferenceCloud*>* regions, const CCLib::ReferenceCloud* region, unsigned k, std::vector<CCLib::ReferenceCloud*>* neighbours, unsigned thresholdDistance) {
2020-05-02 11:04:24 +02:00
ccPointCloud* computedRegion = basePointCloud->partialClone(region);
// compute distances
CCLib::DistanceComputationTools::Cloud2CloudDistanceComputationParams params = CCLib::DistanceComputationTools::Cloud2CloudDistanceComputationParams();
params.kNNForLocalModel = k;
params.maxSearchDist = thresholdDistance;
// create to array, one containing regions, and another containing the distances to these regions.
std::vector<CCLib::ReferenceCloud*>* tempNeighbours = new std::vector<CCLib::ReferenceCloud*>();
std::vector<int>* distances = new std::vector<int>();
for (CCLib::ReferenceCloud* r : *regions)
{
distances->push_back(CCLib::DistanceComputationTools::computeCloud2CloudDistance(computedRegion, basePointCloud->partialClone(r), params));
tempNeighbours->push_back(r);
}
// sort the vectors
std::vector<int>* index = new std::vector<int>(tempNeighbours->size());
int n = 0;
std::generate(index->begin(), index->end(),
[n]
()
mutable
{
return n++;
});
std::sort(
index->begin(), index->end(),
[&](int a, int b) { return distances[a] < distances[b]; });
// then extract the 'k' nearest neighbors.
for (int i : *index)
{
if (neighbours->size() < k)
{
neighbours->push_back(tempNeighbours->at(i));
}
}
2020-03-14 13:08:12 +01:00
2020-02-27 19:52:52 +01:00
}
2020-04-27 11:27:40 +02:00
/**
* @brief colorimetricalDifference Compute colorimetrical difference between two RGB color values.
* @param c1 First color value.
* @param c2 Second color value.
* @return Colorimetrical difference.
*/
2020-02-26 13:43:06 +01:00
double colorimetricalDifference(ccColor::Rgb c1, ccColor::Rgb c2) {
2020-05-02 11:04:24 +02:00
return sqrt(pow(c1.r - c2.r, 2) + pow(c1.g - c2.g, 2) + pow(c1.b - c2.b, 2));
2020-02-26 13:43:06 +01:00
}
2020-03-02 11:10:44 +01:00
ccColor::Rgb* meanRgb(ccPointCloud* basePointCloud, CCLib::ReferenceCloud* c)
{
2020-05-02 11:04:24 +02:00
unsigned red = 0;
unsigned green = 0;
unsigned blue = 0;
for (unsigned j = 0; j < c->size(); ++j)
{
const ccColor::Rgb& pRgb = basePointCloud->getPointColor(c->getPointGlobalIndex(j));
red += pRgb.r;
green += pRgb.g;
blue += pRgb.b;
}
ccColor::Rgb* rgb = new ccColor::Rgb();
rgb->r = red / c->size();
rgb->g = green / c->size();
rgb->b = blue / c->size();
return rgb;
2020-03-02 11:10:44 +01:00
}
/**
2020-04-27 11:27:40 +02:00
* @brief colorimetricalDifference compute mean colorimetrical difference between two reference clouds.
* The points in both clouds must be represented in RGB value.
* @param basePointCloud The base cloud on which the reference clouds are based.
* @param c1 The first reference cloud.
* @param c2 The second reference cloud.
* @return Colorimetrical difference.
*/
2020-02-27 20:51:43 +01:00
double colorimetricalDifference(ccPointCloud* basePointCloud, CCLib::ReferenceCloud* c1, CCLib::ReferenceCloud* c2) {
2020-05-02 11:04:24 +02:00
ccColor::Rgb* rgb1 = meanRgb(basePointCloud, c1);
2020-05-02 11:04:24 +02:00
ccColor::Rgb* rgb2 = meanRgb(basePointCloud, c2);
2020-02-27 20:51:43 +01:00
2020-05-02 11:04:24 +02:00
return colorimetricalDifference(*rgb1, *rgb2);
2020-02-27 20:51:43 +01:00
}
2020-02-26 13:43:06 +01:00
2020-04-19 15:57:39 +02:00
std::vector<CCLib::ReferenceCloud*>* ColorimetricSegmenter::regionGrowing(ccPointCloud* pointCloud, const unsigned TNN, const double TPP, const double TD)
2020-02-27 19:52:52 +01:00
{
2020-05-02 11:04:24 +02:00
std::vector<unsigned> unlabeledPoints;
for (unsigned j = 0; j < pointCloud->size(); ++j)
{
unlabeledPoints.push_back(j);
}
std::vector<CCLib::ReferenceCloud*>* regions = new std::vector<CCLib::ReferenceCloud*>();
std::vector<unsigned>* points = new std::vector<unsigned>();
CCLib::DgmOctree* octree = new CCLib::DgmOctree(pointCloud);// used to search nearest neighbors
octree->build();
// while there is any point in {P} that hasnt been labeled
while (unlabeledPoints.size() > 0)
{
// push an unlabeled point into stack Points
points->push_back(unlabeledPoints.back());
unlabeledPoints.pop_back();
// initialize a new region Rc and add current point to R
CCLib::ReferenceCloud* rc = new CCLib::ReferenceCloud(pointCloud);
rc->addPointIndex(unlabeledPoints.back());
// while stack Points is not empty
while (points->size() > 0)
{
// pop Points top element Tpoint
unsigned tPointIndex = points->back();
points->pop_back();
// for each point p in {KNNTNN(Tpoint)}
CCLib::DgmOctree::NearestNeighboursSearchStruct nNSS = CCLib::DgmOctree::NearestNeighboursSearchStruct();
nNSS.level = 1;
nNSS.queryPoint = *(pointCloud->getPoint(tPointIndex));
Tuple3i cellPos = Tuple3i();
octree->getCellPos(octree->getCellCode(tPointIndex), 1, cellPos, false);
nNSS.cellPos = cellPos;
CCVector3 cellCenter;
octree->computeCellCenter(octree->getCellCode(tPointIndex), 1, cellCenter);
nNSS.cellCenter = cellCenter;
nNSS.maxSearchSquareDistd = TD;
nNSS.minNumberOfNeighbors = TNN;
octree->findNearestNeighborsStartingFromCell(nNSS);
CCLib::DgmOctree::NeighboursSet knnResult = nNSS.pointsInNeighbourhood;
for (int i = 0; i < knnResult.size(); i++)
{
unsigned p = knnResult.at(i).pointIndex;
// if p is labelled
if (std::find(unlabeledPoints.begin(), unlabeledPoints.end(), p) != unlabeledPoints.end())
{
continue;
}
// if CD(Tpoint,p)<TPP
if (colorimetricalDifference(pointCloud->getPointColor(p), pointCloud->getPointColor(tPointIndex)) < TPP)
{
points->push_back(p);
rc->addPointIndex(p);
}
}
}
regions->push_back(rc);
}
return regions;
}
2020-02-27 19:52:52 +01:00
2020-04-27 11:27:40 +02:00
/**
* @brief findRegion Find a given region in a vector of reference clouds.
* @param container Container containing all the regions.
* @param region Region to search for in the vector.
* @return The pointer to the region if found, nullptr in the other case.
*/
2020-02-27 20:51:43 +01:00
std::vector<CCLib::ReferenceCloud*>* findRegion(std::vector<std::vector<CCLib::ReferenceCloud*>*>* container, CCLib::ReferenceCloud* region)
{
2020-05-02 11:04:24 +02:00
if (container->size() == 0)
{
return nullptr;
}
for (std::vector<CCLib::ReferenceCloud*>* l : *container)
{
if (std::find(l->begin(), l->end(), region) != l->end())
{
return l;
}
}
return nullptr;
2020-02-27 20:51:43 +01:00
}
2020-04-19 15:57:39 +02:00
std::vector<CCLib::ReferenceCloud*>* ColorimetricSegmenter::regionMergingAndRefinement(ccPointCloud* basePointCloud, std::vector<CCLib::ReferenceCloud*>* regions, const unsigned TNN, const double TRR, const double TD, const unsigned Min)
2020-02-27 19:52:52 +01:00
{
2020-05-02 11:04:24 +02:00
std::vector<std::vector<CCLib::ReferenceCloud*>*>* homogeneous = new std::vector<std::vector<CCLib::ReferenceCloud*>*>();
// for each region Ri in {R}
for (CCLib::ReferenceCloud* ri : *regions)
{
// if Ri is not in {H}
if (findRegion(homogeneous, ri) == nullptr)
{
// create a new list to record Ri
std::vector<CCLib::ReferenceCloud*>* newRegionGroup = new std::vector<CCLib::ReferenceCloud*>();
newRegionGroup->push_back(ri);
homogeneous->push_back(newRegionGroup);
}
// for each region Rj in {KNNTNN2,TD2(Ri)}
std::vector<CCLib::ReferenceCloud*>* knnResult = new std::vector<CCLib::ReferenceCloud*>();
knnRegions(basePointCloud, regions, ri, TNN, knnResult, TD);
for (CCLib::ReferenceCloud* rj : *knnResult)
{
// if CD(Ri,Rj)<TRR
if (colorimetricalDifference(basePointCloud, ri, rj) < TNN)
{
// if Rj is in {H}
std::vector<CCLib::ReferenceCloud*>* regionContainer = findRegion(homogeneous, rj);
if (regionContainer != nullptr)
{
continue;
}
else
{
// add Rj to the list which contains Ri
regionContainer->push_back(rj);
}
}
}
}
// merge all the regions in the same list in {H} and get {R}
std::vector<CCLib::ReferenceCloud*>* mergedRegionsRef = new std::vector<CCLib::ReferenceCloud*>();
for (std::vector<CCLib::ReferenceCloud*>* l : *homogeneous)
{
CCLib::ReferenceCloud* merged = l->at(0);
for (int i = 1; i < l->size(); i++)
{
merged->add(*l->at(i));
}
mergedRegionsRef->push_back(merged);
}
std::vector<CCLib::ReferenceCloud*>* knnResult;
// for each region Ri in {R}
/*for (CCLib::ReferenceCloud* r : *mergedRegionsRef)
{
// if sizeof(Ri)<Min
if(r->size() < Min)
{
// merge Ri to its nearest neighbors
knnRegions(basePointCloud, mergedRegionsRef, r, 1, knnResult, 0);
mergedRegionsRef.
}
}*/
//Return the merged and refined {R}
return mergedRegionsRef;
2020-03-02 11:10:44 +01:00
}
void ColorimetricSegmenter::filterRgbWithSegmentation()
{
2020-05-02 11:04:24 +02:00
if (m_app == nullptr)
{
// m_app should have already been initialized by CC when plugin is loaded
Q_ASSERT(false);
return;
}
// Retrieve parameters from dialog
rgbDlg = new RgbDialog(m_pickingHub, (QWidget*)m_app->getMainWindow());
auto start = std::chrono::high_resolution_clock::now();
if (!rgbDlg->exec())
return;
// Get margin value (percent)
double marginError = static_cast<double>(rgbDlg->margin->value()) / 100.0;
// Get all values to make the color range with RGB values
int redInf = rgbDlg->red_first->value() - (marginError * rgbDlg->red_first->value());
int redSup = rgbDlg->red_second->value() + marginError * rgbDlg->red_second->value();
int greenInf = rgbDlg->green_first->value() - marginError * rgbDlg->green_first->value();
int greenSup = rgbDlg->green_second->value() + marginError * rgbDlg->green_second->value();
int blueInf = rgbDlg->blue_first->value() - marginError * rgbDlg->blue_first->value();
int blueSup = rgbDlg->blue_second->value() + marginError * rgbDlg->blue_second->value();
std::vector<ccPointCloud*> clouds = getSelectedPointClouds();
for (ccPointCloud* cloud : clouds) {
if (cloud->hasColors())
{
std::vector<CCLib::ReferenceCloud*>* regions = regionGrowing(cloud, TNN, TPP, TD);
regions = regionMergingAndRefinement(cloud, regions, TNN, TRR, TD, Min);
//m_app->dispToConsole(QString("[ColorimetricSegmenter] regions %1").arg(regions->size()), ccMainAppInterface::STD_CONSOLE_MESSAGE);
// retrieve the nearest region (in color range)
for (CCLib::ReferenceCloud* r : *regions)
{
ccColor::Rgb* mean = meanRgb(cloud, r);
if (redInf >= mean->r && redSup <= mean->r &&
greenInf >= mean->g && greenSup <= mean->g &&
blueInf >= mean->b && blueSup <= mean->b)
{
ccPointCloud* newCloud = cloud->partialClone(r);
cloud->setEnabled(false);
if (cloud->getParent()) {
cloud->getParent()->addChild(newCloud);
}
m_app->addToDB(newCloud, false, true, false, false);
m_app->dispToConsole("[ColorimetricSegmenter] Cloud successfully filtered with segmentation ! ", ccMainAppInterface::STD_CONSOLE_MESSAGE);
}
}
}
}
2020-02-06 18:58:02 +01:00
// Stop timer
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
QString s = QString::number(duration);
2020-03-18 17:37:51 +01:00
2020-02-06 18:58:02 +01:00
//Print time of execution
2020-03-18 17:37:51 +01:00
ccLog::Print("Time to execute : " + s + " milliseconds.");
}
2020-02-01 03:59:24 +01:00
2020-03-18 17:37:51 +01:00
// Algorithm for the HSV filter
// It uses the Hue-Saturation-Value (HSV) color space to filter the point cloud
2020-02-29 18:00:36 +01:00
void ColorimetricSegmenter::filterHSV()
{
if (m_app == nullptr)
{
// m_app should have already been initialized by CC when plugin is loaded
Q_ASSERT(false);
return;
}
//check valid window
if (!m_app->getActiveGLWindow())
{
m_app->dispToConsole("[ccCompass] Could not find valid 3D window.", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
return;
}
// Retrieve parameters from dialog
if (m_app->pickingHub()) {
m_pickingHub = m_app->pickingHub();
}
hsvDlg = new HSVDialog(m_pickingHub, (QWidget*)m_app->getMainWindow());
hsvDlg->show();
if (!hsvDlg->exec())
return;
// Start timer
auto start = std::chrono::high_resolution_clock::now();
2020-03-18 17:37:51 +01:00
// Get HSV values
2020-02-29 18:00:36 +01:00
hsv hsv_first;
hsv_first.h = hsvDlg->hue_first->value();
hsv_first.s = hsvDlg->sat_first->value();
hsv_first.v = hsvDlg->val_first->value();
std::vector<ccPointCloud*> clouds = getSelectedPointClouds();
for (ccPointCloud* cloud : clouds) {
2020-03-18 17:37:51 +01:00
if (cloud->hasColors()) {
2020-02-29 18:00:36 +01:00
// Use only references for speed reasons
2020-04-29 16:11:14 +02:00
CCLib::ReferenceCloud* filteredCloudInside = new CCLib::ReferenceCloud(cloud);
CCLib::ReferenceCloud* filteredCloudOutside = new CCLib::ReferenceCloud(cloud);
2020-02-29 18:00:36 +01:00
2020-03-18 17:37:51 +01:00
// We manually add color ranges with HSV values
2020-02-29 18:00:36 +01:00
for (unsigned j = 0; j < cloud->size(); ++j)
{
const ccColor::Rgb& rgb = cloud->getPointColor(j);
hsv hsv_current = hsvDlg->rgb2hsv(rgb);
2020-05-02 10:27:30 +02:00
2020-03-18 17:37:51 +01:00
// Hue is useless here because the saturation is not high enough
2020-05-02 11:04:24 +02:00
if (0 <= hsv_first.s && hsv_first.s <= 25 && 0 <= hsv_current.s && hsv_current.s <= 25)
2020-02-29 18:00:36 +01:00
{
2020-03-18 17:37:51 +01:00
// We only check value
2020-05-02 11:04:24 +02:00
if (hsv_first.v >= 0 && hsv_first.v <= 25 && 0 <= hsv_current.v && hsv_current.v <= 25) addPoint(filteredCloudInside, j); // black
else if (hsv_first.v > 25 && hsv_first.v <= 60 && hsv_current.v > 25 && hsv_current.v <= 60) addPoint(filteredCloudInside, j); // grey
2020-04-29 16:11:14 +02:00
else if (hsv_first.v > 60 && hsv_first.v <= 100 && hsv_current.v > 60 && hsv_current.v <= 100) addPoint(filteredCloudInside, j); // white
else addPoint(filteredCloudOutside, j);
2020-02-29 18:00:36 +01:00
}
2020-03-18 17:37:51 +01:00
else if (hsv_first.s > 25 && hsv_first.s <= 100 && hsv_current.s > 25 && hsv_current.s <= 100) {
// We need to check value first
2020-04-29 16:11:14 +02:00
if (0 <= hsv_first.v && hsv_first.v <= 25 && 0 <= hsv_current.v && hsv_current.v <= 25) addPoint(filteredCloudInside, j); // black
2020-03-18 17:37:51 +01:00
// Then, we can check value
2020-05-02 10:27:30 +02:00
else if (hsv_first.v > 25 && hsv_first.v <= 100 && hsv_current.v > 25 && hsv_current.v <= 100)
2020-03-18 17:37:51 +01:00
{
2020-05-02 11:04:24 +02:00
if (((hsv_first.h >= 0 && hsv_first.h <= 30) || (hsv_first.h >= 330 && hsv_first.h <= 360)) &&
((hsv_current.h >= 0 && hsv_current.h <= 30) || (hsv_current.h >= 330 && hsv_current.h <= 360))) addPoint(filteredCloudInside, j); // red
else if (hsv_first.h > 30 && hsv_first.h <= 90 && hsv_current.h > 30 && hsv_current.h <= 90) addPoint(filteredCloudInside, j); // yellow
else if (hsv_first.h > 90 && hsv_first.h <= 150 && hsv_current.h > 90 && hsv_current.h <= 150) addPoint(filteredCloudInside, j); // green
2020-04-29 16:11:14 +02:00
else if (hsv_first.h > 150 && hsv_first.h <= 210 && hsv_current.h > 150 && hsv_current.h <= 210) addPoint(filteredCloudInside, j); // cyan
else if (hsv_first.h > 210 && hsv_first.h <= 270 && hsv_current.h > 210 && hsv_current.h <= 270) addPoint(filteredCloudInside, j); // blue
else if (hsv_first.h > 270 && hsv_first.h <= 330 && hsv_current.h > 270 && hsv_current.h <= 330) addPoint(filteredCloudInside, j); // magenta
else addPoint(filteredCloudOutside, j);
2020-02-29 18:00:36 +01:00
}
2020-04-29 16:11:14 +02:00
else addPoint(filteredCloudOutside, j);
2020-05-02 10:27:30 +02:00
}
2020-04-29 16:11:14 +02:00
else addPoint(filteredCloudOutside, j);
2020-02-29 18:00:36 +01:00
}
2020-04-29 16:11:14 +02:00
std::string name = "h:" + std::to_string((int)hsv_first.h) + "/s:" + std::to_string((int)hsv_first.s) + "/v:" + std::to_string((int)hsv_first.v);
createClouds<HSVDialog*>(hsvDlg, cloud, filteredCloudInside, filteredCloudOutside, name);
2020-02-29 18:00:36 +01:00
m_app->dispToConsole("[ColorimetricSegmenter] Cloud successfully filtered ! ", ccMainAppInterface::STD_CONSOLE_MESSAGE);
}
}
// Stop timer
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
QString s = QString::number(duration);
2020-03-18 17:37:51 +01:00
2020-02-29 18:00:36 +01:00
//Print time of execution
2020-03-18 17:37:51 +01:00
ccLog::Print("Time to execute : " + s + " milliseconds");
2020-02-27 19:52:52 +01:00
}
2020-03-02 11:10:44 +01:00
2020-04-29 16:15:26 +02:00
// Method to add point to a ReferenceCloud*
2020-02-29 18:00:36 +01:00
void ColorimetricSegmenter::addPoint(CCLib::ReferenceCloud* filteredCloud, unsigned int j)
{
if (!filteredCloud->addPointIndex(j))
{
//not enough memory
delete filteredCloud;
filteredCloud = nullptr;
m_app->dispToConsole("[ColorimetricSegmenter] Error, filter canceled.");
}
}
2020-04-29 00:18:47 +02:00
2020-04-29 16:15:26 +02:00
// Method to interact with the component "Which points to keep"
2020-04-29 16:11:14 +02:00
template <typename T>
void ColorimetricSegmenter::createClouds(T& dlg, ccPointCloud* cloud, CCLib::ReferenceCloud* filteredCloudInside, CCLib::ReferenceCloud* filteredCloudOutside, std::string name)
2020-04-29 00:18:47 +02:00
{
2020-05-02 10:27:30 +02:00
2020-04-29 16:11:14 +02:00
if (dlg->retain->isChecked()) {
2020-04-29 00:18:47 +02:00
createCloud(cloud, filteredCloudInside, name, true);
}
2020-04-29 16:11:14 +02:00
else if (dlg->exclude->isChecked()) {
2020-04-29 00:18:47 +02:00
createCloud(cloud, filteredCloudOutside, name, false);
}
2020-04-29 16:11:14 +02:00
else if (dlg->both->isChecked()) {
2020-04-29 00:18:47 +02:00
createCloud(cloud, filteredCloudInside, name, true);
createCloud(cloud, filteredCloudOutside, name, false);
}
2020-05-02 10:27:30 +02:00
2020-04-29 00:18:47 +02:00
}
2020-04-29 16:15:26 +02:00
// Method to create a new cloud
2020-04-29 00:18:47 +02:00
void ColorimetricSegmenter::createCloud(ccPointCloud* cloud, CCLib::ReferenceCloud* referenceCloud, std::string name, bool inside) {
ccPointCloud* newCloud = cloud->partialClone(referenceCloud);
if (inside) {
name += ".inside";
}
else {
name += ".outside";
}
newCloud->setName(QString::fromStdString(name));
cloud->setEnabled(false);
if (cloud->getParent()) {
cloud->getParent()->addChild(newCloud);
}
m_app->addToDB(newCloud, false, true, false, false);
}
2020-05-02 10:27:30 +02:00
/**
Generate nxnxn clusters of points according to their color value (RGB)
@param cloud : the cloud which we work with
@param clusterPerDim : coefficient uses to split each RGB component
Returns a map of nxnxn keys, for each key a vector of the points index in the partition
*/
std::map<int, std::vector<unsigned>> getKeyCluster(const ccPointCloud& cloud, int clusterPerDim) {
float clusterSize = 256 / clusterPerDim;
std::map<int, std::vector<unsigned>> keyMap;
std::map<int, std::vector<unsigned>>::iterator it;
for (unsigned i = 0; i < cloud.size(); i++) {
const ccColor::Rgb& rgb = cloud.getPointColor(i);
int redCluster = rgb.r / clusterSize;
int greenCluster = rgb.g / clusterSize;
int blueCluster = rgb.b / clusterSize;
2020-05-02 11:04:24 +02:00
int index = redCluster + greenCluster * 10 + blueCluster * 100;
2020-05-02 10:27:30 +02:00
it = keyMap.find(index);
//check if the entry with this index already exists
if (it == keyMap.end()) {
//if no, we create it
std::vector<unsigned> points = { i };
keyMap.insert(std::pair<int, std::vector<unsigned>>(index, points));
}
else {
//else we add the point in the container
it->second.push_back(i);
}
}
return keyMap;
}
/**
Compute the average color (RGB)
@param cloud : cloud who contains the points
@param bucket : vector of indexes of points
Returns average color (RGB)
*/
ccColor::Rgb computeAverageColor(const ccPointCloud& cloud, std::vector<unsigned> bucket) {
2020-05-02 11:04:24 +02:00
unsigned red = 0, green = 0, blue = 0;
2020-05-02 10:27:30 +02:00
unsigned length = bucket.size();
//other formula to compute the average can be used
for (unsigned point : bucket) {
const ccColor::Rgb rgb = cloud.getPointColor(point);
2020-05-02 11:04:24 +02:00
red += rgb.r;
2020-05-02 10:27:30 +02:00
green += rgb.g;
blue += rgb.b;
}
red = floor(red / length);
blue = floor(blue / length);
green = floor(green / length);
ccColor::Rgb res;
res.r = static_cast<unsigned char>(red); res.b = static_cast<unsigned char>((blue)); res.g = static_cast<unsigned char>((green));
return res;
}
/**
Compute the distance between two colors
/!\ the formula can be modified, here it is simple to be as quick as possible
*/
double ColorDistance(ccColor::Rgb c1, ccColor::Rgb c2) {
2020-05-02 11:04:24 +02:00
return (c1.r - c2.r) + (c1.b - c2.b) + (c1.g - c2.g);
2020-05-02 10:27:30 +02:00
}
/**
Generate a pointcloud quantified using an histogram clustering
The purpose is to counter luminance variation due to the merge of different scans
*/
void ColorimetricSegmenter::HistogramClustering() {
if (m_app == nullptr)
{
// m_app should have already been initialized by CC when plugin is loaded
Q_ASSERT(false);
return;
}
// creation of the window
2020-05-02 11:04:24 +02:00
quantiDlg = new QuantiDialog((QWidget*)m_app->getMainWindow());
if (!quantiDlg->exec())
2020-05-02 10:27:30 +02:00
return;
// Start timer
auto start = std::chrono::high_resolution_clock::now();
2020-05-02 11:04:24 +02:00
int nbClusterByComponent = quantiDlg->area_quanti->value();
2020-05-02 10:27:30 +02:00
std::vector<ccPointCloud*> clouds = ColorimetricSegmenter::getSelectedPointClouds();
for (ccPointCloud* cloud : clouds) {
if (cloud->hasColors()) {
ccPointCloud* histCloud = cloud->cloneThis();
2020-05-02 11:04:24 +02:00
histCloud->setName(QString::fromStdString("HistogramClustering : Indice Q : " + std::to_string(nbClusterByComponent) + " //Couleurs : " + std::to_string(nbClusterByComponent * nbClusterByComponent * nbClusterByComponent)));
2020-05-02 10:27:30 +02:00
std::map<int, std::vector<unsigned>> clusterMap;
2020-05-02 11:04:24 +02:00
clusterMap = getKeyCluster(*histCloud, nbClusterByComponent);
2020-05-02 10:27:30 +02:00
2020-05-02 11:04:24 +02:00
for (std::map<int, std::vector<unsigned>>::iterator it = clusterMap.begin(), end = clusterMap.end(); it != end; it++)
2020-05-02 10:27:30 +02:00
{
ccColor::Rgb averageColor = computeAverageColor(*histCloud, it->second);
for (auto point : it->second) {
(*histCloud).setPointColor(point, averageColor);
}
}
cloud->setEnabled(false);
if (cloud->getParent()) {
cloud->getParent()->addChild(histCloud);
}
m_app->addToDB(histCloud, false, true, false, false);
m_app->dispToConsole("[ColorimetricSegmenter] Cloud successfully clustering ! ", ccMainAppInterface::STD_CONSOLE_MESSAGE);
}
}
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
QString s = QString::number(duration);
//Print time of execution
ccLog::Print("Time to execute : " + s + " milliseconds.");
}
/**
K-means algorithm
@param k : k clusters
@param it : limit of iterations before returns a result
Returns a cloud quantified
*/
2020-05-02 11:04:24 +02:00
ccPointCloud* computeKmeansClustering(ccPointCloud* theCloud, unsigned char K, int it)
2020-05-02 10:27:30 +02:00
{
//valid parameters?
if (!theCloud || K == 0)
{
assert(false);
2020-05-06 14:09:40 +02:00
return nullptr;
2020-05-02 10:27:30 +02:00
}
unsigned n = theCloud->size();
if (n == 0)
2020-05-06 14:09:40 +02:00
return nullptr;
2020-05-02 10:27:30 +02:00
//on a besoin de memoire ici !
std::vector<ccColor::Rgb> theKMeans; //K clusters centers
std::vector<unsigned char> belongings; //index of the cluster the point belongs to
std::vector<double> minDistsToMean; //distance to the nearest cluster center
std::vector<unsigned> theKNums; //number of points per clusters
std::vector<unsigned> theOldKNums; //number of points per clusters (prior to iteration)
try
{
theKMeans.resize(n);
belongings.resize(n);
minDistsToMean.resize(n);
theKNums.resize(K);
theOldKNums.resize(K);
}
catch (const std::bad_alloc&)
{
//not enough memory
2020-05-06 14:09:40 +02:00
return nullptr;
2020-05-02 10:27:30 +02:00
}
//init classes centers (regularly sampled
unsigned step = n / K;
for (unsigned char j = 0; j < K; ++j)
2020-05-02 11:04:24 +02:00
theKMeans[j] = theCloud->getPointColor(step * j);
2020-05-02 10:27:30 +02:00
//let's start
bool meansHaveMoved = false;
int iteration = 0;
do
{
meansHaveMoved = false;
++iteration;
//
std::map<unsigned char, std::vector<unsigned>> KGroups;
{
for (unsigned i = 0; i < n; ++i)
{
unsigned char minK = 0;
ccColor::Rgb color = theCloud->getPointColor(i);
minDistsToMean[i] = std::abs(ColorDistance(color, theKMeans[minK]));
//we look for the nearest cluster center
for (unsigned char j = 1; j < K; ++j)
{
double distToMean = std::abs(ColorDistance(color, theKMeans[j]));
if (distToMean < minDistsToMean[i])
{
minDistsToMean[i] = distToMean;
minK = j;
}
}
belongings[i] = minK;
//minDistsToMean[i] = V;
}
}
//compute the clusters centers
theOldKNums = theKNums;
std::fill(theKNums.begin(), theKNums.end(), static_cast<unsigned>(0));
for (unsigned i = 0; i < n; ++i)
{
auto it = KGroups.find(belongings[i]);
if (it == KGroups.end()) {
std::vector<unsigned> points = { i };
KGroups.insert(std::pair<unsigned char, std::vector<unsigned>>(belongings[i], points));
}
else {
it->second.push_back(i);
}
++theKNums[belongings[i]];
}
for (unsigned char j = 0; j < K; ++j)
{
ccColor::Rgb newMean = (KGroups[j].size() > 0 ? computeAverageColor(*theCloud, KGroups[j]) : theKMeans[j]);
if (theOldKNums[j] != theKNums[j]) {
2020-05-02 11:04:24 +02:00
meansHaveMoved = true;
2020-05-02 10:27:30 +02:00
}
theKMeans[j] = newMean;
}
} while (iteration < it);
ccPointCloud* KCloud = theCloud->cloneThis();
KCloud->setName(QString::fromStdString("Kmeans clustering : K : " + std::to_string(K)));
//set color for each cluster
for (unsigned i = 0; i < n; i++) {
(*KCloud).setPointColor(i, theKMeans[belongings[i]]);
}
return KCloud;
}
/**
Algorithm based on k-means for clustering points cloud by its colors
*/
void ColorimetricSegmenter::KmeansClustering() {
kmeansDlg = new KmeansDlg((QWidget*)m_app->getMainWindow());
2020-05-02 11:04:24 +02:00
if (!kmeansDlg->exec())
2020-05-02 10:27:30 +02:00
return;
// Start timer
auto start = std::chrono::high_resolution_clock::now();
std::vector<ccPointCloud*> clouds = ColorimetricSegmenter::getSelectedPointClouds();
for (ccPointCloud* cloud : clouds)
{
2020-05-02 11:04:24 +02:00
ccPointCloud* kcloud = computeKmeansClustering(cloud, kmeansDlg->spinBox_k->value(), kmeansDlg->spinBox_it->value());
2020-05-02 10:27:30 +02:00
2020-05-02 11:04:24 +02:00
cloud->setEnabled(false);
if (cloud->getParent())
{
cloud->getParent()->addChild(kcloud);
}
2020-05-02 10:27:30 +02:00
2020-05-02 11:04:24 +02:00
m_app->addToDB(kcloud, false, true, false, false);
m_app->dispToConsole("[ColorimetricSegmenter] Cloud successfully clustering ! ", ccMainAppInterface::STD_CONSOLE_MESSAGE);
2020-05-02 10:27:30 +02:00
}
// Stop timer
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
QString s = QString::number(duration);
//Print time of execution
ccLog::Print("Time to execute : " + s + " milliseconds");
}