Added inliers curve

Added incremental vocabulary option
Multi-threaded object features extraction
Moved Statistics to its own dock widget

git-svn-id: http://find-object.googlecode.com/svn/trunk/find_object@268 620bd6b2-0a58-f614-fd9a-1bd335dccda9
This commit is contained in:
matlabbe 2014-05-11 23:57:08 +00:00
parent 3bd58086d2
commit 228cce72bf
10 changed files with 693 additions and 322 deletions

View File

@ -46,6 +46,7 @@ SET(SRC_FILES
../src/ObjWidget.cpp ../src/ObjWidget.cpp
../src/AboutDialog.cpp ../src/AboutDialog.cpp
../src/TcpServer.cpp ../src/TcpServer.cpp
../src/Vocabulary.cpp
../src/utilite/UPlot.cpp ../src/utilite/UPlot.cpp
../src/utilite/UDirectory.cpp ../src/utilite/UDirectory.cpp
../src/utilite/UFile.cpp ../src/utilite/UFile.cpp

View File

@ -63,7 +63,6 @@ int Camera::getCurrentFrameIndex()
void Camera::moveToFrame(int frame) void Camera::moveToFrame(int frame)
{ {
printf("Moved to frame %d.\n", frame);
if(frame < images_.size()) if(frame < images_.size())
{ {
currentImageIndex_ = frame; currentImageIndex_ = frame;

View File

@ -45,6 +45,7 @@ MainWindow::MainWindow(Camera * camera, const QString & settings, QWidget * pare
camera_(camera), camera_(camera),
settings_(settings), settings_(settings),
likelihoodCurve_(0), likelihoodCurve_(0),
inliersCurve_(0),
lowestRefreshRate_(99), lowestRefreshRate_(99),
objectsModified_(false), objectsModified_(false),
tcpServer_(0) tcpServer_(0)
@ -55,7 +56,11 @@ MainWindow::MainWindow(Camera * camera, const QString & settings, QWidget * pare
this->setStatusBar(new QStatusBar()); this->setStatusBar(new QStatusBar());
likelihoodCurve_ = new rtabmap::PdfPlotCurve("Likelihood", &imagesMap_, this); likelihoodCurve_ = new rtabmap::PdfPlotCurve("Likelihood", &imagesMap_, this);
inliersCurve_ = new rtabmap::PdfPlotCurve("Inliers", &imagesMap_, this);
likelihoodCurve_->setPen(QPen(Qt::blue));
inliersCurve_->setPen(QPen(Qt::red));
ui_->likelihoodPlot->addCurve(likelihoodCurve_, false); ui_->likelihoodPlot->addCurve(likelihoodCurve_, false);
ui_->likelihoodPlot->addCurve(inliersCurve_, false);
ui_->likelihoodPlot->setGraphicsView(true); ui_->likelihoodPlot->setGraphicsView(true);
if(!camera_) if(!camera_)
@ -67,6 +72,7 @@ MainWindow::MainWindow(Camera * camera, const QString & settings, QWidget * pare
camera_->setParent(this); camera_->setParent(this);
} }
ui_->dockWidget_statistics->setVisible(false);
ui_->dockWidget_parameters->setVisible(false); ui_->dockWidget_parameters->setVisible(false);
ui_->dockWidget_plot->setVisible(false); ui_->dockWidget_plot->setVisible(false);
ui_->widget_controls->setVisible(false); ui_->widget_controls->setVisible(false);
@ -87,6 +93,7 @@ MainWindow::MainWindow(Camera * camera, const QString & settings, QWidget * pare
SIGNAL(editingFinished()), SIGNAL(editingFinished()),
camera_, camera_,
SLOT(updateImageRate())); SLOT(updateImageRate()));
ui_->menuView->addAction(ui_->dockWidget_statistics->toggleViewAction());
ui_->menuView->addAction(ui_->dockWidget_parameters->toggleViewAction()); ui_->menuView->addAction(ui_->dockWidget_parameters->toggleViewAction());
ui_->menuView->addAction(ui_->dockWidget_objects->toggleViewAction()); ui_->menuView->addAction(ui_->dockWidget_objects->toggleViewAction());
ui_->menuView->addAction(ui_->dockWidget_plot->toggleViewAction()); ui_->menuView->addAction(ui_->dockWidget_plot->toggleViewAction());
@ -293,20 +300,20 @@ bool MainWindow::saveSettings(const QString & path)
int MainWindow::loadObjects(const QString & dirPath) int MainWindow::loadObjects(const QString & dirPath)
{ {
int loadedObjects = 0; int loadedObjects = 0;
QDir dir(dirPath); QString formats = Settings::getGeneral_imageFormats().remove('*').remove('.');
if(dir.exists()) UDirectory dir(dirPath.toStdString(), formats.toStdString());
if(dir.isValid())
{ {
QStringList filters = Settings::getGeneral_imageFormats().split(' '); const std::list<std::string> & names = dir.getFileNames(); // sorted in natural order
QFileInfoList list = dir.entryInfoList(filters, QDir::Files, QDir::Name); for(std::list<std::string>::const_iterator iter=names.begin(); iter!=names.end(); ++iter)
for(int i=0; i<list.size(); ++i)
{ {
this->addObjectFromFile(list.at(i).filePath()); this->addObjectFromFile((dirPath.toStdString()+dir.separator()+*iter).c_str());
} }
if(list.size()) if(names.size())
{ {
this->updateObjects(); this->updateObjects();
} }
loadedObjects = list.size(); loadedObjects = names.size();
} }
return loadedObjects; return loadedObjects;
} }
@ -320,6 +327,7 @@ void MainWindow::saveObjects(const QString & dirPath)
{ {
objects_.at(i)->pixmap().save(QString("%1/%2.bmp").arg(dirPath).arg(objects_.at(i)->id())); objects_.at(i)->pixmap().save(QString("%1/%2.bmp").arg(dirPath).arg(objects_.at(i)->id()));
} }
objectsModified_ = false;
} }
} }
@ -392,7 +400,6 @@ void MainWindow::updateObjectSize(ObjWidget * obj)
{ {
obj->setVisible(false); obj->setVisible(false);
} }
obj->setFeaturesShown(value<=50?false:true);
} }
} }
@ -627,45 +634,96 @@ void MainWindow::showObject(ObjWidget * obj)
} }
} }
class ExtractFeaturesThread : public QThread
{
public:
ExtractFeaturesThread(int objectId, int objectIndex, const cv::Mat & image) :
objectId_(objectId),
objectIndex_(objectIndex),
image_(image)
{
}
int objectId() const {return objectId_;}
int objectIndex() const {return objectIndex_;}
const cv::Mat & image() const {return image_;}
const std::vector<cv::KeyPoint> & keypoints() const {return keypoints_;}
const cv::Mat & descriptors() const {return descriptors_;}
protected:
virtual void run()
{
QTime time;
time.start();
printf("Extracting descriptors from object %d...\n", objectId_);
cv::FeatureDetector * detector = Settings::createFeaturesDetector();
keypoints_.clear();
descriptors_ = cv::Mat();
detector->detect(image_, keypoints_);
delete detector;
if(keypoints_.size())
{
cv::DescriptorExtractor * extractor = Settings::createDescriptorsExtractor();
extractor->compute(image_, keypoints_, descriptors_);
delete extractor;
if((int)keypoints_.size() != descriptors_.rows)
{
printf("ERROR : obj=%d kpt=%d != descriptors=%d\n", objectId_, (int)keypoints_.size(), descriptors_.rows);
}
}
else
{
printf("WARNING: no features detected in object %d !?!\n", objectId_);
}
printf("%d descriptors extracted from object %d (in %d ms)\n", descriptors_.rows, objectId_, time.elapsed());
}
private:
int objectId_;
int objectIndex_;
cv::Mat image_;
std::vector<cv::KeyPoint> keypoints_;
cv::Mat descriptors_;
};
void MainWindow::updateObjects() void MainWindow::updateObjects()
{ {
if(objects_.size()) if(objects_.size())
{ {
for(int i=0; i<objects_.size(); ++i) int threadCounts = Settings::getGeneral_threads();
if(threadCounts == 0)
{ {
QTime time; threadCounts = objects_.size();
time.start();
printf("Extracting descriptors from object %d...\n", objects_.at(i)->id());
const cv::Mat & img = objects_.at(i)->cvImage();
cv::FeatureDetector * detector = Settings::createFeaturesDetector();
std::vector<cv::KeyPoint> keypoints;
detector->detect(img, keypoints);
delete detector;
cv::Mat descriptors;
if(keypoints.size())
{
cv::DescriptorExtractor * extractor = Settings::createDescriptorsExtractor();
extractor->compute(img, keypoints, descriptors);
delete extractor;
if((int)keypoints.size() != descriptors.rows)
{
printf("ERROR : kpt=%d != descriptors=%d\n", (int)keypoints.size(), descriptors.rows);
}
}
else
{
printf("WARNING: no features detected in object %d !?!\n", objects_.at(i)->id());
}
printf("%d descriptors extracted from object %d (in %d ms)\n", descriptors.rows, objects_.at(i)->id(), time.elapsed());
objects_.at(i)->setData(keypoints, descriptors, img, Settings::currentDetectorType(), Settings::currentDescriptorType());
//update object labels
QLabel * title = qFindChild<QLabel*>(this, QString("%1title").arg(objects_.at(i)->id()));
title->setText(QString("%1 (%2)").arg(objects_.at(i)->id()).arg(QString::number(objects_.at(i)->keypoints().size())));
QLabel * detectorDescriptorType = qFindChild<QLabel*>(this, QString("%1type").arg(objects_.at(i)->id()));
detectorDescriptorType->setText(QString("%1/%2").arg(objects_.at(i)->detectorType()).arg(objects_.at(i)->descriptorType()));
} }
QTime time;
time.start();
printf("Features extraction from %d objects...\n", objects_.size());
for(int i=0; i<objects_.size(); i+=threadCounts)
{
QVector<ExtractFeaturesThread*> threads;
for(int k=i; k<i+threadCounts && k<objects_.size(); ++k)
{
threads.push_back(new ExtractFeaturesThread(objects_.at(k)->id(), k, objects_.at(k)->cvImage()));
threads.back()->start();
}
for(int j=0; j<threads.size(); ++j)
{
threads[j]->wait();
int index = threads[j]->objectIndex();
objects_.at(index)->setData(threads[j]->keypoints(), threads[j]->descriptors(), threads[j]->image(), Settings::currentDetectorType(), Settings::currentDescriptorType());
//update object labels
QLabel * title = qFindChild<QLabel*>(this, QString("%1title").arg(objects_.at(index)->id()));
title->setText(QString("%1 (%2)").arg(objects_.at(index)->id()).arg(QString::number(objects_.at(index)->keypoints().size())));
QLabel * detectorDescriptorType = qFindChild<QLabel*>(this, QString("%1type").arg(objects_.at(index)->id()));
detectorDescriptorType->setText(QString("%1/%2").arg(objects_.at(index)->detectorType()).arg(objects_.at(index)->descriptorType()));
}
}
printf("Features extraction from %d objects... done! (%d ms)\n", objects_.size(), time.elapsed());
updateData(); updateData();
} }
if(!camera_->isRunning() && !ui_->imageView_source->cvImage().empty()) if(!camera_->isRunning() && !ui_->imageView_source->cvImage().empty())
@ -688,6 +746,7 @@ void MainWindow::updateData()
objectsDescriptors_.clear(); objectsDescriptors_.clear();
dataRange_.clear(); dataRange_.clear();
vocabulary_.clear();
int count = 0; int count = 0;
int dim = -1; int dim = -1;
int type = -1; int type = -1;
@ -732,7 +791,7 @@ void MainWindow::updateData()
printf("Updating global descriptors matrix: Objects=%d, total descriptors=%d, dim=%d, type=%d\n", (int)objects_.size(), count, dim, type); printf("Updating global descriptors matrix: Objects=%d, total descriptors=%d, dim=%d, type=%d\n", (int)objects_.size(), count, dim, type);
if(Settings::getGeneral_invertedSearch() || Settings::getGeneral_threads() == 1) if(Settings::getGeneral_invertedSearch() || Settings::getGeneral_threads() == 1)
{ {
// If inverted index or only one thread, put all descriptors in the same cv::Mat // If only one thread, put all descriptors in the same cv::Mat
objectsDescriptors_.push_back(cv::Mat(count, dim, type)); objectsDescriptors_.push_back(cv::Mat(count, dim, type));
int row = 0; int row = 0;
for(int i=0; i<objects_.size(); ++i) for(int i=0; i<objects_.size(); ++i)
@ -748,18 +807,47 @@ void MainWindow::updateData()
dataRange_.insert(row-1, i); dataRange_.insert(row-1, i);
} }
} }
if(Settings::getGeneral_invertedSearch()) if(Settings::getGeneral_invertedSearch())
{ {
printf("Creating FLANN index (%s) with objects' descriptors...\n", Settings::currentNearestNeighborType().toStdString().c_str());
// CREATE INDEX
QTime time; QTime time;
time.start(); time.start();
cv::flann::IndexParams * params = Settings::createFlannIndexParams(); bool incremental = Settings::getGeneral_incrementalVocabulary();
flannIndex_.build(objectsDescriptors_[0], *params, Settings::getFlannDistanceType()); if(incremental)
delete params; {
printf("Creating incremental vocabulary...\n");
}
else
{
printf("Creating vocabulary...\n");
}
QTime localTime;
localTime.start();
for(int i=0; i<objects_.size(); ++i)
{
QMultiMap<int, int> words = vocabulary_.addWords(objects_[i]->descriptors(), i, incremental);
objects_[i]->setWords(words);
printf("Object %d, %d words from %d descriptors (%d words, %d ms)\n",
objects_[i]->id(),
words.uniqueKeys().size(),
objects_[i]->descriptors().rows,
vocabulary_.size(),
localTime.restart());
}
if(!incremental)
{
vocabulary_.update();
}
ui_->label_timeIndexing->setNum(time.elapsed()); ui_->label_timeIndexing->setNum(time.elapsed());
ui_->label_vocabularySize->setNum(objectsDescriptors_[0].rows); ui_->label_vocabularySize->setNum(vocabulary_.size());
printf("Creating FLANN index (%s) with objects' descriptors... done! (%d ms)\n", Settings::currentNearestNeighborType().toStdString().c_str(), time.elapsed()); if(incremental)
{
printf("Creating incremental vocabulary... done! size=%d (%d ms)\n", vocabulary_.size(), time.elapsed());
}
else
{
printf("Creating vocabulary... done! size=%d (%d ms)\n", vocabulary_.size(), time.elapsed());
}
} }
} }
else else
@ -879,10 +967,11 @@ void MainWindow::moveCameraFrame(int frame)
class SearchThread: public QThread class SearchThread: public QThread
{ {
public: public:
SearchThread(cv::flann::Index * index, int objectIndex, const cv::Mat * descriptors) : SearchThread(Vocabulary * vocabulary, int objectIndex, const cv::Mat * descriptors, const ObjWidget * sceneObject) :
index_(index), vocabulary_(vocabulary),
objectIndex_(objectIndex), objectIndex_(objectIndex),
descriptors_(descriptors), descriptors_(descriptors),
sceneObject_(sceneObject),
minMatchedDistance_(-1.0f), minMatchedDistance_(-1.0f),
maxMatchedDistance_(-1.0f) maxMatchedDistance_(-1.0f)
{ {
@ -908,7 +997,7 @@ protected:
int k = Settings::getNearestNeighbor_3nndrRatioUsed()?2:1; int k = Settings::getNearestNeighbor_3nndrRatioUsed()?2:1;
results = cv::Mat(descriptors_->rows, k, CV_32SC1); // results index results = cv::Mat(descriptors_->rows, k, CV_32SC1); // results index
dists = cv::Mat(descriptors_->rows, k, CV_32FC1); // Distance results are CV_32FC1 dists = cv::Mat(descriptors_->rows, k, CV_32FC1); // Distance results are CV_32FC1
index_->knnSearch(*descriptors_, results, dists, k, Settings::getFlannSearchParams() ); // maximum number of leafs checked vocabulary_->search(*descriptors_, results, dists, k);
// PROCESS RESULTS // PROCESS RESULTS
// Get all matches for each object // Get all matches for each object
@ -934,6 +1023,10 @@ protected:
matched = false; matched = false;
} }
} }
if(!matched && !Settings::getNearestNeighbor_3nndrRatioUsed() && !Settings::getNearestNeighbor_5minDistanceUsed())
{
matched = true; // no criterion, match to the nearest descriptor
}
if(minMatchedDistance_ == -1 || minMatchedDistance_ > dists.at<float>(i,0)) if(minMatchedDistance_ == -1 || minMatchedDistance_ > dists.at<float>(i,0))
{ {
minMatchedDistance_ = dists.at<float>(i,0); minMatchedDistance_ = dists.at<float>(i,0);
@ -943,8 +1036,10 @@ protected:
maxMatchedDistance_ = dists.at<float>(i,0); maxMatchedDistance_ = dists.at<float>(i,0);
} }
if(matched) int wordId = results.at<int>(i,0);
if(matched && sceneObject_->words().count(wordId) == 1)
{ {
matches_.insert(i, sceneObject_->words().value(wordId));
matches_.insert(i, results.at<int>(i,0)); matches_.insert(i, results.at<int>(i,0));
} }
} }
@ -952,9 +1047,10 @@ protected:
//printf("Search Object %d time=%d ms\n", objectIndex_, time.elapsed()); //printf("Search Object %d time=%d ms\n", objectIndex_, time.elapsed());
} }
private: private:
cv::flann::Index * index_; // would be const but flann search() method is not const!? Vocabulary * vocabulary_; // would be const but flann search() method is not const!?
int objectIndex_; int objectIndex_;
const cv::Mat * descriptors_; const cv::Mat * descriptors_;
const ObjWidget * sceneObject_;
float minMatchedDistance_; float minMatchedDistance_;
float maxMatchedDistance_; float maxMatchedDistance_;
@ -1114,10 +1210,14 @@ void MainWindow::update(const cv::Mat & image)
ui_->imageView_source->setData(keypoints, cv::Mat(), image, Settings::currentDetectorType(), Settings::currentDescriptorType()); ui_->imageView_source->setData(keypoints, cv::Mat(), image, Settings::currentDetectorType(), Settings::currentDescriptorType());
} }
bool consistentNNData = (vocabulary_.size()!=0 && vocabulary_.wordToObjects().begin().value()!=-1 && Settings::getGeneral_invertedSearch()) ||
((vocabulary_.size()==0 || vocabulary_.wordToObjects().begin().value()==-1) && !Settings::getGeneral_invertedSearch());
// COMPARE // COMPARE
if(!objectsDescriptors_.empty() && if(!objectsDescriptors_.empty() &&
keypoints.size() && keypoints.size() &&
(Settings::getNearestNeighbor_3nndrRatioUsed() || Settings::getNearestNeighbor_5minDistanceUsed()) && consistentNNData &&
objectsDescriptors_[0].cols == descriptors.cols &&
objectsDescriptors_[0].type() == descriptors.type()) // binary descriptor issue, if the dataTree is not yet updated with modified settings objectsDescriptors_[0].type() == descriptors.type()) // binary descriptor issue, if the dataTree is not yet updated with modified settings
{ {
QVector<QMultiMap<int, int> > matches(objects_.size()); // Map< ObjectDescriptorIndex, SceneDescriptorIndex > QVector<QMultiMap<int, int> > matches(objects_.size()); // Map< ObjectDescriptorIndex, SceneDescriptorIndex >
@ -1129,11 +1229,15 @@ void MainWindow::update(const cv::Mat & image)
{ {
// CREATE INDEX for the scene // CREATE INDEX for the scene
//printf("Creating FLANN index (%s)\n", Settings::currentNearestNeighborType().toStdString().c_str()); //printf("Creating FLANN index (%s)\n", Settings::currentNearestNeighborType().toStdString().c_str());
cv::flann::IndexParams * params = Settings::createFlannIndexParams(); vocabulary_.clear();
flannIndex_.build(descriptors, *params, Settings::getFlannDistanceType()); QMultiMap<int, int> words = vocabulary_.addWords(descriptors, -1, Settings::getGeneral_incrementalVocabulary());
delete params; if(!Settings::getGeneral_incrementalVocabulary())
{
vocabulary_.update();
}
ui_->imageView_source->setWords(words);
ui_->label_timeIndexing->setNum(time.restart()); ui_->label_timeIndexing->setNum(time.restart());
ui_->label_vocabularySize->setNum(descriptors.rows); ui_->label_vocabularySize->setNum(vocabulary_.size());
} }
if(Settings::getGeneral_invertedSearch() || Settings::getGeneral_threads() == 1) if(Settings::getGeneral_invertedSearch() || Settings::getGeneral_threads() == 1)
@ -1147,14 +1251,14 @@ void MainWindow::update(const cv::Mat & image)
//match objects to scene //match objects to scene
results = cv::Mat(objectsDescriptors_[0].rows, k, CV_32SC1); // results index results = cv::Mat(objectsDescriptors_[0].rows, k, CV_32SC1); // results index
dists = cv::Mat(objectsDescriptors_[0].rows, k, CV_32FC1); // Distance results are CV_32FC1 dists = cv::Mat(objectsDescriptors_[0].rows, k, CV_32FC1); // Distance results are CV_32FC1
flannIndex_.knnSearch(objectsDescriptors_[0], results, dists, k, Settings::getFlannSearchParams() ); // maximum number of leafs checked vocabulary_.search(objectsDescriptors_[0], results, dists, k);
} }
else else
{ {
//match scene to objects //match scene to objects
results = cv::Mat(descriptors.rows, k, CV_32SC1); // results index results = cv::Mat(descriptors.rows, k, CV_32SC1); // results index
dists = cv::Mat(descriptors.rows, k, CV_32FC1); // Distance results are CV_32FC1 dists = cv::Mat(descriptors.rows, k, CV_32FC1); // Distance results are CV_32FC1
flannIndex_.knnSearch(descriptors, results, dists, k, Settings::getFlannSearchParams() ); // maximum number of leafs checked vocabulary_.search(descriptors, results, dists, k);
} }
// PROCESS RESULTS // PROCESS RESULTS
@ -1181,6 +1285,10 @@ void MainWindow::update(const cv::Mat & image)
matched = false; matched = false;
} }
} }
if(!matched && !Settings::getNearestNeighbor_3nndrRatioUsed() && !Settings::getNearestNeighbor_5minDistanceUsed())
{
matched = true; // no criterion, match to the nearest descriptor
}
if(minMatchedDistance == -1 || minMatchedDistance > dists.at<float>(i,0)) if(minMatchedDistance == -1 || minMatchedDistance > dists.at<float>(i,0))
{ {
minMatchedDistance = dists.at<float>(i,0); minMatchedDistance = dists.at<float>(i,0);
@ -1194,11 +1302,16 @@ void MainWindow::update(const cv::Mat & image)
{ {
if(Settings::getGeneral_invertedSearch()) if(Settings::getGeneral_invertedSearch())
{ {
QMap<int, int>::iterator iter = dataRange_.lowerBound(results.at<int>(i,0)); int wordId = results.at<int>(i,0);
int objectIndex = iter.value(); QList<int> objIndexes = vocabulary_.wordToObjects().values(wordId);
int previousDescriptorIndex = (iter == dataRange_.begin())?0:(--iter).key()+1; for(int j=0; j<objIndexes.size(); ++j)
int objectDescriptorIndex = results.at<int>(i,0) - previousDescriptorIndex; {
matches[objectIndex].insert(objectDescriptorIndex, i); // just add unique matches
if(vocabulary_.wordToObjects().count(wordId, objIndexes[j]) == 1)
{
matches[objIndexes[j]].insert(objects_.at(objIndexes[j])->words().value(wordId), i);
}
}
} }
else else
{ {
@ -1206,7 +1319,12 @@ void MainWindow::update(const cv::Mat & image)
int objectIndex = iter.value(); int objectIndex = iter.value();
int fisrtObjectDescriptorIndex = (iter == dataRange_.begin())?0:(--iter).key()+1; int fisrtObjectDescriptorIndex = (iter == dataRange_.begin())?0:(--iter).key()+1;
int objectDescriptorIndex = i - fisrtObjectDescriptorIndex; int objectDescriptorIndex = i - fisrtObjectDescriptorIndex;
matches[objectIndex].insert(objectDescriptorIndex, results.at<int>(i,0));
int wordId = results.at<int>(i,0);
if(ui_->imageView_source->words().count(wordId) == 1)
{
matches[objectIndex].insert(objectDescriptorIndex, ui_->imageView_source->words().value(wordId));
}
} }
} }
} }
@ -1225,7 +1343,7 @@ void MainWindow::update(const cv::Mat & image)
for(unsigned int k=j; k<j+threadCounts && k<objectsDescriptors_.size(); ++k) for(unsigned int k=j; k<j+threadCounts && k<objectsDescriptors_.size(); ++k)
{ {
threads.push_back(new SearchThread(&flannIndex_, k, &objectsDescriptors_[k])); threads.push_back(new SearchThread(&vocabulary_, k, &objectsDescriptors_[k], ui_->imageView_source));
threads.back()->start(); threads.back()->start();
} }
@ -1251,11 +1369,11 @@ void MainWindow::update(const cv::Mat & image)
ui_->label_timeMatching->setNum(time.restart()); ui_->label_timeMatching->setNum(time.restart());
// GUI: Homographies and color // GUI: Homographies and color
QMap<int, float> scores;
int maxScoreId = -1; int maxScoreId = -1;
int maxScore = 0; int maxScore = 0;
QMultiMap<int, QPair<QRect, QTransform> > objectsDetected; QMultiMap<int, QPair<QRect, QTransform> > objectsDetected;
QMap<int, float> inliersScores;
if(Settings::getHomography_homographyComputed()) if(Settings::getHomography_homographyComputed())
{ {
// HOMOGRAHPY // HOMOGRAHPY
@ -1264,6 +1382,7 @@ void MainWindow::update(const cv::Mat & image)
{ {
threadCounts = matches.size(); threadCounts = matches.size();
} }
for(int i=0; i<matches.size(); i+=threadCounts) for(int i=0; i<matches.size(); i+=threadCounts)
{ {
QVector<HomographyThread*> threads; QVector<HomographyThread*> threads;
@ -1282,11 +1401,12 @@ void MainWindow::update(const cv::Mat & image)
int index = threads[j]->getObjectIndex(); int index = threads[j]->getObjectIndex();
// COLORIZE (should be done in the GUI thread) // COLORIZE (should be done in the GUI thread)
int nColor = index % 11 + 7; int nColor = index % 10 + 7;
QColor color((Qt::GlobalColor)(nColor==Qt::yellow?Qt::gray:nColor)); QColor color((Qt::GlobalColor)(nColor==Qt::yellow?Qt::darkYellow:nColor));
QLabel * label = ui_->dockWidget_objects->findChild<QLabel*>(QString("%1detection").arg(objects_.at(index)->id())); QLabel * label = ui_->dockWidget_objects->findChild<QLabel*>(QString("%1detection").arg(objects_.at(index)->id()));
if(!threads[j]->getHomography().empty()) if(!threads[j]->getHomography().empty())
{ {
inliersScores.insert(objects_.at(index)->id(), (float)threads[j]->getInliers());
if(threads[j]->getInliers() >= Settings::getHomography_minimumInliers()) if(threads[j]->getInliers() >= Settings::getHomography_minimumInliers())
{ {
const cv::Mat & H = threads[j]->getHomography(); const cv::Mat & H = threads[j]->getHomography();
@ -1337,7 +1457,7 @@ void MainWindow::update(const cv::Mat & image)
} }
else if(!objectsDetected.contains(objects_.at(index)->id())) else if(!objectsDetected.contains(objects_.at(index)->id()))
{ {
objects_.at(index)->setKptColor(threads[j]->getIndexesA().at(k), QColor(0,0,0)); objects_.at(index)->setKptColor(threads[j]->getIndexesA().at(k), Qt::black);
} }
} }
} }
@ -1362,10 +1482,14 @@ void MainWindow::update(const cv::Mat & image)
} }
QPen rectPen(color); QPen rectPen(color);
rectPen.setWidth(4); rectPen.setWidth(4);
QGraphicsRectItem * rectItem = new QGraphicsRectItem(rect); QGraphicsRectItem * rectItemScene = new QGraphicsRectItem(rect);
rectItem->setPen(rectPen); rectItemScene->setPen(rectPen);
rectItem->setTransform(hTransform); rectItemScene->setTransform(hTransform);
ui_->imageView_source->addRect(rectItem); ui_->imageView_source->addRect(rectItemScene);
QGraphicsRectItem * rectItemObj = new QGraphicsRectItem(rect);
rectItemObj->setPen(rectPen);
objects_.at(index)->addRect(rectItemObj);
} }
} }
} }
@ -1376,6 +1500,7 @@ void MainWindow::update(const cv::Mat & image)
} }
else if(this->isVisible() && objectsDetected.count(objects_.at(index)->id()) == 0) else if(this->isVisible() && objectsDetected.count(objects_.at(index)->id()) == 0)
{ {
inliersScores.insert(objects_.at(index)->id(), 0.0f);
if(threads[j]->getInliers() >= Settings::getHomography_minimumInliers()) if(threads[j]->getInliers() >= Settings::getHomography_minimumInliers())
{ {
label->setText(QString("Ignored, all inliers (%1 in %2 out)").arg(matches[index].size()).arg(threads[j]->getOutliers())); label->setText(QString("Ignored, all inliers (%1 in %2 out)").arg(matches[index].size()).arg(threads[j]->getOutliers()));
@ -1402,10 +1527,12 @@ void MainWindow::update(const cv::Mat & image)
} }
QLabel * label = ui_->dockWidget_objects->findChild<QLabel*>(QString("%1detection").arg(objects_.at(i)->id())); QLabel * label = ui_->dockWidget_objects->findChild<QLabel*>(QString("%1detection").arg(objects_.at(i)->id()));
label->setText(QString("%1 matches").arg(matches[i].size())); label->setText(QString("%1 matches").arg(matches[i].size()));
inliersScores.insert(objects_.at(i)->id(), 0.0f);
} }
} }
//scores //scores
QMap<int, float> scores;
for(int i=0; i<matches.size(); ++i) for(int i=0; i<matches.size(); ++i)
{ {
int objectIndex = matchesId.at(i) >= 0? matchesId.at(i): i; int objectIndex = matchesId.at(i) >= 0? matchesId.at(i): i;
@ -1413,15 +1540,21 @@ void MainWindow::update(const cv::Mat & image)
{ {
scores.insert(objects_.at(objectIndex)->id(), matches[i].size()); scores.insert(objects_.at(objectIndex)->id(), matches[i].size());
} }
if(maxScoreId == -1 || maxScore < matches[i].size()) // If objects detected, set max score to one detected with the most
// matches. Otherwise select any objects with the most matches.
if(objectsDetected.empty() || objectsDetected.contains(objects_.at(objectIndex)->id()))
{ {
maxScoreId = objects_.at(objectIndex)->id(); if(maxScoreId == -1 || maxScore < matches[i].size())
maxScore = matches[i].size(); {
maxScoreId = objects_.at(objectIndex)->id();
maxScore = matches[i].size();
}
} }
} }
//update likelihood plot //update likelihood plot
likelihoodCurve_->setData(scores, QMap<int, int>()); likelihoodCurve_->setData(scores, QMap<int, int>());
inliersCurve_->setData(inliersScores, QMap<int, int>());
if(ui_->likelihoodPlot->isVisible()) if(ui_->likelihoodPlot->isVisible())
{ {
ui_->likelihoodPlot->update(); ui_->likelihoodPlot->update();
@ -1431,7 +1564,7 @@ void MainWindow::update(const cv::Mat & image)
ui_->label_maxMatchedDistance->setNum(maxMatchedDistance); ui_->label_maxMatchedDistance->setNum(maxMatchedDistance);
//Scroll objects slider to the best score //Scroll objects slider to the best score
if(maxScoreId>=0) if(maxScoreId>=0 && Settings::getGeneral_autoScroll())
{ {
QLabel * label = ui_->dockWidget_objects->findChild<QLabel*>(QString("%1title").arg(maxScoreId)); QLabel * label = ui_->dockWidget_objects->findChild<QLabel*>(QString("%1title").arg(maxScoreId));
if(label) if(label)
@ -1445,10 +1578,17 @@ void MainWindow::update(const cv::Mat & image)
{ {
emit objectsFound(objectsDetected); emit objectsFound(objectsDetected);
} }
ui_->label_objectsDetected->setNum(objectsDetected.size());
} }
else if(this->isVisible()) else
{ {
ui_->imageView_source->setData(keypoints, cv::Mat(), image, Settings::currentDetectorType(), Settings::currentDescriptorType()); this->statusBar()->showMessage(tr("Cannot search, objects must be updated!"));
printf("Cannot search, objects must be updated!\n");
if(this->isVisible())
{
ui_->imageView_source->setData(keypoints, cv::Mat(), image, Settings::currentDetectorType(), Settings::currentDescriptorType());
}
} }
if(this->isVisible()) if(this->isVisible())
@ -1503,28 +1643,18 @@ void MainWindow::notifyParametersChanged(const QStringList & paramChanged)
//Selective update (to not update all objects for a simple camera's parameter modification) //Selective update (to not update all objects for a simple camera's parameter modification)
bool detectorDescriptorParamsChanged = false; bool detectorDescriptorParamsChanged = false;
bool nearestNeighborParamsChanged = false; bool nearestNeighborParamsChanged = false;
QString currentDetectorType = Settings::currentDetectorType();
QString currentDescriptorType = Settings::currentDescriptorType();
QString currentNNType = Settings::currentNearestNeighborType();
//printf("currentDescriptorType: %s\n", currentDescriptorType.toStdString().c_str());
//printf("currentNNType: %s\n", currentNNType.toStdString().c_str());
for(QStringList::const_iterator iter = paramChanged.begin(); iter!=paramChanged.end(); ++iter) for(QStringList::const_iterator iter = paramChanged.begin(); iter!=paramChanged.end(); ++iter)
{ {
printf("Parameter changed: %s\n", iter->toStdString().c_str()); printf("Parameter changed: %s\n", iter->toStdString().c_str());
if(!detectorDescriptorParamsChanged && if(!detectorDescriptorParamsChanged && iter->contains("Feature2D"))
( iter->contains(currentDetectorType) ||
iter->contains(currentDescriptorType) ||
iter->compare(Settings::kFeature2D_1Detector()) == 0 ||
iter->compare(Settings::kFeature2D_2Descriptor()) == 0 ))
{ {
detectorDescriptorParamsChanged = true; detectorDescriptorParamsChanged = true;
} }
else if(!nearestNeighborParamsChanged && else if(!nearestNeighborParamsChanged &&
( iter->contains(currentNNType) || ( (iter->contains("NearestNeighbor") && Settings::getGeneral_invertedSearch()) ||
iter->compare(Settings::kGeneral_invertedSearch()) == 0 || iter->compare(Settings::kGeneral_invertedSearch()) == 0 ||
iter->compare(Settings::kGeneral_threads()) == 0 || (iter->compare(Settings::kGeneral_incrementalVocabulary()) == 0 && Settings::getGeneral_invertedSearch()) ||
iter->compare(Settings::kNearestNeighbor_1Strategy()) == 0 || (iter->compare(Settings::kGeneral_threads()) == 0 && !Settings::getGeneral_invertedSearch()) ))
iter->compare(Settings::kNearestNeighbor_2Distance_type()) == 0))
{ {
nearestNeighborParamsChanged = true; nearestNeighborParamsChanged = true;
} }
@ -1548,7 +1678,7 @@ void MainWindow::notifyParametersChanged(const QStringList & paramChanged)
this->updateData(); this->updateData();
} }
} }
else if(objects_.size()) else if(objects_.size() && (detectorDescriptorParamsChanged || nearestNeighborParamsChanged))
{ {
this->statusBar()->showMessage(tr("A parameter has changed... \"Update objects\" may be required.")); this->statusBar()->showMessage(tr("A parameter has changed... \"Update objects\" may be required."));
} }

