From dd14e5e1ca506a00ab7f022a4eb2e520f7180cb6 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 7 Jul 2015 16:49:38 -0400 Subject: [PATCH] Added Load/Save vocabulary actions in File menu (in console mode, see "--vocabulary" option) (issue #2) --- app/main.cpp | 119 +++++++++++++++++++++++++------ include/find_object/FindObject.h | 3 + include/find_object/MainWindow.h | 5 +- include/find_object/ObjWidget.h | 2 +- include/find_object/Settings.h | 2 +- src/FindObject.cpp | 44 +++++++++++- src/KeypointItem.h | 1 + src/MainWindow.cpp | 77 +++++++++++++++++++- src/ObjWidget.cpp | 31 +++++--- src/Vocabulary.cpp | 96 +++++++++++++++++++------ src/Vocabulary.h | 6 +- src/ui/mainWindow.ui | 13 ++++ 12 files changed, 341 insertions(+), 58 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index ab8a74c5..9048d3c7 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -99,15 +99,19 @@ void showUsage() " --console Don't use the GUI (by default the camera will be\n" " started automatically). Option --objects must also be\n" " used with valid objects.\n" - " --session \"path\" Path to a session to load (*.bin).\n" + " --session \"path\" Path to a session to load (*.bin). Use \"--session_new\" to\n" + " create a session instead (will be saved to \"path\" on exit, only\n" + " on console mode).\n" " --object \"path\" Path to an object to detect.\n" " --objects \"path\" Directory of the objects to detect (--object is ignored).\n" " --config \"path\" Path to configuration file (default: %s).\n" - " If set to \"\", default parameters are used " + " If set to \"\", default parameters are used\n" " without saving modified parameters on closing.\n" " --scene \"path\" Path to a scene image file.\n" + " --vocabulary \"path\" Path to a vocabulary file (*.yaml *.xml). Parameters \"General/invertedSearch\"\n" + " and \"General/vocabularyFixed\" will be also enabled. Ignored if \"--session\" is set.\n" " --images_not_saved Don't keep images in RAM after the features are extracted (only\n" - " in console mode). Images won't be saved if an output session is set.\n" + " in console mode). Images won't be saved if an output session is set.\n" " --debug Show debug log.\n" " --debug-time Show debug log with time.\n" " --params Show all parameters.\n" @@ -133,10 +137,12 @@ int main(int argc, char* argv[]) ////////////////////////// bool guiMode = true; QString sessionPath = ""; + bool sessionNew = false; QString objectsPath = ""; QString objectPath = ""; QString scenePath = ""; QString configPath = find_object::Settings::iniDefaultPath(); + QString vocabularyPath = ""; QString jsonPath; find_object::ParametersMap customParameters; bool imagesSaved = true; @@ -176,8 +182,15 @@ int main(int argc, char* argv[]) continue; } if(strcmp(argv[i], "-session") == 0 || - strcmp(argv[i], "--session") == 0) + strcmp(argv[i], "--session") == 0 || + strcmp(argv[i], "-session_new") == 0 || + strcmp(argv[i], "--session_new") == 0) { + if(strcmp(argv[i], "-session_new") == 0 || + strcmp(argv[i], "--session_new") == 0) + { + sessionNew = true; + } ++i; if(i < argc) { @@ -186,9 +199,10 @@ int main(int argc, char* argv[]) { sessionPath.replace('~', QDir::homePath()); } - if(!QFile(sessionPath).exists()) + + if(!sessionNew && !QFile(sessionPath).exists()) { - UERROR("Session path not valid : %s", sessionPath.toStdString().c_str()); + UERROR("Session path not valid : %s (if you want to create a new session, use \"--session_new\")", sessionPath.toStdString().c_str()); showUsage(); } } @@ -244,6 +258,29 @@ int main(int argc, char* argv[]) } continue; } + if(strcmp(argv[i], "-vocabulary") == 0 || + strcmp(argv[i], "--vocabulary") == 0) + { + ++i; + if(i < argc) + { + vocabularyPath = argv[i]; + if(vocabularyPath.contains('~')) + { + vocabularyPath.replace('~', QDir::homePath()); + } + if(!QFile(vocabularyPath).exists()) + { + UERROR("Vocabulary path not valid : %s", vocabularyPath.toStdString().c_str()); + showUsage(); + } + } + else + { + showUsage(); + } + continue; + } if(strcmp(argv[i], "-config") == 0 || strcmp(argv[i], "--config") == 0) { @@ -365,7 +402,14 @@ int main(int argc, char* argv[]) UINFO(" GUI mode = %s", guiMode?"true":"false"); if(!sessionPath.isEmpty()) { - UINFO(" Session path: \"%s\"", sessionPath.toStdString().c_str()); + if(sessionNew) + { + UINFO(" Session path: \"%s\" [NEW]", sessionPath.toStdString().c_str()); + } + else + { + UINFO(" Session path: \"%s\"", sessionPath.toStdString().c_str()); + } } else if(!objectsPath.isEmpty()) { @@ -381,6 +425,21 @@ int main(int argc, char* argv[]) UINFO(" JSON path: \"%s\"", jsonPath.toStdString().c_str()); } UINFO(" Settings path: \"%s\"", configPath.toStdString().c_str()); + UINFO(" Vocabulary path: \"%s\"", vocabularyPath.toStdString().c_str()); + + if(!vocabularyPath.isEmpty()) + { + if(customParameters.contains(find_object::Settings::kGeneral_vocabularyFixed())) + { + UWARN("\"General/vocabularyFixed\" custom parameter overwritten as a fixed vocabulary is used."); + } + if(customParameters.contains(find_object::Settings::kGeneral_invertedSearch())) + { + UWARN("\"General/invertedSearch\" custom parameter overwritten as a fixed vocabulary is used."); + } + customParameters[find_object::Settings::kGeneral_vocabularyFixed()] = true; + customParameters[find_object::Settings::kGeneral_invertedSearch()] = true; + } for(find_object::ParametersMap::iterator iter= customParameters.begin(); iter!=customParameters.end(); ++iter) { @@ -405,8 +464,14 @@ int main(int argc, char* argv[]) // Load objects if path is set int objectsLoaded = 0; - if(!sessionPath.isEmpty()) + if(!sessionPath.isEmpty() && !sessionNew) { + if(!vocabularyPath.isEmpty() && !findObject->loadVocabulary(vocabularyPath)) + { + UWARN("Vocabulary \"%s\" is not loaded as a session \"%s\" is already loaded", + vocabularyPath.toStdString().c_str(), + sessionPath.toStdString().c_str()); + } if(!findObject->loadSession(sessionPath)) { UERROR("Could not load session \"%s\"", sessionPath.toStdString().c_str()); @@ -418,6 +483,10 @@ int main(int argc, char* argv[]) } else if(!objectsPath.isEmpty()) { + if(!vocabularyPath.isEmpty() && !findObject->loadVocabulary(vocabularyPath)) + { + UERROR("Failed to load vocabulary \"%s\"", vocabularyPath.toStdString().c_str()); + } objectsLoaded = findObject->loadObjects(objectsPath); if(!objectsLoaded) { @@ -426,6 +495,11 @@ int main(int argc, char* argv[]) } else if(!objectPath.isEmpty()) { + if(!vocabularyPath.isEmpty() && !findObject->loadVocabulary(vocabularyPath)) + { + UERROR("Failed to load vocabulary \"%s\"", vocabularyPath.toStdString().c_str()); + } + const find_object::ObjSignature * obj = findObject->addObject(objectPath); if(obj) { @@ -438,6 +512,11 @@ int main(int argc, char* argv[]) UWARN("No object loaded from \"%s\"", objectsPath.toStdString().c_str()); } } + else if(!vocabularyPath.isEmpty() && !findObject->loadVocabulary(vocabularyPath)) + { + UERROR("Failed to load vocabulary \"%s\"", vocabularyPath.toStdString().c_str()); + } + cv::Mat scene; if(!scenePath.isEmpty()) { @@ -468,13 +547,6 @@ int main(int argc, char* argv[]) } else { - if(objectsLoaded == 0) - { - UERROR("In console mode, at least one object must be loaded! See -console option."); - delete findObject; - showUsage(); - } - QCoreApplication app(argc, argv); if(!scene.empty()) @@ -535,13 +607,20 @@ int main(int argc, char* argv[]) { app.exec(); - if(!sessionPath.isEmpty() && findObject->isSessionModified()) + if(!sessionPath.isEmpty()) { - UINFO("The session has been modified, updating the session file..."); - if(findObject->saveSession(sessionPath)) + if(findObject->isSessionModified()) { - UINFO("Session \"%s\" successfully saved (%d objects)!", - sessionPath.toStdString().c_str(), findObject->objects().size()); + UINFO("The session has been modified, updating the session file..."); + if(findObject->saveSession(sessionPath)) + { + UINFO("Session \"%s\" successfully saved (%d objects)!", + sessionPath.toStdString().c_str(), findObject->objects().size()); + } + } + else if(sessionNew) + { + UINFO("The session has not been modified, session file not created..."); } } } diff --git a/include/find_object/FindObject.h b/include/find_object/FindObject.h index 27273cc6..d466ffd8 100644 --- a/include/find_object/FindObject.h +++ b/include/find_object/FindObject.h @@ -69,6 +69,9 @@ public: bool saveSession(const QString & path); bool isSessionModified() const {return sessionModified_;} + bool saveVocabulary(const QString & filePath) const; + bool loadVocabulary(const QString & filePath); + int loadObjects(const QString & dirPath, bool recursive = false); // call updateObjects() const ObjSignature * addObject(const QString & filePath); const ObjSignature * addObject(const cv::Mat & image, int id=0, const QString & filePath = QString()); diff --git a/include/find_object/MainWindow.h b/include/find_object/MainWindow.h index 62dd41b2..7cd2c613 100644 --- a/include/find_object/MainWindow.h +++ b/include/find_object/MainWindow.h @@ -87,6 +87,8 @@ private Q_SLOTS: void saveSettings(); void loadObjects(); bool saveObjects(); + void loadVocabulary(); + void saveVocabulary(); void addObjectFromScene(); void addObjectsFromFiles(const QStringList & fileNames); void addObjectsFromFiles(); @@ -104,7 +106,7 @@ private Q_SLOTS: void showHideControls(); void showObjectsFeatures(); void hideObjectsFeatures(); - void updateObjects(const QList & ids = QList()); + void updateObjects(); void notifyParametersChanged(const QStringList & param); void moveCameraFrame(int frame); void rectHovered(int objId); @@ -122,6 +124,7 @@ private: void showObject(find_object::ObjWidget * obj); void updateObjectSize(find_object::ObjWidget * obj); void updateVocabulary(const QList & ids = QList()); + void updateObjects(const QList & ids); private: Ui_mainWindow * ui_; diff --git a/include/find_object/ObjWidget.h b/include/find_object/ObjWidget.h index 8e133a42..6867eb36 100644 --- a/include/find_object/ObjWidget.h +++ b/include/find_object/ObjWidget.h @@ -81,7 +81,7 @@ public: const std::vector keypoints() const {return keypoints_;} const QMap & words() const {return words_;} const QPixmap & pixmap() const {return pixmap_;} - QColor defaultColor() const; + QColor defaultColor(int id) const; bool isImageShown() const; bool isFeaturesShown() const; bool isSizedFeatures() const; diff --git a/include/find_object/Settings.h b/include/find_object/Settings.h index bddbc9bd..a91a3134 100644 --- a/include/find_object/Settings.h +++ b/include/find_object/Settings.h @@ -267,7 +267,7 @@ class FINDOBJECT_EXP Settings PARAMETER(General, nextObjID, uint, 1, "Next object ID to use."); PARAMETER(General, imageFormats, QString, "*.png *.jpg *.bmp *.tiff *.ppm *.pgm", "Image formats supported."); PARAMETER(General, videoFormats, QString, "*.avi *.m4v *.mp4", "Video formats supported."); - PARAMETER(General, mirrorView, bool, true, "Flip the camera image horizontally (like all webcam applications)."); + PARAMETER(General, mirrorView, bool, false, "Flip the camera image horizontally (like all webcam applications)."); PARAMETER(General, invertedSearch, bool, true, "Instead of matching descriptors from the objects to those in a vocabulary created with descriptors extracted from the scene, we create a vocabulary from all the objects' descriptors and we match scene's descriptors to this vocabulary. It is the inverted search mode."); PARAMETER(General, controlsShown, bool, false, "Show play/image seek controls (useful with video file and directory of images modes)."); PARAMETER(General, threads, int, 1, "Number of threads used for objects matching and homography computation. 0 means as many threads as objects. On InvertedSearch mode, multi-threading has only effect on homography computation."); diff --git a/src/FindObject.cpp b/src/FindObject.cpp index f3742549..d14b8bec 100644 --- a/src/FindObject.cpp +++ b/src/FindObject.cpp @@ -143,6 +143,29 @@ bool FindObject::saveSession(const QString & path) return false; } +bool FindObject::saveVocabulary(const QString & filePath) const +{ + return vocabulary_->save(filePath); +} + +bool FindObject::loadVocabulary(const QString & filePath) +{ + if(!Settings::getGeneral_vocabularyFixed() || !Settings::getGeneral_invertedSearch()) + { + UWARN("Doesn't make sense to load a vocabulary if \"General/vocabularyFixed\" and \"General/invertedSearch\" are not enabled! It will " + "be cleared at the time the objects are updated."); + } + if(vocabulary_->load(filePath)) + { + if(objects_.size()) + { + updateVocabulary(); + } + return true; + } + return false; +} + int FindObject::loadObjects(const QString & dirPath, bool recursive) { QString formats = Settings::getGeneral_imageFormats().remove('*').remove('.'); @@ -192,7 +215,6 @@ int FindObject::loadObjects(const QString & dirPath, bool recursive) const ObjSignature * FindObject::addObject(const QString & filePath) { - UINFO("Load file %s", filePath.toStdString().c_str()); if(!filePath.isNull()) { cv::Mat img = cv::imread(filePath.toStdString().c_str(), cv::IMREAD_GRAYSCALE); @@ -218,8 +240,26 @@ const ObjSignature * FindObject::addObject(const QString & filePath) id = 0; } } - return this->addObject(img, id, filePath); + else + { + UERROR("File name doesn't contain \".\" (\"%s\")", filePath.toStdString().c_str()); + } + + const ObjSignature * s = this->addObject(img, id, filePath); + if(s) + { + UINFO("Added object %d (%s)", s->id(), filePath.toStdString().c_str()); + return s; + } } + else + { + UERROR("Could not read image \"%s\"", filePath.toStdString().c_str()); + } + } + else + { + UERROR("File path is null!?"); } return 0; } diff --git a/src/KeypointItem.h b/src/KeypointItem.h index 1f7e3cef..4370bf7b 100644 --- a/src/KeypointItem.h +++ b/src/KeypointItem.h @@ -44,6 +44,7 @@ public: void setColor(const QColor & color); void setWordID(int id) {wordID_ = id;} + int wordID() const {return wordID_;} int id() const {return id_;} protected: diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index b2ab6bd8..b8f6d743 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -183,7 +183,7 @@ MainWindow::MainWindow(FindObject * findObject, Camera * camera, QWidget * paren //buttons connect(ui_->pushButton_restoreDefaults, SIGNAL(clicked()), ui_->toolBox, SLOT(resetCurrentPage())); - connect(ui_->pushButton_updateObjects, SIGNAL(clicked()), this, SLOT(updateObjects(const QList &))); + connect(ui_->pushButton_updateObjects, SIGNAL(clicked()), this, SLOT(updateObjects())); connect(ui_->horizontalSlider_objectsSize, SIGNAL(valueChanged(int)), this, SLOT(updateObjectsSize())); ui_->actionStop_camera->setEnabled(false); @@ -211,6 +211,8 @@ MainWindow::MainWindow(FindObject * findObject, Camera * camera, QWidget * paren connect(ui_->actionLoad_settings, SIGNAL(triggered()), this, SLOT(loadSettings())); connect(ui_->actionSave_session, SIGNAL(triggered()), this, SLOT(saveSession())); connect(ui_->actionLoad_session, SIGNAL(triggered()), this, SLOT(loadSession())); + connect(ui_->actionSave_vocabulary, SIGNAL(triggered()), this, SLOT(saveVocabulary())); + connect(ui_->actionLoad_vocabulary, SIGNAL(triggered()), this, SLOT(loadVocabulary())); connect(ui_->actionShow_objects_features, SIGNAL(triggered()), this, SLOT(showObjectsFeatures())); connect(ui_->actionHide_objects_features, SIGNAL(triggered()), this, SLOT(hideObjectsFeatures())); @@ -251,6 +253,13 @@ MainWindow::MainWindow(FindObject * findObject, Camera * camera, QWidget * paren objWidgets_.insert(obj->id(), obj); this->showObject(obj); } + ui_->actionSave_objects->setEnabled(true); + ui_->actionSave_session->setEnabled(true); + } + if(findObject_->vocabulary()->size()) + { + ui_->label_vocabularySize->setNum(findObject_->vocabulary()->size()); + ui_->actionSave_session->setEnabled(true); } @@ -585,6 +594,66 @@ bool MainWindow::saveObjects() return false; } +void MainWindow::loadVocabulary() +{ + if(!Settings::getGeneral_vocabularyFixed() || + !Settings::getGeneral_invertedSearch()) + { + QMessageBox::StandardButton b = QMessageBox::question(this, tr("Load vocabulary..."), + tr("Parameters \"General/vocabularyFixed\" and \"General/invertedSearch\" should be enabled to load a vocabulary. " + "Do you want to enable them now?"), + QMessageBox::Cancel | QMessageBox::Yes); + if(b == QMessageBox::Yes) + { + Settings::setGeneral_vocabularyFixed(true); + Settings::setGeneral_invertedSearch(true); + } + } + if(Settings::getGeneral_vocabularyFixed() && + Settings::getGeneral_invertedSearch()) + { + QString path = QFileDialog::getOpenFileName(this, tr("Load vocabulary..."), Settings::workingDirectory(), "Data (*.yaml *.xml)"); + if(!path.isEmpty()) + { + if(findObject_->loadVocabulary(path)) + { + ui_->label_vocabularySize->setNum(findObject_->vocabulary()->size()); + ui_->actionSave_session->setEnabled(findObject_->vocabulary()->size() || findObject_->objects().size()); + QMessageBox::information(this, tr("Loading..."), tr("Vocabulary loaded from \"%1\" (%2 words).").arg(path).arg(findObject_->vocabulary()->size())); + } + else + { + QMessageBox::warning(this, tr("Loading..."), tr("Failed to load vocabulary \"%1\"!").arg(path)); + } + } + } +} + +void MainWindow::saveVocabulary() +{ + if(findObject_->vocabulary()->size() == 0) + { + QMessageBox::warning(this, tr("Saving vocabulary..."), tr("Vocabulary is empty!")); + return; + } + QString path = QFileDialog::getSaveFileName(this, tr("Save vocabulary..."), Settings::workingDirectory(), "Data (*.yaml *.xml)"); + if(!path.isEmpty()) + { + if(QFileInfo(path).suffix().compare("yaml") != 0 && QFileInfo(path).suffix().compare("xml") != 0) + { + path.append(".yaml"); + } + if(findObject_->saveVocabulary(path)) + { + QMessageBox::information(this, tr("Saving..."), tr("Vocabulary saved to \"%1\" (%2 words).").arg(path).arg(findObject_->vocabulary()->size())); + } + else + { + QMessageBox::warning(this, tr("Saving..."), tr("Failed to save vocabulary \"%1\"!").arg(path)); + } + } +} + void MainWindow::removeObject(find_object::ObjWidget * object) { if(object) @@ -977,6 +1046,11 @@ void MainWindow::showObject(ObjWidget * obj) } } +void MainWindow::updateObjects() +{ + updateObjects(QList()); +} + void MainWindow::updateObjects(const QList & ids) { if(objWidgets_.size()) @@ -1045,6 +1119,7 @@ void MainWindow::updateVocabulary(const QList & ids) } lastObjectsUpdateParameters_ = Settings::getParameters(); this->statusBar()->clearMessage(); + ui_->dockWidget_objects->update(); } void MainWindow::startProcessing() diff --git a/src/ObjWidget.cpp b/src/ObjWidget.cpp index b852a446..8a4772bb 100644 --- a/src/ObjWidget.cpp +++ b/src/ObjWidget.cpp @@ -71,7 +71,7 @@ ObjWidget::ObjWidget(int id, const std::vector & keypoints, const graphicsView_(0), graphicsViewInitialized_(false), alpha_(100), - color_(QColor((Qt::GlobalColor)((id % 11 + 7)==Qt::yellow?Qt::gray:(id % 11 + 7)))) + color_(QColor((Qt::GlobalColor)((id % 10 + 7)==Qt::yellow?Qt::darkYellow:(id % 10 + 7)))) { setupUi(); this->updateImage(image); @@ -136,7 +136,7 @@ void ObjWidget::setupUi() void ObjWidget::setId(int id) { - color_ = QColor((Qt::GlobalColor)((id % 11 + 7)==Qt::yellow?Qt::gray:(id % 11 + 7))); + color_ = QColor((Qt::GlobalColor)((id % 10 + 7)==Qt::yellow?Qt::darkYellow:(id % 10 + 7))); id_=id; if(id_) { @@ -274,7 +274,7 @@ void ObjWidget::updateImage(const QImage & image) void ObjWidget::updateData(const std::vector & keypoints, const QMultiMap & words) { keypoints_ = keypoints; - kptColors_ = QVector((int)keypoints.size(), defaultColor()); + kptColors_ = QVector((int)keypoints.size(), defaultColor(0)); keypointItems_.clear(); rectItems_.clear(); this->updateWords(words); @@ -297,9 +297,14 @@ void ObjWidget::updateWords(const QMultiMap & words) { words_.insert(iter.value(), iter.key()); } - for(int i=0; isetWordID(words_.value(i,-1)); + kptColors_[i] = defaultColor(words_.size()?words_.value(i,-1):0); + if(keypointItems_.size() == kptColors_.size()) + { + keypointItems_[i]->setWordID(words_.value(i,-1)); + keypointItems_[i]->setColor(defaultColor(words_.size()?keypointItems_[i]->wordID():0)); + } } } @@ -307,10 +312,14 @@ void ObjWidget::resetKptsColor() { for(int i=0; iisChecked()) + if(keypointItems_.size() == kptColors_.size()) { - keypointItems_[i]->setColor(this->defaultColor()); + kptColors_[i] = defaultColor(keypointItems_[i]->wordID()); + keypointItems_[i]->setColor(this->defaultColor(keypointItems_[i]->wordID())); + } + else + { + kptColors_[i] = defaultColor(words_.value(i,-1)); } } qDeleteAll(rectItems_.begin(), rectItems_.end()); @@ -738,6 +747,8 @@ void ObjWidget::drawKeypoints(QPainter * painter) item->setVisible(this->isFeaturesShown()); item->setZValue(2); graphicsView_->scene()->addItem(item); + item->setColor(defaultColor(item->wordID())); + kptColors_[i] = defaultColor(item->wordID()); keypointItems_.append(item); } @@ -752,9 +763,9 @@ void ObjWidget::drawKeypoints(QPainter * painter) } } -QColor ObjWidget::defaultColor() const +QColor ObjWidget::defaultColor(int id) const { - QColor color(Qt::yellow); + QColor color(id >= 0 ? Qt::yellow : Qt::white); color.setAlpha(alpha_); return color; } diff --git a/src/Vocabulary.cpp b/src/Vocabulary.cpp index 3ec43405..6d5ac5a8 100644 --- a/src/Vocabulary.cpp +++ b/src/Vocabulary.cpp @@ -60,6 +60,8 @@ void Vocabulary::clear() if(Settings::getGeneral_vocabularyFixed() && Settings::getGeneral_invertedSearch()) { + this->update(); // if vocabulary structure has changed + // If the dictionary is fixed, don't clear indexed descriptors return; } @@ -67,41 +69,81 @@ void Vocabulary::clear() indexedDescriptors_ = cv::Mat(); } -void Vocabulary::save(QDataStream & streamPtr) const +void Vocabulary::save(QDataStream & streamSessionPtr) const { - if(!indexedDescriptors_.empty() && !wordToObjects_.empty()) - { - UASSERT(notIndexedDescriptors_.empty() && notIndexedWordIds_.empty()); + // save index + streamSessionPtr << wordToObjects_; - // save index - streamPtr << wordToObjects_; - - // save words - qint64 dataSize = indexedDescriptors_.elemSize()*indexedDescriptors_.cols*indexedDescriptors_.rows; - streamPtr << indexedDescriptors_.rows << - indexedDescriptors_.cols << - indexedDescriptors_.type() << - dataSize; - streamPtr << QByteArray((char*)indexedDescriptors_.data, dataSize); - } + // save words + qint64 dataSize = indexedDescriptors_.elemSize()*indexedDescriptors_.cols*indexedDescriptors_.rows; + streamSessionPtr << indexedDescriptors_.rows << + indexedDescriptors_.cols << + indexedDescriptors_.type() << + dataSize; + streamSessionPtr << QByteArray((char*)indexedDescriptors_.data, dataSize); } -void Vocabulary::load(QDataStream & streamPtr) +void Vocabulary::load(QDataStream & streamSessionPtr) { // load index - streamPtr >> wordToObjects_; + streamSessionPtr >> wordToObjects_; // load words int rows,cols,type; qint64 dataSize; - streamPtr >> rows >> cols >> type >> dataSize; + streamSessionPtr >> rows >> cols >> type >> dataSize; QByteArray data; - streamPtr >> data; + streamSessionPtr >> data; indexedDescriptors_ = cv::Mat(rows, cols, type, data.data()).clone(); update(); } +bool Vocabulary::save(const QString & filename) const +{ + // save descriptors + cv::FileStorage fs(filename.toStdString(), cv::FileStorage::WRITE); + if(fs.isOpened()) + { + fs << "Descriptors" << indexedDescriptors_; + return true; + } + else + { + UERROR("Failed to open vocabulary file \"%s\"", filename.toStdString().c_str()); + } + return false; +} + +bool Vocabulary::load(const QString & filename) +{ + // save descriptors + cv::FileStorage fs(filename.toStdString(), cv::FileStorage::READ); + if(fs.isOpened()) + { + cv::Mat tmp; + fs["Descriptors"] >> tmp; + + if(!tmp.empty()) + { + // clear index + wordToObjects_.clear(); + indexedDescriptors_ = tmp; + update(); + return true; + } + else + { + UERROR("Failed to read \"Descriptors\" matrix field (doesn't exist or is empty) from vocabulary file \"%s\"", filename.toStdString().c_str()); + } + } + else + { + UERROR("Failed to open vocabulary file \"%s\"", filename.toStdString().c_str()); + } + return false; +} + QMultiMap Vocabulary::addWords(const cv::Mat & descriptors, int objectId) { QMultiMap words; @@ -119,7 +161,21 @@ QMultiMap Vocabulary::addWords(const cv::Mat & descriptors, int object bool globalSearch = false; if(!indexedDescriptors_.empty() && indexedDescriptors_.rows >= (int)k) { - UASSERT(indexedDescriptors_.type() == descriptors.type() && indexedDescriptors_.cols == descriptors.cols); + if(indexedDescriptors_.type() != descriptors.type() || indexedDescriptors_.cols != descriptors.cols) + { + if(Settings::getGeneral_vocabularyFixed()) + { + UERROR("Descriptors (type=%d size=%d) to search in vocabulary are not the same type/size as those in the vocabulary (type=%d size=%d)! Empty words returned.", + descriptors.type(), descriptors.cols, indexedDescriptors_.type(), indexedDescriptors_.cols); + return words; + } + else + { + UFATAL("Descriptors (type=%d size=%d) to search in vocabulary are not the same type/size as those in the vocabulary (type=%d size=%d)!", + descriptors.type(), descriptors.cols, indexedDescriptors_.type(), indexedDescriptors_.cols); + } + } + this->search(descriptors, results, dists, k); if( dists.type() == CV_32S ) diff --git a/src/Vocabulary.h b/src/Vocabulary.h index 844adecf..9c754d48 100644 --- a/src/Vocabulary.h +++ b/src/Vocabulary.h @@ -49,8 +49,10 @@ public: const QMultiMap & wordToObjects() const {return wordToObjects_;} const cv::Mat & indexedDescriptors() const {return indexedDescriptors_;} - void save(QDataStream & streamPtr) const; - void load(QDataStream & streamPtr); + void save(QDataStream & streamSessionPtr) const; + void load(QDataStream & streamSessionPtr); + bool save(const QString & filename) const; + bool load(const QString & filename); private: cv::flann::Index flannIndex_; diff --git a/src/ui/mainWindow.ui b/src/ui/mainWindow.ui index eee48ad0..29e111d9 100644 --- a/src/ui/mainWindow.ui +++ b/src/ui/mainWindow.ui @@ -225,6 +225,9 @@ + + + @@ -894,6 +897,16 @@ Show objects features + + + Load vocabulary... + + + + + Save vocabulary... + +