View File

@ -16,6 +16,8 @@
#include <opencv2/features2d/features2d.hpp> #include <opencv2/features2d/features2d.hpp>
#include <opencv2/imgproc/imgproc_c.h> #include <opencv2/imgproc/imgproc_c.h>
#include "Vocabulary.h"
class Ui_mainWindow; class Ui_mainWindow;
class ObjWidget; class ObjWidget;
class Camera; class Camera;
@ -89,10 +91,11 @@ private:
Camera * camera_; Camera * camera_;
QString settings_; QString settings_;
rtabmap::PdfPlotCurve * likelihoodCurve_; rtabmap::PdfPlotCurve * likelihoodCurve_;
rtabmap::PdfPlotCurve * inliersCurve_;
AboutDialog * aboutDialog_; AboutDialog * aboutDialog_;
QList<ObjWidget*> objects_; QList<ObjWidget*> objects_;
std::vector<cv::Mat> objectsDescriptors_; std::vector<cv::Mat> objectsDescriptors_;
cv::flann::Index flannIndex_; Vocabulary vocabulary_;
QMap<int, int> dataRange_; // <last id of object's descriptor, id> QMap<int, int> dataRange_; // <last id of object's descriptor, id>
QTime updateRate_; QTime updateRate_;
QTime refreshStartTime_; QTime refreshStartTime_;

View File

@ -267,6 +267,12 @@ void ObjWidget::setData(const std::vector<cv::KeyPoint> & keypoints,
label_->setVisible(image.empty()); label_->setVisible(image.empty());
} }
void ObjWidget::setWords(const QMultiMap<int, int> & words)
{
Q_ASSERT(words.size() == keypoints_.size());
words_ = words;
}
void ObjWidget::resetKptsColor() void ObjWidget::resetKptsColor()
{ {
for(int i=0; i<kptColors_.size(); ++i) for(int i=0; i<kptColors_.size(); ++i)

View File

@ -7,6 +7,7 @@
#include <opencv2/features2d/features2d.hpp> #include <opencv2/features2d/features2d.hpp>
#include <QtGui/QWidget> #include <QtGui/QWidget>
#include <QtCore/QMultiMap>
class KeypointItem; class KeypointItem;
class ImageKptsView; class ImageKptsView;
@ -39,6 +40,7 @@ public:
const cv::Mat & image, const cv::Mat & image,
const QString & detectorType, const QString & detectorType,
const QString & descriptorType); const QString & descriptorType);
void setWords(const QMultiMap<int, int> & words);
void setTextLabel(const QString & text); void setTextLabel(const QString & text);
void resetKptsColor(); void resetKptsColor();
void setKptColor(int index, const QColor & color); void setKptColor(int index, const QColor & color);
@ -53,6 +55,7 @@ public:
void addRect(QGraphicsRectItem * rect); void addRect(QGraphicsRectItem * rect);
void clearRoiSelection() {mousePressedPos_ = mouseCurrentPos_ = QPoint();update();} void clearRoiSelection() {mousePressedPos_ = mouseCurrentPos_ = QPoint();update();}
const QMultiMap<int, int> & words() const {return words_;}
const std::vector<cv::KeyPoint> & keypoints() const {return keypoints_;} const std::vector<cv::KeyPoint> & keypoints() const {return keypoints_;}
const cv::Mat & descriptors() const {return descriptors_;} const cv::Mat & descriptors() const {return descriptors_;}
const QPixmap & pixmap() const {return pixmap_;} const QPixmap & pixmap() const {return pixmap_;}
@ -97,6 +100,7 @@ private:
private: private:
std::vector<cv::KeyPoint> keypoints_; std::vector<cv::KeyPoint> keypoints_;
cv::Mat descriptors_; cv::Mat descriptors_;
QMultiMap<int, int> words_; // <word id, keypoint indexes>
QPixmap pixmap_; QPixmap pixmap_;
cv::Mat cvImage_; cv::Mat cvImage_;
QList<KeypointItem*> keypointItems_; QList<KeypointItem*> keypointItems_;

View File

@ -176,6 +176,8 @@ class Settings
PARAMETER(General, multiDetection, bool, false, "Multiple detection of the same object."); PARAMETER(General, multiDetection, bool, false, "Multiple detection of the same object.");
PARAMETER(General, multiDetectionRadius, int, 30, "Ignore detection of the same object in X pixels radius of the previous detections."); PARAMETER(General, multiDetectionRadius, int, 30, "Ignore detection of the same object in X pixels radius of the previous detections.");
PARAMETER(General, port, int, 0, "Port on objects detected are published. If port=0, a port is chosen automatically.") PARAMETER(General, port, int, 0, "Port on objects detected are published. If port=0, a port is chosen automatically.")
PARAMETER(General, incrementalVocabulary, bool, false, "The vocabulary is created incrementally. When new objects are added, their descriptors are compared to those already in vocabulary to find if the visual word already exist or not. \"NearestNeighbor/nndrRatio\" is used to compare descriptors.");
PARAMETER(General, autoScroll, bool, true, "Auto scroll to detected object in Objects panel.");
PARAMETER(Homography, homographyComputed, bool, true, "Compute homography? On ROS, this is required to publish objects detected."); PARAMETER(Homography, homographyComputed, bool, true, "Compute homography? On ROS, this is required to publish objects detected.");
PARAMETER(Homography, method, QString, "1:LMEDS;RANSAC", "Type of the robust estimation algorithm: least-median algorithm or RANSAC algorithm."); PARAMETER(Homography, method, QString, "1:LMEDS;RANSAC", "Type of the robust estimation algorithm: least-median algorithm or RANSAC algorithm.");

174
src/Vocabulary.cpp Normal file
View File

@ -0,0 +1,174 @@
/*
* Vocabulary.cpp
*
* Created on: 2014-05-09
* Author: mathieu
*/
#include "Vocabulary.h"
#include "Settings.h"
#include <QtCore/QVector>
#include <stdio.h>
Vocabulary::Vocabulary()
{
}
Vocabulary::~Vocabulary()
{
}
void Vocabulary::clear()
{
descriptors_ = cv::Mat();
wordToObjects_.clear();
}
QMultiMap<int, int> Vocabulary::addWords(const cv::Mat & descriptors, int objectIndex, bool incremental)
{
QMultiMap<int, int> words;
if (descriptors.empty())
{
return words;
}
if(incremental)
{
int k = 2;
cv::Mat results(descriptors.rows, k, CV_32SC1); // results index
cv::Mat dists(descriptors.rows, k, CV_32FC1); // Distance results are CV_32FC1
bool globalSearch = false;
if(!descriptors_.empty() && descriptors_.rows >= (int)k)
{
flannIndex_.knnSearch(descriptors, results, dists, k, Settings::getFlannSearchParams() );
globalSearch = true;
}
QVector<int> newWordsId; // index global
cv::Mat newWords;
int matches = 0;
for(int i = 0; i < descriptors.rows; ++i)
{
QMap<float, int> fullResults; // nearest descriptors sorted by distance
if(newWords.rows)
{
// Check if this descriptor matches with a word not already added to the vocabulary
cv::flann::Index tmpIndex;
cv::flann::IndexParams * params;
if(descriptors.type()==CV_8U)
{
params = Settings::createFlannIndexParams(); // should be LSH
}
else
{
params = new cv::flann::LinearIndexParams(); // faster
}
tmpIndex.build(newWords, *params, Settings::getFlannDistanceType());
delete params;
cv::Mat tmpResults(1, newWords.rows>1?k:1, CV_32SC1); // results index
cv::Mat tmpDists(1, newWords.rows>1?k:1, CV_32FC1); // Distance results are CV_32FC1
tmpIndex.knnSearch(descriptors.row(i), tmpResults, tmpDists, newWords.rows>1?k:1, Settings::getFlannSearchParams());
for(int j = 0; j < (newWords.rows>1?k:1); ++j)
{
fullResults.insert(tmpDists.at<float>(0,j), newWordsId.at(tmpResults.at<int>(0,j)));
}
}
if(globalSearch)
{
for(int j=0; j<k; ++j)
{
fullResults.insert(dists.at<float>(i,j), results.at<int>(i,j));
}
}
bool match = false;
// Apply NNDR
if(fullResults.size() >= 2 &&
fullResults.begin().key() <= Settings::getNearestNeighbor_4nndrRatio() * (++fullResults.begin()).key())
{
match = true;
}
if(match)
{
words.insert(fullResults.begin().value(), i);
wordToObjects_.insert(fullResults.begin().value(), objectIndex);
++matches;
}
else
{
cv::Mat tmp(newWords.rows+1, descriptors.cols, descriptors.type());
if(newWords.rows)
{
cv::Mat dest(tmp, cv::Range(0, newWords.rows));
newWords.copyTo(dest);
}
cv::Mat dest(tmp, cv::Range(newWords.rows, newWords.rows+1));
descriptors.row(i).copyTo(dest);
newWordsId.push_back(descriptors_.rows + newWords.rows);
newWords = tmp;
words.insert(newWordsId.back(), i);
wordToObjects_.insert(newWordsId.back(), objectIndex);
}
}
//printf("matches = %d\n", matches);
//concatenate new words
if(newWords.rows)
{
cv::Mat tmp(descriptors_.rows+newWords.rows, descriptors.cols, descriptors.type());
if(descriptors_.rows)
{
cv::Mat dest(tmp, cv::Range(0, descriptors_.rows));
descriptors_.copyTo(dest);
}
cv::Mat dest(tmp, cv::Range(descriptors_.rows, descriptors_.rows+newWords.rows));
newWords.copyTo(dest);
descriptors_ = tmp;
}
//update
this->update();
}
else
{
for(int i = 0; i < descriptors.rows; ++i)
{
wordToObjects_.insert(descriptors_.rows+i, objectIndex);
words.insert(descriptors_.rows+i, i);
}
//just concatenate descriptors
cv::Mat tmp(descriptors_.rows+descriptors.rows, descriptors.cols, descriptors.type());
if(descriptors_.rows)
{
cv::Mat dest(tmp, cv::Range(0, descriptors_.rows));
descriptors_.copyTo(dest);
}
cv::Mat dest(tmp, cv::Range(descriptors_.rows, descriptors_.rows+descriptors.rows));
descriptors.copyTo(dest);
descriptors_ = tmp;
}
return words;
}
void Vocabulary::update()
{
if(!descriptors_.empty())
{
cv::flann::IndexParams * params = Settings::createFlannIndexParams();
flannIndex_.build(descriptors_, *params, Settings::getFlannDistanceType());
delete params;
}
}
void Vocabulary::search(const cv::Mat & descriptors, cv::Mat & results, cv::Mat & dists, int k)
{
if(!descriptors_.empty())
{
flannIndex_.knnSearch(descriptors, results, dists, k, Settings::getFlannSearchParams());
}
}

32
src/Vocabulary.h Normal file
View File

@ -0,0 +1,32 @@
/*
* Vocabulary.h
*
* Created on: 2014-05-09
* Author: mathieu
*/
#ifndef VOCABULARY_H_
#define VOCABULARY_H_
#include <QtCore/QMultiMap>
#include <opencv2/opencv.hpp>
class Vocabulary {
public:
Vocabulary();
virtual ~Vocabulary();
void clear();
QMultiMap<int, int> addWords(const cv::Mat & descriptors, int objectIndex, bool incremental);
void update();
void search(const cv::Mat & descriptors, cv::Mat & results, cv::Mat & dists, int k);
int size() const {return descriptors_.rows;}
const QMultiMap<int, int> & wordToObjects() const {return wordToObjects_;}
private:
cv::flann::Index flannIndex_;
cv::Mat descriptors_;
QMultiMap<int, int> wordToObjects_;
};
#endif /* VOCABULARY_H_ */

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>826</width> <width>826</width>
<height>448</height> <height>572</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -257,7 +257,7 @@
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>360</width> <width>360</width>
<height>376</height> <height>380</height>
</size> </size>
</property> </property>
<property name="floating"> <property name="floating">
@ -277,224 +277,6 @@
<property name="margin"> <property name="margin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Statistics</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout" columnstretch="1,0,0">
<property name="horizontalSpacing">
<number>6</number>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="3" column="1">
<widget class="QLabel" name="label_timeIndexing">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_timeMatching">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_9">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLabel" name="label_10">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QLabel" name="label_12">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label_minMatchedDistance">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Min matched distance</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Max matched distance</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="label_maxMatchedDistance">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Detect outliers and GUI</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_timeGui">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Descriptors extraction</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_timeExtraction">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Features detection</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_timeDetection">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Descriptors matching</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Descriptors indexing</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Total</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_timeTotal">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_16">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Vocabulary size</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="label_vocabularySize">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>IP address</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Port</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="label_ipAddress">
<property name="text">
<string>0.0.0.0</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLabel" name="label_port">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="ParametersToolBox" name="toolBox"> <widget class="ParametersToolBox" name="toolBox">
<property name="currentIndex"> <property name="currentIndex">
@ -506,7 +288,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>360</width> <width>360</width>
<height>86</height> <height>297</height>
</rect> </rect>
</property> </property>
<attribute name="label"> <attribute name="label">
@ -564,7 +346,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>198</width> <width>198</width>
<height>318</height> <height>442</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_objects"> <layout class="QVBoxLayout" name="verticalLayout_objects">
@ -651,6 +433,244 @@
</layout> </layout>
</widget> </widget>
</widget> </widget>
<widget class="QDockWidget" name="dockWidget_statistics">
<property name="windowTitle">
<string>Statistics</string>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_4">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout" columnstretch="1,0,0">
<property name="horizontalSpacing">
<number>6</number>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Descriptors indexing</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_timeIndexing">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_timeMatching">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_9">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLabel" name="label_10">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QLabel" name="label_12">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="label_minMatchedDistance">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Min matched distance</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Max matched distance</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="label_maxMatchedDistance">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Detect outliers and GUI</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_timeGui">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Descriptors extraction</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_timeExtraction">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Features detection</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_timeDetection">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Descriptors matching</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Total</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_timeTotal">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_16">
<property name="text">
<string>ms</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Vocabulary size</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="label_vocabularySize">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>IP address</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Port</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLabel" name="label_ipAddress">
<property name="text">
<string>0.0.0.0</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLabel" name="label_port">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Objects detected</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label_objectsDetected">
<property name="text">
<string>000</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<action name="actionExit"> <action name="actionExit">
<property name="text"> <property name="text">
<string>Exit</string> <string>Exit</string>