├── pics ├── similar_vision.rc ├── up.png ├── down.png ├── find.ico ├── right.png ├── refresh.png ├── search.png ├── folder_add.png ├── folder_go.png ├── bin_recycle.png └── folder_delete.png ├── main.cpp ├── ui ├── duplicate_img_table_view.hpp ├── duplicate_img_table_view.cpp ├── paint_custom_words.hpp ├── single_bar_plot.hpp ├── basic_setting_dialog.hpp ├── paint_custom_words.cpp ├── advance_setting_dialog.hpp ├── basic_setting_dialog.cpp ├── single_bar_plot.cpp ├── basic_setting_dialog.ui ├── advance_setting_dialog.cpp └── advance_setting_dialog.ui ├── pics.qrc ├── model ├── folder_model.hpp ├── folder_model.cpp ├── duplicate_img_model.hpp └── duplicate_img_model.cpp ├── core ├── scan_folder.hpp ├── pics_find_img_hash.hpp ├── scan_folder.cpp ├── vp_tree.hpp └── pics_find_img_hash.cpp ├── DBAD_License.md ├── DETAILS.md ├── similar_vision.pro ├── README.md ├── mainwindow.hpp ├── mainwindow.ui └── mainwindow.cpp /pics/similar_vision.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "find.ico" 2 | -------------------------------------------------------------------------------- /pics/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereomatchingkiss/similar_vision/HEAD/pics/up.png -------------------------------------------------------------------------------- /pics/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereomatchingkiss/similar_vision/HEAD/pics/down.png -------------------------------------------------------------------------------- /pics/find.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereomatchingkiss/similar_vision/HEAD/pics/find.ico -------------------------------------------------------------------------------- /pics/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereomatchingkiss/similar_vision/HEAD/pics/right.png -------------------------------------------------------------------------------- /pics/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereomatchingkiss/similar_vision/HEAD/pics/refresh.png -------------------------------------------------------------------------------- /pics/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereomatchingkiss/similar_vision/HEAD/pics/search.png -------------------------------------------------------------------------------- /pics/folder_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereomatchingkiss/similar_vision/HEAD/pics/folder_add.png -------------------------------------------------------------------------------- /pics/folder_go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereomatchingkiss/similar_vision/HEAD/pics/folder_go.png -------------------------------------------------------------------------------- /pics/bin_recycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereomatchingkiss/similar_vision/HEAD/pics/bin_recycle.png -------------------------------------------------------------------------------- /pics/folder_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereomatchingkiss/similar_vision/HEAD/pics/folder_delete.png -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.hpp" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | QCoreApplication::setOrganizationName("freedom"); 8 | QCoreApplication::setApplicationName("similar_vision"); 9 | 10 | MainWindow w; 11 | w.show(); 12 | 13 | return a.exec(); 14 | } 15 | -------------------------------------------------------------------------------- /ui/duplicate_img_table_view.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DUPLICATE_IMG_TABLE_VIEW_HPP 2 | #define DUPLICATE_IMG_TABLE_VIEW_HPP 3 | 4 | #include 5 | 6 | class duplicate_img_table_view : public QTableView 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit duplicate_img_table_view(QWidget *parent = nullptr); 12 | 13 | void keyPressEvent(QKeyEvent *event); 14 | 15 | signals: 16 | void key_press(int key); 17 | }; 18 | 19 | #endif // DUPLICATE_IMG_TABLE_VIEW_HPP 20 | -------------------------------------------------------------------------------- /ui/duplicate_img_table_view.cpp: -------------------------------------------------------------------------------- 1 | #include "duplicate_img_table_view.hpp" 2 | 3 | #include 4 | 5 | duplicate_img_table_view::duplicate_img_table_view(QWidget *parent) : 6 | QTableView(parent) 7 | { 8 | 9 | } 10 | 11 | void duplicate_img_table_view::keyPressEvent(QKeyEvent *event) 12 | { 13 | QTableView::keyPressEvent(event); 14 | 15 | if(event->key() == Qt::Key_Up || event->key() == Qt::Key_Down){ 16 | emit key_press(event->key()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pics.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | pics/down.png 4 | pics/refresh.png 5 | pics/search.png 6 | pics/up.png 7 | pics/bin_recycle.png 8 | pics/folder_add.png 9 | pics/folder_delete.png 10 | pics/folder_go.png 11 | pics/right.png 12 | pics/find.ico 13 | 14 | 15 | -------------------------------------------------------------------------------- /model/folder_model.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FOLDER_MODEL_HPP 2 | #define FOLDER_MODEL_HPP 3 | 4 | #include 5 | 6 | class folder_model : public QStringListModel 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit folder_model(QObject *parent = nullptr); 11 | 12 | bool dropMimeData(const QMimeData *data, Qt::DropAction action, 13 | int row, int column, 14 | const QModelIndex &parent) override; 15 | 16 | Qt::ItemFlags flags(const QModelIndex &index) const override; 17 | QStringList mimeTypes() const override; 18 | 19 | signals: 20 | void drop_data(); 21 | 22 | private: 23 | 24 | }; 25 | 26 | #endif // FOLDER_MODEL_HPP 27 | -------------------------------------------------------------------------------- /ui/paint_custom_words.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAINT_CUSTOM_WORDS_H 2 | #define PAINT_CUSTOM_WORDS_H 3 | 4 | #include 5 | 6 | class paint_custom_words : public QListView 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit paint_custom_words(QWidget *parent = nullptr); 11 | 12 | QString const& get_empty_str() const; 13 | void set_emptry_str(QString const &value); 14 | 15 | signals: 16 | void view_selected(); 17 | 18 | private: 19 | void keyPressEvent(QKeyEvent *event) override; 20 | void mousePressEvent(QMouseEvent *event) override; 21 | void paintEvent(QPaintEvent *e) override; 22 | 23 | QString empty_str_; 24 | }; 25 | 26 | #endif // PAINT_CUSTOM_WORDS_H 27 | -------------------------------------------------------------------------------- /core/scan_folder.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SCAN_FOLDER_HPP 2 | #define SCAN_FOLDER_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | class scan_folder : public QThread 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit scan_folder(QStringList const &abs_dirs, 13 | QStringList const &scan_types, 14 | bool scan_subfolders = true, 15 | QObject *parent = nullptr); 16 | 17 | QStringList get_abs_file_path() const; 18 | 19 | signals: 20 | void end(); 21 | void progress(QString msg); 22 | 23 | private: 24 | QStringList exclude_child_folders(QStringList const &folders) const; 25 | 26 | void run() override; 27 | 28 | void scan_folders(); 29 | 30 | QStringList abs_dirs_; 31 | QStringList files_; 32 | bool scan_subfolders_; 33 | QStringList scan_types_; 34 | }; 35 | 36 | #endif // SCAN_FOLDER_HPP 37 | -------------------------------------------------------------------------------- /ui/single_bar_plot.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SINGLE_BAR_PLOT_HPP 2 | #define SINGLE_BAR_PLOT_HPP 3 | 4 | #include 5 | 6 | class QwtColumnSymbol; 7 | class QwtPlotBarChart; 8 | 9 | class single_bar_plot : public QwtPlot 10 | { 11 | public: 12 | explicit single_bar_plot(QWidget *parent = nullptr); 13 | 14 | void set_data(QVector const &value, 15 | QStringList const &labels, 16 | QColor const &color = QColor(115, 186, 37)); 17 | 18 | void set_labels_title(QString const &title); 19 | void set_main_title(QString const &title); 20 | void set_orientation(Qt::Orientation orientation); 21 | void set_values_title(QString const &title); 22 | 23 | private: 24 | QwtPlotBarChart *bar_chart_; 25 | QStringList labels_; 26 | QString labels_title_; 27 | QwtColumnSymbol *symbol_; 28 | QString value_title_; 29 | }; 30 | 31 | #endif // SINGLE_BAR_PLOT_HPP 32 | -------------------------------------------------------------------------------- /ui/basic_setting_dialog.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BASIC_SETTING_DIALOG_HPP 2 | #define BASIC_SETTING_DIALOG_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace Ui { 10 | class basic_setting_dialog; 11 | } 12 | 13 | class QAbstractButton; 14 | class QCheckBox; 15 | 16 | class basic_setting_dialog : public QDialog 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | explicit basic_setting_dialog(QWidget *parent = 0); 22 | ~basic_setting_dialog(); 23 | 24 | bool auto_update() const; 25 | bool perfect_match() const; 26 | QStringList scan_img_type() const; 27 | 28 | private slots: 29 | void on_buttonBox_accepted(); 30 | 31 | void on_buttonBox_rejected(); 32 | 33 | private: 34 | void closeEvent(QCloseEvent *event) override; 35 | 36 | Ui::basic_setting_dialog *ui; 37 | 38 | std::vector> check_box_settings_; 39 | std::vector> origin_state_; 40 | }; 41 | 42 | #endif // BASIC_SETTING_DIALOG_HPP 43 | -------------------------------------------------------------------------------- /ui/paint_custom_words.cpp: -------------------------------------------------------------------------------- 1 | #include "paint_custom_words.hpp" 2 | 3 | #include 4 | #include 5 | 6 | paint_custom_words::paint_custom_words(QWidget *parent) : 7 | QListView(parent), 8 | empty_str_("You can drag directories into here") 9 | { 10 | 11 | } 12 | 13 | const QString &paint_custom_words::get_empty_str() const 14 | { 15 | return empty_str_; 16 | } 17 | 18 | void paint_custom_words::set_emptry_str(const QString &value) 19 | { 20 | empty_str_ = value; 21 | } 22 | 23 | void paint_custom_words::keyPressEvent(QKeyEvent *event) 24 | { 25 | QListView::keyPressEvent(event); 26 | emit view_selected(); 27 | } 28 | 29 | void paint_custom_words::mousePressEvent(QMouseEvent *event) 30 | { 31 | QListView::mousePressEvent(event); 32 | if(!indexAt(event->pos()).isValid()){ 33 | clearSelection(); 34 | } 35 | emit view_selected(); 36 | } 37 | 38 | void paint_custom_words::paintEvent(QPaintEvent *e) 39 | { 40 | QListView::paintEvent(e); 41 | if(model() && model()->rowCount(rootIndex()) > 0) return; 42 | 43 | QPainter p(viewport()); 44 | p.drawText(rect(), Qt::AlignCenter, empty_str_); 45 | } 46 | -------------------------------------------------------------------------------- /DBAD_License.md: -------------------------------------------------------------------------------- 1 | # DON'T BE A DICK PUBLIC LICENSE 2 | 3 | > Version 1, December 2009 4 | 5 | > Copyright (C) 2009 Philip Sturgeon 6 | 7 | Everyone is permitted to copy and distribute verbatim or modified 8 | copies of this license document, and changing it is allowed as long 9 | as the name is changed. 10 | 11 | > DON'T BE A DICK PUBLIC LICENSE 12 | > TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 13 | 14 | 1. Do whatever you like with the original work, just don't be a dick. 15 | 16 | Being a dick includes - but is not limited to - the following instances: 17 | 18 | 1a. Outright copyright infringement - Don't just copy this and change the name. 19 | 1b. Selling the unmodified original with no work done what-so-ever, that's REALLY being a dick. 20 | 1c. Modifying the original work to contain hidden harmful content. That would make you a PROPER dick. 21 | 22 | 2. If you become rich through modifications, related works/services, or supporting the original work, 23 | share the love. Only a dick would make loads off this work and not buy the original work's 24 | creator(s) a pint. 25 | 26 | 3. Code is provided with no warranty. Using somebody else's code and bitching when it goes wrong makes 27 | you a DONKEY dick. Fix the problem yourself. A non-dick would submit the fix back. 28 | -------------------------------------------------------------------------------- /model/folder_model.cpp: -------------------------------------------------------------------------------- 1 | #include "folder_model.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | folder_model::folder_model(QObject *parent) : 9 | QStringListModel(parent) 10 | { 11 | 12 | } 13 | 14 | bool folder_model::dropMimeData(const QMimeData *data, Qt::DropAction action, 15 | int row, int column, const QModelIndex &parent) 16 | { 17 | Q_UNUSED(row); 18 | Q_UNUSED(column); 19 | Q_UNUSED(parent); 20 | 21 | if(action == Qt::CopyAction && data->hasUrls()){ 22 | auto list = stringList(); 23 | int const pre_size = list.size(); 24 | for(auto const &url : data->urls()){ 25 | QFileInfo const info(url.toLocalFile()); 26 | if(info.isDir()){ 27 | list.push_back(info.absoluteFilePath()); 28 | } 29 | } 30 | list.removeDuplicates(); 31 | setStringList(list); 32 | 33 | emit drop_data(); 34 | 35 | return pre_size != list.size(); 36 | } 37 | 38 | return false; 39 | } 40 | 41 | Qt::ItemFlags folder_model::flags(const QModelIndex &index) const 42 | { 43 | return QStringListModel::flags(index) | Qt::ItemIsDropEnabled; 44 | } 45 | 46 | QStringList folder_model::mimeTypes() const 47 | { 48 | return QStringList()<<"text/uri-list"; 49 | } 50 | -------------------------------------------------------------------------------- /ui/advance_setting_dialog.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ADVANCE_SETTING_DIALOG_HPP 2 | #define ADVANCE_SETTING_DIALOG_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace Ui { 12 | class advance_setting_dialog; 13 | } 14 | 15 | class QRadioButton; 16 | class QSlider; 17 | 18 | class advance_setting_dialog : public QDialog 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | explicit advance_setting_dialog(QWidget *parent = 0); 24 | ~advance_setting_dialog(); 25 | 26 | cv::Ptr get_hash_algo() const; 27 | 28 | double get_threshold() const; 29 | 30 | private slots: 31 | void on_pb_avg_hash_default_clicked(); 32 | 33 | void on_pb_bmh_0_default_clicked(); 34 | 35 | void on_pb_bmh_1_default_clicked(); 36 | 37 | void on_pb_marr_hildreth_default_clicked(); 38 | 39 | void on_pb_phash_default_clicked(); 40 | 41 | private: 42 | void cancel_clicked(); 43 | void create_connection(); 44 | void plot_accuracy_chart(bool val = false); 45 | void plot_hash_chart(); 46 | void update_hash_origin_state(); 47 | 48 | Ui::advance_setting_dialog *ui; 49 | 50 | std::vector default_hash_threshold_; 51 | std::vector default_thresh_name_; 52 | std::vector hash_buttons_; 53 | std::vector sliders_; 54 | 55 | QStringList hash_name_; 56 | std::vector hash_origin_state_; 57 | }; 58 | 59 | #endif // ADVANCE_SETTING_DIALOG_HPP 60 | -------------------------------------------------------------------------------- /core/pics_find_img_hash.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICS_FIND_IMG_HASH_HPP 2 | #define PICS_FIND_IMG_HASH_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class img_reader; 15 | 16 | class pics_find_img_hash : public QThread 17 | { 18 | Q_OBJECT 19 | public: 20 | explicit pics_find_img_hash( 21 | cv::Ptr algo, 22 | QStringList const &abs_file_path, 23 | double threshold, 24 | QObject *parent = nullptr); 25 | 26 | QStringList get_duplicate_img() const; 27 | QStringList get_original_img() const; 28 | 29 | signals: 30 | void end(); 31 | void progress(QString msg); 32 | 33 | private: 34 | void compare_hash(); 35 | 36 | //Different versions of compute hash functions exist for performance 37 | //measurement 38 | void compute_hash(); 39 | void compute_hash_mt(); 40 | void compute_hash_mt2(); 41 | 42 | std::pair read_img(QString const &img_path); 43 | void run() override; 44 | 45 | QStringList abs_file_path_; 46 | cv::Ptr algo_; 47 | std::condition_variable cv_; 48 | QStringList duplicate_img_; 49 | QElapsedTimer elapsed_time_; 50 | bool finished_; 51 | std::vector> hash_arr_; 52 | std::mutex mutex_; 53 | QStringList original_img_; 54 | int const pool_size_; 55 | double threshold_; 56 | }; 57 | 58 | #endif // FIND_SIMILAR_PICS_HPP 59 | -------------------------------------------------------------------------------- /DETAILS.md: -------------------------------------------------------------------------------- 1 | # Synopsis 2 | 3 | similar_vision is a free gui tools which could visualize the 4 | performance of difference algorithms on finding similar images and videos. 5 | 6 | # Motivation 7 | 8 | - Show the power of [img_hash module](https://github.com/stereomatchingkiss/opencv_contrib/tree/img_hash/modules/img_hash) 9 | - Create a platform to demonstrate the performance of different algorithms 10 | - It is fun 11 | 12 | # Dependencies 13 | 14 | The dependency of this project are 15 | 16 | 1. [Qt5.6.x](https://www.qt.io/download-open-source/#section-2) 17 | 2. [OpenCV3.x](http://opencv.org/) 18 | 3. [qwt](http://qwt.sourceforge.net/) 19 | 4. [boost1.61](http://www.boost.org/) 20 | 5. [img_hash module of opencv_contrib(haven't merged yet)](https://github.com/stereomatchingkiss/opencv_contrib/tree/img_hash/modules/img_hash) 21 | 6. [auto_update](https://github.com/stereomatchingkiss/auto_updater) 22 | 23 | #How to build them 24 | 25 | ##Qt5.6.x 26 | 27 | Community already build it for us, just download the installer suit your os and compiler. 28 | 29 | ##OpenCV3.x 30 | 31 | Same as Qt5, community build it for us, lucky part of this library is it is very easy to build, 32 | the community do a good job on mantaining their make file. 33 | 34 | ##qwt 35 | 36 | Open the qwt.pro file by your QtCreator, build it and wait for everything to be done. 37 | 38 | ##boost 39 | 40 | This app reply on header only libraries, they are boost::multi_index and boost::graph. 41 | 42 | ##img_hash module 43 | 44 | This [page]((https://github.com/stereomatchingkiss/opencv_contrib/tree/img_hash/modules/img_hash)) show you the details. 45 | 46 | ##auto_updater 47 | 48 | similar_vision use this small CLI tool to do the auto update chores, this tool do not affect build process of simialr_vision. 49 | 50 | # Bugs report and features request 51 | 52 | If you found any bugs or have any features request, please open the issue at github. 53 | I will try to solve them when I have time. 54 | -------------------------------------------------------------------------------- /similar_vision.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2016-06-27T15:15:00 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui concurrent 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | CONFIG += c++14 12 | 13 | TARGET = similar_vision 14 | TEMPLATE = app 15 | 16 | INCLUDEPATH += ui 17 | #INCLUDEPATH += ../ 18 | 19 | win32{ 20 | RC_FILE = pics/similar_vision.rc 21 | 22 | include(../pri/boost.pri) 23 | include(../pri/cv_dev.pri) 24 | include(../pri/qwt.pri) 25 | 26 | } 27 | 28 | unix:!macx { 29 | 30 | LIBS += -L/usr/local/lib -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs -lopencv_video -lopencv_videoio 31 | LIBS += -L/usr/local/lib -lopencv_img_hash 32 | 33 | } 34 | 35 | macx: { 36 | 37 | #INCLUDEPATH += ../3rdLibs/opencv/opencv/build_macos/install/include/opencv4 38 | #LIBS += -L"../3rdLibs/opencv/opencv/build_macos/install/lib" \ 39 | # -l"opencv_world" -l"opencv_img_hash" 40 | 41 | INCLUDEPATH += ../3rdLibs/opencv/opencv/build_macos_static/install/include/opencv4 42 | LIBS += -L"../3rdLibs/opencv/opencv/build_macos_static/install/lib" \ 43 | -l"opencv_world" -l"opencv_img_hash" 44 | 45 | INCLUDEPATH += ../3rdLibs/boost/boost_1_70_0/ 46 | 47 | QWT_ROOT = ../3rdLibs/qwt/qwt-6.1.4 48 | 49 | INCLUDEPATH += $${QWT_ROOT}/src 50 | LIBS += $${QWT_ROOT}/lib/libqwt.a 51 | 52 | } 53 | 54 | SOURCES += main.cpp\ 55 | mainwindow.cpp \ 56 | ui/paint_custom_words.cpp \ 57 | model/folder_model.cpp \ 58 | core/scan_folder.cpp \ 59 | core/pics_find_img_hash.cpp \ 60 | model/duplicate_img_model.cpp \ 61 | ui/basic_setting_dialog.cpp \ 62 | ui/advance_setting_dialog.cpp \ 63 | ui/single_bar_plot.cpp \ 64 | ui/duplicate_img_table_view.cpp 65 | 66 | HEADERS += mainwindow.hpp \ 67 | ui/paint_custom_words.hpp \ 68 | model/folder_model.hpp \ 69 | core/scan_folder.hpp \ 70 | core/pics_find_img_hash.hpp \ 71 | core/vp_tree.hpp \ 72 | model/duplicate_img_model.hpp \ 73 | ui/basic_setting_dialog.hpp \ 74 | ui/advance_setting_dialog.hpp \ 75 | ui/single_bar_plot.hpp \ 76 | ui/duplicate_img_table_view.hpp 77 | 78 | FORMS += mainwindow.ui \ 79 | ui/basic_setting_dialog.ui \ 80 | ui/advance_setting_dialog.ui 81 | 82 | RESOURCES += \ 83 | pics.qrc 84 | -------------------------------------------------------------------------------- /model/duplicate_img_model.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DUPLICATE_IMG_MODEL_HPP 2 | #define DUPLICATE_IMG_MODEL_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class duplicate_img_model : public QAbstractTableModel 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit duplicate_img_model(QObject *parent = nullptr); 16 | 17 | void clear(); 18 | 19 | int columnCount(const QModelIndex &parent) const override; 20 | QVariant data(const QModelIndex &index, int role) const override; 21 | 22 | QVariant headerData(int section, Qt::Orientation orientation, int role) const override; 23 | 24 | bool remove_img(QString const &img); 25 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 26 | 27 | void set_img_set(QStringList const &origin_img, QStringList const &duplicate_img); 28 | 29 | private: 30 | bool insertRows(int row, int count, const QModelIndex &parent = {}) override; 31 | bool removeRows(int row, int count, const QModelIndex &parent = {}) override; 32 | 33 | struct model_items 34 | { 35 | model_items(QString const &duplicate_img, 36 | QString const &origin_img) : 37 | duplicate_img_(duplicate_img), 38 | origin_img_(origin_img) 39 | {} 40 | QString duplicate_img_; 41 | QString origin_img_; 42 | }; 43 | 44 | struct duplicate{}; 45 | struct origin{}; 46 | 47 | // define a multiply indexed set with indices by id and name 48 | using items_set = boost::multi_index::multi_index_container 49 | < 50 | model_items, 51 | boost::multi_index::indexed_by< 52 | boost::multi_index::random_access<>, 53 | boost::multi_index::ordered_non_unique< 54 | boost::multi_index::tag, 55 | boost::multi_index::member 56 | >, 57 | boost::multi_index::ordered_non_unique< 58 | boost::multi_index::tag, 59 | boost::multi_index::member 60 | > 61 | > 62 | >; 63 | 64 | items_set items_; 65 | }; 66 | 67 | #endif // DUPLICATE_IMG_MODEL_HPP 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Synopsis 2 | 3 | similar_vision is a free tool inspired by [Awesome Duplicate Photo Finder](http://www.duplicate-finder.com/photo.html), 4 | this app can help you find and remove duplicate photos(and videos in the future) on your PC. 5 | 6 | Same as [Awesome Duplicate Photo Finder](http://www.duplicate-finder.com/photo.html), similar_vision is a completely FREE Software. It contains absolutely 7 | NO ADWARE, NO SPYWARE, NO REGISTRATION, NO POPUPS, NO MALWARE or other unwanted software. 8 | 9 | Although this app is inspired by [Awesome Duplicate Photo Finder](http://www.duplicate-finder.com/photo.html), it is not a 10 | copy and paste of it. This app adds some new features on finding similar images and intend to support the functions of 11 | finding similar videos in the future. 12 | 13 | # ScreenShot 14 | 15 | ![Found image](https://a.fsdn.com/con/app/proj/similar-vision/screenshots/similar_00.JPG) 16 | ![Accuracy chart](https://a.fsdn.com/con/app/proj/similar-vision/screenshots/similar_03.JPG) 17 | 18 | # Features 19 | - Simple interface 20 | - Finds similar or exact duplicate photos 21 | - Search through the following image types: BMP, JPG, PNG, PBM, PGM, PPM, TIFF, WEBP 22 | - Support 4 algorithms 23 | - Average hash 24 | - PHash 25 | - Marr Hildreth Hash 26 | - Block Mean Hash(mode 0 and mode 1) 27 | - Able to compare pictures that was resized, blurring, different contrast, 28 | blurring, noise(gaussian), embedded with watermark, different format, jpeg compression 29 | - Add multiple folders or drives for scanning 30 | - Works with removable devices (USB etc.) 31 | - Support moving and deleting of duplicate photos 32 | - Saving settings, including geometry 33 | - Self explain chart of the pros and cons of different algorithms 34 | - You can update this app with one click 35 | - This is an open source software, you can tweak it as you like 36 | - Run on windows XP~windows10, will support Ubuntu 64 in the future 37 | 38 | # Installation 39 | 40 | 1. Unzip the file you download from [SourceForge](https://sourceforge.net/projects/similar-vision/files/?source=navbar) 41 | 2. Double click the program--similar_vision, this is all you need to do 42 | 43 | # Email 44 | 45 | If you have any questions, like bugs, desired features, nice algorithms want me to 46 | implement, please send it to my email--thamngapwei@gmail.com. 47 | 48 | # For programmers 49 | 50 | Please go to [details](https://github.com/stereomatchingkiss/similar_vision/blob/master/DETAILS.md) 51 | 52 | #License 53 | 54 | similar_vision is free and open-source software, it is released under the [DBAD](https://github.com/stereomatchingkiss/similar_vision/blob/master/DBAD_License.md) license. 55 | -------------------------------------------------------------------------------- /mainwindow.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_HPP 2 | #define MAINWINDOW_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace Ui { 9 | class MainWindow; 10 | } 11 | 12 | class QGraphicsView; 13 | class QElapsedTimer; 14 | 15 | class duplicate_img_model; 16 | class folder_model; 17 | class pics_find_img_hash; 18 | class scan_folder; 19 | 20 | class MainWindow : public QMainWindow 21 | { 22 | Q_OBJECT 23 | 24 | public: 25 | explicit MainWindow(QWidget *parent = 0); 26 | ~MainWindow(); 27 | 28 | private slots: 29 | void on_pb_add_folder_clicked(); 30 | 31 | void on_pb_find_folder_clicked(); 32 | 33 | void on_pb_refresh_clicked(); 34 | 35 | void on_pb_delete_folder_clicked(); 36 | 37 | void on_pb_up_clicked(); 38 | 39 | void on_pb_down_clicked(); 40 | 41 | void on_pb_lf_move_clicked(); 42 | 43 | void on_pb_rt_move_clicked(); 44 | 45 | void on_pb_lf_browse_clicked(); 46 | 47 | void on_pb_rt_browse_clicked(); 48 | 49 | void on_pb_lf_recycle_clicked(); 50 | 51 | void on_pb_rt_recycle_clicked(); 52 | 53 | void on_action_advance_setting_triggered(); 54 | 55 | void on_action_basic_setting_triggered(); 56 | 57 | void on_action_exit_triggered(); 58 | 59 | void on_action_qt_triggered(); 60 | 61 | void on_action_fatcow_triggered(); 62 | 63 | void on_action_visit_program_website_triggered(); 64 | 65 | void on_action_check_for_update_triggered(); 66 | 67 | private: 68 | bool can_update() const; 69 | void delete_img(QString const &name); 70 | void duplicate_img_select(QModelIndex const &index); 71 | void enable_folder_edit_ui(); 72 | void enable_image_edit_ui(); 73 | void enable_main_ui(); 74 | void enable_up_down_arrow(int item_size, int select_size); 75 | void find_similar_pics(); 76 | void find_similar_pics_end(); 77 | QString get_select_name(int col); 78 | void move_file(QString const &name); 79 | void open_folder(QString const &name); 80 | void remove_img_from_table(QString const &name); 81 | void resizeEvent(QResizeEvent*) override; 82 | void scan_folders(); 83 | void set_label_info(QString info); 84 | bool view_duplicate_img(QString const &name, 85 | bool img_read, 86 | QGraphicsView *view); 87 | 88 | Ui::MainWindow *ui; 89 | 90 | duplicate_img_model *duplicate_img_model_; 91 | folder_model *folder_model_; 92 | bool img_lf_changed_; 93 | bool img_rt_changed_; 94 | pics_find_img_hash *pf_img_hash_; 95 | QString pre_img_name_lf_; 96 | QString pre_img_name_rt_; 97 | scan_folder *scf_thread_; 98 | std::unique_ptr timer_; 99 | }; 100 | 101 | #endif // MAINWINDOW_HPP 102 | -------------------------------------------------------------------------------- /model/duplicate_img_model.cpp: -------------------------------------------------------------------------------- 1 | #include "duplicate_img_model.hpp" 2 | 3 | #include 4 | 5 | duplicate_img_model::duplicate_img_model(QObject *parent) : 6 | QAbstractTableModel(parent) 7 | { 8 | 9 | } 10 | 11 | void duplicate_img_model::clear() 12 | { 13 | set_img_set({}, {}); 14 | } 15 | 16 | int duplicate_img_model::columnCount(const QModelIndex&) const 17 | { 18 | return 2; 19 | } 20 | 21 | QVariant duplicate_img_model::data(const QModelIndex &index, int role) const 22 | { 23 | switch(role){ 24 | case Qt::DisplayRole:{ 25 | switch(index.column()){ 26 | case 0: { 27 | return items_.get<0>()[index.row()].origin_img_; 28 | } 29 | case 1: { 30 | return items_.get<0>()[index.row()].duplicate_img_; 31 | } 32 | default:{ 33 | return {}; 34 | } 35 | } 36 | } 37 | } 38 | 39 | return {}; 40 | } 41 | 42 | QVariant duplicate_img_model::headerData(int section, Qt::Orientation orientation, int role) const 43 | { 44 | if(role == Qt::DisplayRole){ 45 | if(orientation == Qt::Horizontal){ 46 | switch(section){ 47 | case 0: { 48 | return QStringLiteral("Original Image"); 49 | } 50 | case 1: { 51 | return QStringLiteral("Duplicate Image"); 52 | } 53 | default:{ 54 | return {}; 55 | } 56 | } 57 | }else if(orientation == Qt::Vertical){ 58 | return section + 1; 59 | } 60 | } 61 | return {}; 62 | } 63 | 64 | bool duplicate_img_model::remove_img(const QString &img) 65 | { 66 | //TODO : use a cheaper solution to remove image 67 | size_t const ori_size = items_.get<0>().size(); 68 | items_.get().erase(img); 69 | items_.get().erase(img); 70 | if(ori_size != items_.get<0>().size()){ 71 | removeRows(0, static_cast(ori_size)); 72 | insertRows(0, static_cast(items_.get<0>().size())); 73 | return true; 74 | } 75 | 76 | return false; 77 | } 78 | 79 | bool duplicate_img_model::insertRows(int row, int count, const QModelIndex&) 80 | { 81 | if(!items_.get<0>().empty()){ 82 | beginInsertRows({}, row, row + count - 1); 83 | endInsertRows(); 84 | } 85 | 86 | return true; 87 | } 88 | 89 | bool duplicate_img_model::removeRows(int row, int count, const QModelIndex&) 90 | { 91 | beginRemoveRows({}, row, row + count - 1); 92 | endRemoveRows(); 93 | 94 | return true; 95 | } 96 | 97 | int duplicate_img_model::rowCount(const QModelIndex&) const 98 | { 99 | return static_cast(items_.get<0>().size()); 100 | } 101 | 102 | void duplicate_img_model::set_img_set(const QStringList &origin_img, const QStringList &duplicate_img) 103 | { 104 | removeRows(0, static_cast(items_.get<0>().size())); 105 | auto &items = items_.get<0>(); 106 | items.clear(); 107 | for(int i = 0; i != origin_img.size(); ++i){ 108 | items.emplace_back(origin_img[i], duplicate_img[i]); 109 | } 110 | insertRows(0, origin_img.size()); 111 | } 112 | -------------------------------------------------------------------------------- /core/scan_folder.cpp: -------------------------------------------------------------------------------- 1 | #include "scan_folder.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | scan_folder::scan_folder(QStringList const &abs_dirs, 13 | QStringList const &scan_types, 14 | bool scan_subfolders, 15 | QObject *parent) : 16 | QThread(parent), 17 | abs_dirs_(abs_dirs), 18 | scan_subfolders_(scan_subfolders), 19 | scan_types_(scan_types) 20 | { 21 | 22 | } 23 | 24 | QStringList scan_folder::get_abs_file_path() const 25 | { 26 | return files_; 27 | } 28 | 29 | /** 30 | * @brief Exclude child folders if any 31 | * @param folders self explained 32 | * @return folders without any child folders 33 | * @code 34 | * "c:/users/wahaha/pics/konosuba" 35 | * "c:/users/wahaha/pics/konosuba/first_episode" 36 | * "c:/users/wahaha/pics/konosuba/sec_episode" 37 | * 38 | * There are two child folders of konosuba, they 39 | * are first episode and second episode, this function 40 | * will remove these child folders. 41 | */ 42 | QStringList scan_folder::exclude_child_folders(const QStringList &folders) const 43 | { 44 | //TODO : find a better, more efficient, easier to understand solution 45 | QStringList result = folders; 46 | result.sort(); 47 | for(int i = 0; i < result.size() - 1; ++i){ 48 | QStringList exclude_fd; 49 | for(int k = 0; k <= i; ++k){ 50 | exclude_fd.push_back(result[k]); 51 | } 52 | for(int j = i; j != result.size(); ++j){ 53 | if(!result[j].startsWith(result[i])){ 54 | exclude_fd.push_back(result[j]); 55 | } 56 | } 57 | result = exclude_fd; 58 | } 59 | 60 | return result; 61 | } 62 | 63 | void scan_folder::scan_folders() 64 | { 65 | files_.clear(); 66 | if(scan_subfolders_){ 67 | abs_dirs_ = exclude_child_folders(abs_dirs_); 68 | } 69 | 70 | struct icp 71 | { 72 | bool operator()(QString const &lhs, QString const &rhs) const 73 | { 74 | return lhs.compare(rhs, Qt::CaseInsensitive) > 0; 75 | } 76 | }; 77 | 78 | std::set is_img; 79 | for(auto const &type : scan_types_){ 80 | is_img.insert(type); 81 | } 82 | 83 | emit progress("Total files found : 0"); 84 | for(auto const &dir : abs_dirs_){ 85 | auto const iter_flags = scan_subfolders_ ? 86 | QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags; 87 | QDirIterator directories(dir, 88 | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot, 89 | iter_flags); 90 | while(directories.hasNext()){ 91 | //qDebug()< 5 | #include 6 | #include 7 | 8 | basic_setting_dialog::basic_setting_dialog(QWidget *parent) : 9 | QDialog(parent), 10 | ui(new Ui::basic_setting_dialog) 11 | { 12 | ui->setupUi(this); 13 | 14 | ui->group_box_search_opt->hide(); 15 | 16 | check_box_settings_.emplace_back("img_type/bmp_checked", ui->cb_bmp); 17 | check_box_settings_.emplace_back("img_type/jpeg_checked", ui->cb_jpeg); 18 | check_box_settings_.emplace_back("img_type/pbm_checked", ui->cb_pbm); 19 | check_box_settings_.emplace_back("img_type/pgm_checked", ui->cb_pgm); 20 | check_box_settings_.emplace_back("img_type/png_checked", ui->cb_png); 21 | check_box_settings_.emplace_back("img_type/ppm_checked", ui->cb_ppm); 22 | check_box_settings_.emplace_back("img_type/tiff_checked", ui->cb_tiff); 23 | check_box_settings_.emplace_back("img_type/webp_checked", ui->cb_webp); 24 | check_box_settings_.emplace_back("Search/perfect_match", ui->cb_perfect_match); 25 | check_box_settings_.emplace_back("App/auto_update", ui->cb_auto_update); 26 | 27 | QSettings const settings; 28 | for(auto &pair : check_box_settings_){ 29 | if(settings.contains(pair.first)){ 30 | pair.second->setChecked(settings.value(pair.first).toBool()); 31 | } 32 | } 33 | if(settings.contains("basic_setting_geometry")){ 34 | restoreGeometry(settings.value("basic_setting_geometry").toByteArray()); 35 | } 36 | 37 | origin_state_.emplace_back(ui->cb_bmp->isChecked(), ui->cb_bmp); 38 | origin_state_.emplace_back(ui->cb_jpeg->isChecked(), ui->cb_jpeg); 39 | origin_state_.emplace_back(ui->cb_pbm->isChecked(), ui->cb_pbm); 40 | origin_state_.emplace_back(ui->cb_pgm->isChecked(), ui->cb_pgm); 41 | origin_state_.emplace_back(ui->cb_png->isChecked(), ui->cb_png); 42 | origin_state_.emplace_back(ui->cb_ppm->isChecked(), ui->cb_ppm); 43 | origin_state_.emplace_back(ui->cb_tiff->isChecked(), ui->cb_tiff); 44 | origin_state_.emplace_back(ui->cb_webp->isChecked(), ui->cb_webp); 45 | origin_state_.emplace_back(ui->cb_perfect_match->isChecked(), ui->cb_perfect_match); 46 | origin_state_.emplace_back(ui->cb_auto_update->isChecked(), ui->cb_auto_update); 47 | } 48 | 49 | basic_setting_dialog::~basic_setting_dialog() 50 | { 51 | delete ui; 52 | } 53 | 54 | bool basic_setting_dialog::auto_update() const 55 | { 56 | return ui->cb_auto_update->isChecked(); 57 | } 58 | 59 | bool basic_setting_dialog::perfect_match() const 60 | { 61 | return ui->cb_perfect_match->isChecked(); 62 | } 63 | 64 | QStringList basic_setting_dialog::scan_img_type() const 65 | { 66 | QStringList types; 67 | std::vector> const mapper 68 | { 69 | {"bmp", ui->cb_bmp}, {"jpeg", ui->cb_jpeg}, 70 | {"jpg", ui->cb_jpeg},{"pbm", ui->cb_pbm}, 71 | {"pgm", ui->cb_pgm}, {"png", ui->cb_png}, 72 | {"ppm", ui->cb_ppm}, {"tiff", ui->cb_tiff}, 73 | {"webp", ui->cb_webp}, {"psb", ui->cb_psb}, 74 | {"psd", ui->cb_psd} 75 | }; 76 | for(auto const &pair : mapper){ 77 | if(pair.second->isChecked()){ 78 | types.push_back(pair.first); 79 | } 80 | } 81 | 82 | return types; 83 | } 84 | 85 | void basic_setting_dialog::closeEvent(QCloseEvent *event) 86 | { 87 | if(event){ 88 | QSettings settings; 89 | for(auto &pair : check_box_settings_){ 90 | settings.setValue(pair.first, pair.second->isChecked()); 91 | } 92 | settings.setValue("version", 1.0); 93 | settings.setValue("basic_setting_geometry", saveGeometry()); 94 | QDialog::closeEvent(event); 95 | } 96 | } 97 | 98 | void basic_setting_dialog::on_buttonBox_accepted() 99 | { 100 | for(auto &pair : origin_state_){ 101 | pair.first = pair.second->isChecked(); 102 | } 103 | close(); 104 | } 105 | 106 | void basic_setting_dialog::on_buttonBox_rejected() 107 | { 108 | for(auto &pair : origin_state_){ 109 | pair.second->setChecked(pair.first); 110 | } 111 | close(); 112 | } 113 | -------------------------------------------------------------------------------- /ui/single_bar_plot.cpp: -------------------------------------------------------------------------------- 1 | #include "single_bar_plot.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace{ 12 | 13 | class label_scale_draw: public QwtScaleDraw 14 | { 15 | public: 16 | label_scale_draw(Qt::Orientation orientation, const QStringList &labels): 17 | d_labels(labels) 18 | { 19 | setTickLength(QwtScaleDiv::MinorTick, 0); 20 | setTickLength(QwtScaleDiv::MediumTick, 0); 21 | setTickLength(QwtScaleDiv::MajorTick, 2); 22 | 23 | enableComponent(QwtScaleDraw::Backbone, false); 24 | 25 | if ( orientation == Qt::Vertical ){ 26 | setLabelRotation( -60.0 ); 27 | }else{ 28 | setLabelRotation( -20.0 ); 29 | } 30 | 31 | setLabelAlignment( Qt::AlignLeft | Qt::AlignVCenter ); 32 | } 33 | 34 | QwtText label(double value) const override 35 | { 36 | QwtText lbl; 37 | 38 | const int index = qRound( value ); 39 | if(index >= 0 && index < d_labels.size()){ 40 | lbl = d_labels[index]; 41 | } 42 | 43 | return lbl; 44 | } 45 | 46 | private: 47 | const QStringList d_labels; 48 | }; 49 | 50 | } //nameless namespace 51 | 52 | single_bar_plot::single_bar_plot(QWidget *parent) : 53 | QwtPlot(parent), 54 | bar_chart_(new QwtPlotBarChart) 55 | { 56 | bar_chart_->setLayoutPolicy(QwtPlotBarChart::AutoAdjustSamples); 57 | bar_chart_->setLayoutHint(4.0); // minimum width for a single bar 58 | bar_chart_->setSpacing(10); // spacing between bars 59 | 60 | setAutoFillBackground(true); 61 | setPalette(QColor("Linen")); 62 | 63 | QwtPlotCanvas *canvas = new QwtPlotCanvas; 64 | canvas->setLineWidth(2); 65 | canvas->setFrameStyle(QFrame::Box | QFrame::Sunken); 66 | canvas->setBorderRadius(10); 67 | 68 | QPalette canvasPalette(QColor("Plum")); 69 | canvasPalette.setColor(QPalette::Foreground, QColor("Indigo")); 70 | canvas->setPalette(canvasPalette); 71 | 72 | setCanvas(canvas); 73 | 74 | bar_chart_->attach(this); 75 | set_orientation(Qt::Vertical); 76 | setAutoReplot(false); 77 | 78 | symbol_ = new QwtColumnSymbol(QwtColumnSymbol::Box); 79 | symbol_->setLineWidth(2); 80 | symbol_->setFrameStyle(QwtColumnSymbol::Raised); 81 | symbol_->setPalette(QPalette(QColor(115, 186, 37))); 82 | bar_chart_->setSymbol(symbol_); 83 | } 84 | 85 | void single_bar_plot::set_data(const QVector &value, 86 | const QStringList &labels, 87 | QColor const &color) 88 | { 89 | if(labels.size() != value.size()){ 90 | //very rough error handle 91 | exit(1); 92 | } 93 | bar_chart_->setSamples(value); 94 | labels_ = labels; 95 | symbol_->setPalette(QPalette(color)); 96 | set_orientation(Qt::Vertical); 97 | } 98 | 99 | void single_bar_plot::set_main_title(const QString &title) 100 | { 101 | setTitle(title); 102 | } 103 | 104 | void single_bar_plot::set_orientation(Qt::Orientation orientation) 105 | { 106 | int axis1 = QwtPlot::xBottom; 107 | int axis2 = QwtPlot::yLeft; 108 | 109 | if(orientation == Qt::Horizontal){ 110 | std::swap(axis1, axis2); 111 | } 112 | 113 | bar_chart_->setOrientation( orientation ); 114 | 115 | setAxisTitle(axis1, labels_title_); 116 | setAxisMaxMinor(axis1, 3); 117 | setAxisScaleDraw(axis1, new label_scale_draw(orientation, labels_)); 118 | 119 | setAxisTitle(axis2, value_title_); 120 | setAxisMaxMinor(axis2, 3); 121 | 122 | QwtScaleDraw *scaleDraw = new QwtScaleDraw; 123 | scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 4 ); 124 | setAxisScaleDraw(axis2, scaleDraw); 125 | 126 | plotLayout()->setCanvasMargin(0); 127 | replot(); 128 | updateCanvasMargins(); 129 | } 130 | 131 | void single_bar_plot::set_labels_title(const QString &title) 132 | { 133 | labels_title_ = title; 134 | } 135 | 136 | void single_bar_plot::set_values_title(const QString &title) 137 | { 138 | value_title_ = title; 139 | } 140 | -------------------------------------------------------------------------------- /ui/basic_setting_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | basic_setting_dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 564 10 | 376 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Scan the following types 23 | 24 | 25 | 26 | 27 | 28 | BMP 29 | 30 | 31 | true 32 | 33 | 34 | 35 | 36 | 37 | 38 | JPEG 39 | 40 | 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | PNG 49 | 50 | 51 | true 52 | 53 | 54 | 55 | 56 | 57 | 58 | PBM 59 | 60 | 61 | true 62 | 63 | 64 | 65 | 66 | 67 | 68 | PGM 69 | 70 | 71 | true 72 | 73 | 74 | 75 | 76 | 77 | 78 | PPM 79 | 80 | 81 | true 82 | 83 | 84 | 85 | 86 | 87 | 88 | PSB 89 | 90 | 91 | true 92 | 93 | 94 | 95 | 96 | 97 | 98 | PSD 99 | 100 | 101 | true 102 | 103 | 104 | 105 | 106 | 107 | 108 | TIFF 109 | 110 | 111 | true 112 | 113 | 114 | 115 | 116 | 117 | 118 | WEBP 119 | 120 | 121 | true 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | Search options 134 | 135 | 136 | 137 | 138 | 139 | Search perfect match only 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | Application 150 | 151 | 152 | 153 | 154 | 155 | Automatically Check for updates 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | Qt::Horizontal 170 | 171 | 172 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | buttonBox 182 | accepted() 183 | basic_setting_dialog 184 | accept() 185 | 186 | 187 | 257 188 | 366 189 | 190 | 191 | 157 192 | 274 193 | 194 | 195 | 196 | 197 | 198 | cancel_clicked() 199 | 200 | 201 | -------------------------------------------------------------------------------- /core/vp_tree.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VP_TREE_HPP 2 | #define VP_TREE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // A VP-Tree implementation, by Steve Hanov. (steve.hanov@gmail.com) 11 | // Released to the Public Domain 12 | // Based on "Data Structures and Algorithms for Nearest Neighbor Search" by Peter N. Yianilos 13 | 14 | template 15 | class vp_tree 16 | { 17 | public: 18 | template 19 | explicit vp_tree(DistType &&distance) : 20 | distance_(std::forward(distance)), 21 | root_(nullptr) 22 | {} 23 | 24 | vp_tree() : 25 | root_(nullptr) 26 | {} 27 | 28 | ~vp_tree() { 29 | delete root_; 30 | } 31 | 32 | void create(std::vector const &items) 33 | { 34 | create_impl(items); 35 | } 36 | 37 | void create(std::vector &&items) 38 | { 39 | create_impl(std::move(items)); 40 | } 41 | 42 | std::vector& get_items() 43 | { 44 | return items_; 45 | } 46 | 47 | std::vector const& get_items() const 48 | { 49 | return items_; 50 | } 51 | 52 | void search( const T& target, int sample_size, 53 | std::vector &results, 54 | std::vector &distances) const 55 | { 56 | search(target, sample_size, results, distances, 57 | [](){return true;}); 58 | } 59 | 60 | template 61 | void search( const T& target, int sample_size, 62 | std::vector &results, 63 | std::vector &distances, 64 | ValidDist &&valid_dist) const 65 | { 66 | std::priority_queue heap; 67 | 68 | double tau = std::numeric_limits::max(); 69 | search(root_, target, sample_size, heap, tau); 70 | 71 | results.clear(); distances.clear(); 72 | 73 | while(!heap.empty()) { 74 | if(valid_dist(heap.top().dist)){ 75 | results.push_back( items_[heap.top().index] ); 76 | distances.push_back( heap.top().dist ); 77 | } 78 | heap.pop(); 79 | } 80 | 81 | std::reverse( std::begin(results), std::end(results) ); 82 | std::reverse( std::begin(distances), std::end(distances) ); 83 | } 84 | 85 | private: 86 | struct Node 87 | { 88 | size_t index; 89 | double threshold; 90 | Node* left; 91 | Node* right; 92 | 93 | Node() : 94 | index(0), threshold(0.), left(nullptr), right(nullptr) {} 95 | 96 | ~Node() { 97 | delete left; 98 | delete right; 99 | } 100 | }; 101 | 102 | struct HeapItem { 103 | HeapItem( size_t index, double dist) : 104 | index(index), dist(dist) {} 105 | size_t index; 106 | double dist; 107 | bool operator<( const HeapItem& o ) const { 108 | return dist < o.dist; 109 | } 110 | }; 111 | 112 | struct DistanceComparator 113 | { 114 | Distance &distance_; 115 | const T& item; 116 | DistanceComparator(Distance &distance, const T& item ) : 117 | distance_(distance), 118 | item(item) {} 119 | bool operator()(const T& a, const T& b) { 120 | return distance_( item, a ) < distance_( item, b ); 121 | } 122 | }; 123 | 124 | template 125 | void create_impl(U &&items) 126 | { 127 | delete root_; 128 | items_ = std::forward(items); 129 | root_ = buildFromPoints(0, items_.size()); 130 | } 131 | 132 | Node* buildFromPoints( size_t lower, size_t upper ) 133 | { 134 | if ( upper == lower ) { 135 | return nullptr; 136 | } 137 | 138 | Node* node = new Node; 139 | node->index = lower; 140 | 141 | if ( upper - lower > 1 ) { 142 | 143 | // choose an arbitrary point and move it to the start 144 | size_t const i = (size_t)((double)rand() / RAND_MAX * (upper - lower - 1) ) + lower; 145 | std::swap( items_[lower], items_[i] ); 146 | 147 | size_t const median = ( upper + lower ) / 2; 148 | 149 | // partitian around the median distance 150 | std::nth_element( 151 | items_.begin() + lower + 1, 152 | items_.begin() + median, 153 | items_.begin() + upper, 154 | DistanceComparator(distance_, items_[lower])); 155 | 156 | // what was the median? 157 | node->threshold = distance_( items_[lower], items_[median] ); 158 | 159 | node->index = lower; 160 | node->left = buildFromPoints( lower + 1, median ); 161 | node->right = buildFromPoints( median, upper ); 162 | } 163 | 164 | return node; 165 | } 166 | 167 | void search( Node* node, const T& target, size_t k, 168 | std::priority_queue& heap, double &_tau ) const 169 | { 170 | if ( node == nullptr ) return; 171 | 172 | double dist = distance_( items_[node->index], target ); 173 | //printf("dist=%g tau=%gn", dist, _tau ); 174 | 175 | if ( dist < _tau ) { 176 | if ( heap.size() == k ) heap.pop(); 177 | heap.push( HeapItem(node->index, dist) ); 178 | if ( heap.size() == k ) _tau = heap.top().dist; 179 | } 180 | 181 | if ( node->left == nullptr && node->right == nullptr ) { 182 | return; 183 | } 184 | 185 | if ( dist < node->threshold ) { 186 | if ( dist - _tau <= node->threshold ) { 187 | search( node->left, target, k, heap, _tau ); 188 | } 189 | 190 | if ( dist + _tau >= node->threshold ) { 191 | search( node->right, target, k, heap, _tau ); 192 | } 193 | 194 | } else { 195 | if ( dist + _tau >= node->threshold ) { 196 | search( node->right, target, k, heap, _tau ); 197 | } 198 | 199 | if ( dist - _tau <= node->threshold ) { 200 | search( node->left, target, k, heap, _tau ); 201 | } 202 | } 203 | } 204 | 205 | Distance distance_; 206 | std::vector items_; 207 | Node *root_; 208 | 209 | }; 210 | 211 | #endif // VP_TREE_HPP 212 | -------------------------------------------------------------------------------- /ui/advance_setting_dialog.cpp: -------------------------------------------------------------------------------- 1 | #include "advance_setting_dialog.hpp" 2 | #include "ui_advance_setting_dialog.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | void advance_setting_dialog::plot_hash_chart() 9 | { 10 | ui->hash_accuracy_chart->set_labels_title(tr("Attack")); 11 | ui->hash_accuracy_chart->set_values_title(tr("Percentage(%)")); 12 | plot_accuracy_chart(); 13 | } 14 | 15 | void advance_setting_dialog::create_connection() 16 | { 17 | for(size_t i = 0; i != hash_buttons_.size(); ++i){ 18 | connect(hash_buttons_[i], &QRadioButton::clicked, this, 19 | &advance_setting_dialog::plot_accuracy_chart); 20 | 21 | } 22 | connect(ui->combox_hash_orien, &QComboBox::currentTextChanged, 23 | [this](QString const&) 24 | { 25 | plot_accuracy_chart(); 26 | }); 27 | connect(ui->buttonBox, &QDialogButtonBox::accepted, this, 28 | &advance_setting_dialog::update_hash_origin_state); 29 | connect(ui->buttonBox, &QDialogButtonBox::rejected, this, 30 | &advance_setting_dialog::cancel_clicked); 31 | } 32 | 33 | advance_setting_dialog::advance_setting_dialog(QWidget *parent) : 34 | QDialog(parent), 35 | ui(new Ui::advance_setting_dialog), 36 | default_hash_threshold_{5, 12, 48, 30, 5}, 37 | default_thresh_name_{"avg_default_thresh", "bmh_0_default_thresh", 38 | "bmh_1_default_thresh", "marr_hash_default_thresh", 39 | "phash_default_thresh"}, 40 | hash_name_{"Average hash", "Block mean hash 0", "Block mean hash 1", 41 | "Marr Hildreth hash", "Phash"}, 42 | hash_origin_state_(hash_name_.size()) 43 | { 44 | ui->setupUi(this); 45 | 46 | hash_buttons_.emplace_back(ui->rb_avg_hash); 47 | hash_buttons_.emplace_back(ui->rb_bm_hash_0); 48 | hash_buttons_.emplace_back(ui->rb_bm_hash_1); 49 | hash_buttons_.emplace_back(ui->rb_marr_hash); 50 | hash_buttons_.emplace_back(ui->rb_phash); 51 | 52 | sliders_.emplace_back(ui->hs_avg_hash); 53 | sliders_.emplace_back(ui->hs_bmh_0); 54 | sliders_.emplace_back(ui->hs_bmh_1); 55 | sliders_.emplace_back(ui->hs_marr_hash); 56 | sliders_.emplace_back(ui->hs_phash); 57 | 58 | QSettings settings; 59 | if(settings.contains(hash_name_[0])){ 60 | for(size_t i = 0; i != hash_buttons_.size(); ++i){ 61 | if(settings.value(hash_name_[static_cast(i)]).toBool()){ 62 | hash_buttons_[i]->setChecked(true); 63 | break; 64 | } 65 | } 66 | } 67 | if(settings.contains(default_thresh_name_[0])){ 68 | for(size_t i = 0; i != sliders_.size(); ++i){ 69 | sliders_[i]->setValue(settings.value(default_thresh_name_[i]).toInt()); 70 | } 71 | } 72 | if(settings.contains("adv_settings_geometry")){ 73 | restoreGeometry(settings.value("adv_settings_geometry").toByteArray()); 74 | } 75 | 76 | update_hash_origin_state(); 77 | 78 | ui->combox_hash_orien->addItems({"Vertical", "Horizontal"}); 79 | ui->combox_hash_orien->setCurrentIndex(0); 80 | ui->combox_hash_orien->setCurrentIndex(0); 81 | 82 | plot_hash_chart(); 83 | create_connection(); 84 | } 85 | 86 | advance_setting_dialog::~advance_setting_dialog() 87 | { 88 | QSettings settings; 89 | for(size_t i = 0; i != hash_buttons_.size(); ++i){ 90 | settings.setValue(hash_name_[static_cast(i)], 91 | hash_buttons_[static_cast(i)]->isChecked()); 92 | } 93 | settings.setValue("adv_settings_geometry", saveGeometry()); 94 | 95 | for(size_t i = 0; i != sliders_.size(); ++i){ 96 | settings.setValue(default_thresh_name_[i], sliders_[i]->value()); 97 | } 98 | 99 | delete ui; 100 | } 101 | 102 | cv::Ptr advance_setting_dialog::get_hash_algo() const 103 | { 104 | using namespace cv::img_hash; 105 | using creator_func = std::function()>; 106 | 107 | std::vector creators 108 | { 109 | [](){ return AverageHash::create(); }, 110 | [](){ return BlockMeanHash::create(0); }, 111 | [](){ return BlockMeanHash::create(1); }, 112 | [](){ return MarrHildrethHash::create(); }, 113 | [](){ return PHash::create(); }, 114 | }; 115 | for(size_t i = 0; i != creators.size(); ++i){ 116 | if(hash_buttons_[i]->isChecked()){ 117 | return creators[i](); 118 | } 119 | } 120 | 121 | return AverageHash::create(); 122 | } 123 | 124 | double advance_setting_dialog::get_threshold() const 125 | { 126 | for(size_t i = 0; i != sliders_.size(); ++i){ 127 | if(hash_buttons_[i]->isChecked()){ 128 | return sliders_[i]->value(); 129 | } 130 | } 131 | 132 | return 0; 133 | } 134 | 135 | void advance_setting_dialog::cancel_clicked() 136 | { 137 | for(size_t i = 0; i != hash_buttons_.size(); ++i){ 138 | if(hash_origin_state_[i]){ 139 | hash_buttons_[i]->setChecked(true); 140 | break; 141 | } 142 | } 143 | 144 | for(size_t i = 0; i != sliders_.size(); ++i){ 145 | sliders_[i]->setValue(default_hash_threshold_[i]); 146 | } 147 | } 148 | 149 | void advance_setting_dialog::plot_accuracy_chart(bool) 150 | { 151 | std::vector> const values 152 | { 153 | {95.6522, 100, 97.8261, 99.5652, 84.3478, 1.44928, 6.95652, 100}, 154 | {89.5652, 100, 100, 100, 97.393, 0, 68.6957, 100}, 155 | {92.1739, 100, 100, 100, 97.3913, 0, 72.1739, 99.1304}, 156 | {86.9565, 98.2609, 86.2319, 84.7826, 52.1739, 0, 2.6087, 83.4783}, 157 | {92.1739, 94.7826, 100, 98.6957, 74.7826, 0, 2.6087, 83.4784}, 158 | }; 159 | QStringList const labels{"contrast", "gaussian blur", "gaussian noise", 160 | "jpeg compression", "resize", "rotation", 161 | "salt pepper noise", "watermark"}; 162 | for(size_t i = 0; i != hash_buttons_.size(); ++i){ 163 | if(hash_buttons_[i]->isChecked()){ 164 | ui->hash_accuracy_chart->set_data(values[i], labels); 165 | ui->hash_accuracy_chart->set_main_title(hash_name_[static_cast(i)]); 166 | if(ui->combox_hash_orien->currentIndex() == 0){ 167 | ui->hash_accuracy_chart->set_orientation(Qt::Vertical); 168 | }else{ 169 | ui->hash_accuracy_chart->set_orientation(Qt::Horizontal); 170 | } 171 | } 172 | } 173 | } 174 | 175 | void advance_setting_dialog::update_hash_origin_state() 176 | { 177 | for(size_t i = 0; i != hash_buttons_.size(); ++i){ 178 | hash_origin_state_[i] = hash_buttons_[i]->isChecked(); 179 | } 180 | } 181 | 182 | void advance_setting_dialog::on_pb_avg_hash_default_clicked() 183 | { 184 | ui->hs_avg_hash->setValue(5); 185 | } 186 | 187 | void advance_setting_dialog::on_pb_bmh_0_default_clicked() 188 | { 189 | ui->hs_bmh_0->setValue(12); 190 | } 191 | 192 | void advance_setting_dialog::on_pb_bmh_1_default_clicked() 193 | { 194 | ui->hs_bmh_1->setValue(48); 195 | } 196 | 197 | void advance_setting_dialog::on_pb_marr_hildreth_default_clicked() 198 | { 199 | ui->hs_marr_hash->setValue(30); 200 | } 201 | 202 | void advance_setting_dialog::on_pb_phash_default_clicked() 203 | { 204 | ui->hs_phash->setValue(5); 205 | } 206 | -------------------------------------------------------------------------------- /core/pics_find_img_hash.cpp: -------------------------------------------------------------------------------- 1 | #include "pics_find_img_hash.hpp" 2 | 3 | #include "vp_tree.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | namespace{ 25 | 26 | using graph_type = boost::adjacency_list; 27 | using edges_type = graph_type::edge_descriptor; 28 | 29 | class dfs_visitor : public boost::default_dfs_visitor 30 | { 31 | public: 32 | explicit dfs_visitor(std::vector &edges) : 33 | edges_(edges) {} 34 | 35 | template 36 | void tree_edge(Edge u, const Graph&) 37 | { 38 | edges_.emplace_back(u); 39 | } 40 | 41 | std::vector &edges_; 42 | }; 43 | 44 | struct dist_compare 45 | { 46 | using value_type = std::pair; 47 | using algo_type = cv::Ptr; 48 | 49 | explicit dist_compare(algo_type algo) : 50 | algo_(algo){} 51 | 52 | double operator()(value_type const &lhs, 53 | value_type const &rhs) const 54 | { 55 | return algo_->compare(lhs.second, rhs.second); 56 | } 57 | 58 | private: 59 | algo_type algo_; 60 | }; 61 | 62 | template 63 | graph_type create_graph(Tree const &tree, 64 | int sample_size, int actual_size, 65 | Condition condition) 66 | { 67 | using value_type = dist_compare::value_type; 68 | 69 | std::map name_id; 70 | auto const &items = tree.get_items(); 71 | for(size_t i = 0; i != items.size(); ++i){ 72 | name_id.insert({items[i].first, i}); 73 | } 74 | sample_size = actual_size > sample_size ? sample_size : actual_size; 75 | std::vector result; 76 | std::vector distance; 77 | graph_type graph; 78 | for(size_t i = 0; i != items.size(); ++i){ 79 | tree.search(items[i], sample_size, result, distance, 80 | condition); 81 | for(auto const &pair : result){ 82 | auto it = name_id.find(pair.first); 83 | if(it != std::end(name_id)){ 84 | boost::add_edge(i, it->second, graph); 85 | } 86 | } 87 | } 88 | 89 | return graph; 90 | } 91 | 92 | template 93 | std::vector create_edges(Graph const &graph) 94 | { 95 | std::vector edges; 96 | dfs_visitor vis(edges); 97 | boost::depth_first_search(graph, boost::visitor(vis)); 98 | 99 | return edges; 100 | } 101 | 102 | cv::Mat qimage_to_cvmat(QImage &img) 103 | { 104 | qDebug()<<__func__<<"use plugin to read image"; 105 | img = img.convertToFormat(QImage::Format_RGB888); 106 | auto mat = cv::Mat(img.height(), img.width(), CV_8UC3, 107 | img.bits(), static_cast(img.bytesPerLine())); 108 | cv::cvtColor(mat, mat, cv::COLOR_RGB2BGR); 109 | return mat.clone(); 110 | } 111 | 112 | QString const temp_folder("thumbnails"); 113 | 114 | std::pair exiftool_can_read(QImage &img, QString const &img_path) 115 | { 116 | qDebug()<<__func__<<"exiftool can read thumbnail"; 117 | auto const temp_path = temp_folder + "/" + QUuid::createUuid().toString() + ".jpg"; 118 | if(img.save(temp_path)){ 119 | qDebug()<<__func__<<"can save temp image"; 120 | return {qimage_to_cvmat(img), temp_path}; 121 | }else{ 122 | return {qimage_to_cvmat(img), img_path}; 123 | } 124 | } 125 | 126 | }//nameless namespace 127 | 128 | pics_find_img_hash:: 129 | pics_find_img_hash(cv::Ptr algo, 130 | QStringList const &abs_file_path, 131 | double threshold, 132 | QObject *parent) : 133 | QThread(parent), 134 | abs_file_path_(abs_file_path), 135 | algo_(algo), 136 | finished_(false), 137 | pool_size_(QThread::idealThreadCount() > 2 ? 138 | QThread::idealThreadCount() - 2 : 1), 139 | threshold_(threshold) 140 | { 141 | cv::ocl::setUseOpenCL(false); 142 | } 143 | 144 | QStringList pics_find_img_hash::get_duplicate_img() const 145 | { 146 | return duplicate_img_; 147 | } 148 | 149 | QStringList pics_find_img_hash::get_original_img() const 150 | { 151 | return original_img_; 152 | } 153 | 154 | void pics_find_img_hash::compare_hash() 155 | { 156 | using namespace cv::img_hash; 157 | using value_type = dist_compare::value_type; 158 | 159 | dist_compare dc(algo_); 160 | vp_tree hamming_tree(std::move(dc)); 161 | hamming_tree.create(std::move(hash_arr_)); 162 | 163 | graph_type graph; 164 | graph = create_graph(hamming_tree, 165 | abs_file_path_.size(), 10, 166 | [=](double val){ return val <= threshold_; }); 167 | auto const edges = create_edges(graph); 168 | auto const &items = hamming_tree.get_items(); 169 | original_img_.clear(); 170 | duplicate_img_.clear(); 171 | for(size_t i = 0; i != edges.size(); ++i){ 172 | original_img_.push_back(items[edges[i].m_source].first); 173 | duplicate_img_.push_back(items[edges[i].m_target].first); 174 | } 175 | } 176 | 177 | void pics_find_img_hash::compute_hash() 178 | { 179 | hash_arr_.clear(); 180 | hash_arr_.reserve(static_cast(abs_file_path_.size())); 181 | for(auto &name : abs_file_path_){ 182 | auto const result = read_img(name); 183 | if(!result.first.empty()){ 184 | cv::Mat hash; 185 | name = result.second; 186 | algo_->compute(result.first, hash); 187 | hash_arr_.emplace_back(name, hash); 188 | } 189 | emit progress("Image preprocess st : " + 190 | QString("%1").arg(hash_arr_.size())); 191 | } 192 | } 193 | 194 | void pics_find_img_hash::compute_hash_mt() 195 | { 196 | hash_arr_.clear(); 197 | hash_arr_.reserve(static_cast(abs_file_path_.size())); 198 | std::mutex mutex; 199 | std::atomic size(0); 200 | auto compute_hash = [&](QString &name) 201 | { 202 | auto const result = read_img(name); 203 | if(!result.first.empty()){ 204 | name = result.second; 205 | ++size; 206 | cv::Mat hash; 207 | std::lock_guard guard(mutex); 208 | algo_->compute(result.first, hash); 209 | hash_arr_.emplace_back(name, hash); 210 | } 211 | emit progress("Image preprocess mt : " + 212 | QString("%1").arg(size.load())); 213 | }; 214 | QtConcurrent::blockingMap(abs_file_path_, compute_hash);//*/ 215 | } 216 | 217 | void pics_find_img_hash::compute_hash_mt2() 218 | { 219 | hash_arr_.clear(); 220 | hash_arr_.reserve(static_cast(abs_file_path_.size())); 221 | int process_size = 0; 222 | auto process_func = [&](cv::Mat const &img, int i) 223 | { 224 | cv::Mat hash; 225 | int size = 0; 226 | { 227 | std::lock_guard lk(mutex_); 228 | algo_->compute(img, hash); 229 | hash_arr_.emplace_back(abs_file_path_[i], hash); 230 | size = process_size + 1; 231 | ++process_size; 232 | } 233 | emit progress("Image preprocess : " + 234 | QString("%1/%2").arg(size). 235 | arg(abs_file_path_.size())); 236 | if(size == abs_file_path_.size()){ 237 | finished_ = true; 238 | cv_.notify_one(); 239 | } 240 | }; 241 | 242 | for(int i = 0; i != abs_file_path_.size(); ++i){ 243 | auto const result = read_img(abs_file_path_[i]); 244 | if(!result.first.empty()){ 245 | abs_file_path_[i] = result.second; 246 | QtConcurrent::run(QThreadPool::globalInstance(), process_func, result.first, i); 247 | } 248 | } 249 | 250 | std::unique_lock lk(mutex_); 251 | cv_.wait(lk, [this](){return finished_;}); 252 | } 253 | 254 | std::pair pics_find_img_hash::read_img(const QString &img_path) 255 | { 256 | auto const suffix = QFileInfo(img_path).suffix(); 257 | if(suffix.toLower() == "psb" || suffix.toLower() == "psd"){ 258 | QProcess process; 259 | QString const command = QString("/usr/local/bin/exiftool -Photoshop:PhotoshopThumbnail -b %1"). 260 | arg(img_path); 261 | process.start(command); 262 | qDebug()<<__func__< 1){ 300 | //compute_hash_mt2(); 301 | compute_hash(); 302 | qDebug()<<"compute hash mt2(ms) : "< 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1199 10 | 693 11 | 12 | 13 | 14 | Similar Vision 15 | 16 | 17 | 18 | :/pics/pics/find.ico:/pics/pics/find.ico 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 0 27 | 0 28 | 29 | 30 | 31 | Qt::Vertical 32 | 33 | 34 | 35 | Select folder 36 | 37 | 38 | false 39 | 40 | 41 | false 42 | 43 | 44 | 45 | 46 | 47 | 48 | 0 49 | 0 50 | 51 | 52 | 53 | 54 | 0 55 | 100 56 | 57 | 58 | 59 | 60 | 16777215 61 | 100 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 0 71 | 0 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 0 83 | 0 84 | 85 | 86 | 87 | Folders : 88 | 89 | 90 | 91 | 92 | 93 | 94 | Add folder 95 | 96 | 97 | 98 | 99 | 100 | 101 | :/pics/pics/folder_add.png:/pics/pics/folder_add.png 102 | 103 | 104 | 105 | 106 | 107 | 108 | Remove folder 109 | 110 | 111 | 112 | 113 | 114 | 115 | :/pics/pics/folder_delete.png:/pics/pics/folder_delete.png 116 | 117 | 118 | 119 | 120 | 121 | 122 | Up 123 | 124 | 125 | 126 | 127 | 128 | 129 | :/pics/pics/up.png:/pics/pics/up.png 130 | 131 | 132 | 133 | 134 | 135 | 136 | Down 137 | 138 | 139 | 140 | 141 | 142 | 143 | :/pics/pics/down.png:/pics/pics/down.png 144 | 145 | 146 | 147 | 148 | 149 | 150 | Refresh 151 | 152 | 153 | 154 | 155 | 156 | 157 | :/pics/pics/refresh.png:/pics/pics/refresh.png 158 | 159 | 160 | 161 | 162 | 163 | 164 | Start search 165 | 166 | 167 | 168 | 169 | 170 | 171 | :/pics/pics/search.png:/pics/pics/search.png 172 | 173 | 174 | 175 | 176 | 177 | 178 | Scan Subdirectories 179 | 180 | 181 | true 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | View image 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 0 208 | 0 209 | 210 | 211 | 212 | 213 | 0 214 | 120 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 0 224 | 0 225 | 226 | 227 | 228 | 229 | 230 | 231 | Qt::AlignCenter 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | Move 241 | 242 | 243 | 244 | 245 | 246 | 247 | :/pics/pics/right.png:/pics/pics/right.png 248 | 249 | 250 | 251 | 252 | 253 | 254 | Browse 255 | 256 | 257 | 258 | 259 | 260 | 261 | :/pics/pics/folder_go.png:/pics/pics/folder_go.png 262 | 263 | 264 | 265 | 266 | 267 | 268 | Delete 269 | 270 | 271 | 272 | 273 | 274 | 275 | :/pics/pics/bin_recycle.png:/pics/pics/bin_recycle.png 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 0 295 | 0 296 | 297 | 298 | 299 | 300 | 0 301 | 120 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 0 311 | 0 312 | 313 | 314 | 315 | 316 | 317 | 318 | Qt::AlignCenter 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | Move 328 | 329 | 330 | 331 | 332 | 333 | 334 | :/pics/pics/right.png:/pics/pics/right.png 335 | 336 | 337 | 338 | 339 | 340 | 341 | Browse 342 | 343 | 344 | 345 | 346 | 347 | 348 | :/pics/pics/folder_go.png:/pics/pics/folder_go.png 349 | 350 | 351 | 352 | 353 | 354 | 355 | Delete 356 | 357 | 358 | 359 | 360 | 361 | 362 | :/pics/pics/bin_recycle.png:/pics/pics/bin_recycle.png 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 0 378 | 0 379 | 380 | 381 | 382 | 383 | 384 | 385 | Qt::AlignCenter 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 0 395 | 0 396 | 397 | 398 | 399 | 400 | 0 401 | 120 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 0 413 | 0 414 | 1199 415 | 21 416 | 417 | 418 | 419 | 420 | File 421 | 422 | 423 | 424 | 425 | 426 | 427 | Settings 428 | 429 | 430 | 431 | 432 | 433 | 434 | Help 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | Qt 450 | 451 | 452 | 453 | 454 | Fatcow 455 | 456 | 457 | 458 | 459 | 460 | :/pics/pics/search.png:/pics/pics/search.png 461 | 462 | 463 | Start search 464 | 465 | 466 | 467 | 468 | Exit 469 | 470 | 471 | 472 | 473 | Check for update 474 | 475 | 476 | 477 | 478 | Visit the program website 479 | 480 | 481 | 482 | 483 | Basic 484 | 485 | 486 | Basic settings 487 | 488 | 489 | 490 | 491 | Advance 492 | 493 | 494 | Advance settings 495 | 496 | 497 | 498 | 499 | 500 | 501 | paint_custom_words 502 | QListView 503 |
paint_custom_words.hpp
504 |
505 | 506 | duplicate_img_table_view 507 | QTableView 508 |
duplicate_img_table_view.hpp
509 |
510 |
511 | 512 | 513 | 514 | 515 |
516 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.hpp" 2 | #include "ui_mainwindow.h" 3 | 4 | #include "core/pics_find_img_hash.hpp" 5 | #include "core/scan_folder.hpp" 6 | #include "model/duplicate_img_model.hpp" 7 | #include "model/folder_model.hpp" 8 | #include "ui/advance_setting_dialog.hpp" 9 | #include "ui/basic_setting_dialog.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | MainWindow::MainWindow(QWidget *parent) : 25 | QMainWindow(parent), 26 | ui(new Ui::MainWindow), 27 | duplicate_img_model_(new duplicate_img_model(this)), 28 | folder_model_(new folder_model(this)), 29 | img_lf_changed_(false), 30 | img_rt_changed_(false), 31 | pf_img_hash_(nullptr), 32 | scf_thread_(nullptr), 33 | timer_(new QElapsedTimer) 34 | { 35 | ui->setupUi(this); 36 | ui->list_view_folder->setModel(folder_model_); 37 | ui->list_view_folder->setAcceptDrops(true); 38 | ui->list_view_folder->setSelectionMode(QAbstractItemView::ExtendedSelection); 39 | ui->list_view_folder->setEditTriggers(QAbstractItemView::NoEditTriggers); 40 | 41 | enable_folder_edit_ui(); 42 | enable_image_edit_ui(); 43 | ui->table_view_similar_pics->setModel(duplicate_img_model_); 44 | ui->table_view_similar_pics->setSelectionMode(QAbstractItemView::SingleSelection); 45 | ui->table_view_similar_pics->setSelectionBehavior(QAbstractItemView::SelectRows); 46 | 47 | ui->gp_view_lf->setScene(new QGraphicsScene(this)); 48 | ui->gp_view_rt->setScene(new QGraphicsScene(this)); 49 | 50 | QSettings settings; 51 | if(settings.contains("main_geometry")){ 52 | restoreGeometry(settings.value("main_geometry").toByteArray()); 53 | } 54 | 55 | connect(folder_model_, &folder_model::drop_data, 56 | this, &MainWindow::enable_folder_edit_ui); 57 | connect(ui->list_view_folder, &paint_custom_words::view_selected, 58 | this, &MainWindow::enable_folder_edit_ui); 59 | connect(ui->table_view_similar_pics, &QTableView::clicked, 60 | this, &MainWindow::duplicate_img_select); 61 | connect(ui->table_view_similar_pics, &duplicate_img_table_view::key_press, 62 | [this](int) 63 | { 64 | auto const index = ui->table_view_similar_pics->currentIndex(); 65 | if(index.isValid()){ 66 | duplicate_img_select(index); 67 | } 68 | }); 69 | 70 | if(basic_setting_dialog().auto_update()){ 71 | if(can_update()){ 72 | QMessageBox::information(this, tr("Update info"), 73 | tr("similar_vision got a new version, please upate it " 74 | "from Help->Check for update")); 75 | } 76 | } 77 | } 78 | 79 | MainWindow::~MainWindow() 80 | { 81 | QSettings settings; 82 | settings.setValue("main_geometry", saveGeometry()); 83 | 84 | delete ui; 85 | } 86 | 87 | void MainWindow::on_pb_add_folder_clicked() 88 | { 89 | QFileDialog dialog(nullptr, tr("Open Directory")); 90 | dialog.setAcceptMode(QFileDialog::AcceptOpen); 91 | dialog.setFileMode(QFileDialog::DirectoryOnly); 92 | dialog.setOption(QFileDialog::ShowDirsOnly); 93 | dialog.setOption(QFileDialog::DontResolveSymlinks); 94 | if(dialog.exec()){ 95 | auto const dir = dialog.selectedFiles()[0]; 96 | auto str_list = folder_model_->stringList(); 97 | str_list.push_back(dir); 98 | str_list.removeDuplicates(); 99 | folder_model_->setStringList(str_list); 100 | enable_folder_edit_ui(); 101 | } 102 | 103 | raise(); 104 | } 105 | 106 | void MainWindow::scan_folders() 107 | { 108 | duplicate_img_model_->clear(); 109 | ui->gp_view_lf->scene()->clear(); 110 | ui->gp_view_rt->scene()->clear(); 111 | timer_->restart(); 112 | scf_thread_ = new scan_folder(folder_model_->stringList(), 113 | basic_setting_dialog().scan_img_type(), 114 | ui->cb_scan_subdir->isChecked(), this); 115 | connect(scf_thread_, &scan_folder::progress, this, &MainWindow::set_label_info); 116 | connect(scf_thread_, &scan_folder::finished, this, &MainWindow::find_similar_pics); 117 | setEnabled(false); 118 | scf_thread_->start(); 119 | } 120 | 121 | void MainWindow::set_label_info(QString info) 122 | { 123 | int const total_sec = timer_->elapsed() / 1000; 124 | int const min = (total_sec / 60)%60; 125 | int const hour = total_sec / 3600; 126 | int const sec = total_sec % 60; 127 | auto const elapsed = QString("%1:%2:%3").arg(hour, 2, 10, QChar('0')). 128 | arg(min, 2, 10, QChar('0')). 129 | arg(sec, 2, 10, QChar('0')); 130 | ui->label_info->setText(QString("%1, Elapsed time : %2").arg(info). 131 | arg(elapsed)); 132 | } 133 | 134 | void MainWindow::on_pb_find_folder_clicked() 135 | { 136 | scan_folders(); 137 | } 138 | 139 | void MainWindow::find_similar_pics() 140 | { 141 | using namespace cv::img_hash; 142 | 143 | pf_img_hash_ = new pics_find_img_hash(advance_setting_dialog().get_hash_algo(), 144 | scf_thread_->get_abs_file_path(), 145 | advance_setting_dialog().get_threshold(), 146 | this); 147 | scf_thread_->deleteLater(); 148 | 149 | connect(pf_img_hash_, &pics_find_img_hash::progress, this, &MainWindow::set_label_info); 150 | connect(pf_img_hash_, &pics_find_img_hash::finished, this, &MainWindow::find_similar_pics_end); 151 | 152 | pf_img_hash_->start(); 153 | } 154 | 155 | void MainWindow::find_similar_pics_end() 156 | { 157 | pf_img_hash_->deleteLater(); 158 | enable_main_ui(); 159 | QMessageBox::information(this, tr("Information"), 160 | tr("Search finished.\n\n" 161 | "Similar images pair found : %1\n"). 162 | arg(duplicate_img_model_->rowCount())); 163 | } 164 | 165 | void MainWindow::resizeEvent(QResizeEvent *event) 166 | { 167 | QMainWindow::resizeEvent(event); 168 | duplicate_img_select(ui->table_view_similar_pics->currentIndex()); 169 | } 170 | 171 | void MainWindow::on_pb_refresh_clicked() 172 | { 173 | folder_model_->setStringList({}); 174 | enable_folder_edit_ui(); 175 | } 176 | 177 | void MainWindow::enable_up_down_arrow(int item_size, int select_size) 178 | { 179 | if(item_size > 1){ 180 | if(select_size == 1){ 181 | auto const index = ui->list_view_folder->currentIndex(); 182 | if(index.isValid()){ 183 | bool const not_at_bottom = 184 | index.row() != folder_model_->rowCount() - 1; 185 | bool const not_at_top = index.row() != 0; 186 | ui->pb_up->setEnabled(not_at_top); 187 | ui->pb_down->setEnabled(not_at_bottom); 188 | }else{ 189 | ui->pb_up->setEnabled(false); 190 | ui->pb_down->setEnabled(false); 191 | } 192 | }else{ 193 | ui->pb_up->setEnabled(false); 194 | ui->pb_down->setEnabled(false); 195 | } 196 | }else{ 197 | ui->pb_up->setEnabled(false); 198 | ui->pb_down->setEnabled(false); 199 | } 200 | } 201 | 202 | void MainWindow::enable_folder_edit_ui() 203 | { 204 | auto const &str_list = folder_model_->stringList(); 205 | bool const has_item = !str_list.isEmpty(); 206 | auto const &selected_rows = ui->list_view_folder->selectionModel()->selectedRows(); 207 | bool const item_selected = !selected_rows.isEmpty(); 208 | 209 | ui->pb_delete_folder->setEnabled(has_item && item_selected); 210 | ui->pb_find_folder->setEnabled(has_item); 211 | ui->pb_refresh->setEnabled(has_item); 212 | ui->action_start_search->setEnabled(has_item); 213 | ui->cb_scan_subdir->setEnabled(has_item); 214 | 215 | enable_up_down_arrow(str_list.size(), selected_rows.size()); 216 | } 217 | 218 | void MainWindow::enable_image_edit_ui() 219 | { 220 | bool const enable = duplicate_img_model_->rowCount() != 0; 221 | ui->table_view_similar_pics->setEnabled(enable); 222 | ui->gb_left_pic->setEnabled(enable); 223 | ui->gb_right_pic->setEnabled(enable); 224 | } 225 | 226 | void MainWindow::enable_main_ui() 227 | { 228 | duplicate_img_model_->set_img_set(pf_img_hash_->get_original_img(), 229 | pf_img_hash_->get_duplicate_img()); 230 | ui->table_view_similar_pics->reset(); 231 | ui->table_view_similar_pics->resizeColumnsToContents(); 232 | setEnabled(true); 233 | enable_image_edit_ui(); 234 | } 235 | 236 | void MainWindow::on_pb_delete_folder_clicked() 237 | { 238 | struct guard_update 239 | { 240 | explicit guard_update(paint_custom_words *view, MainWindow *win) : 241 | view_(view), 242 | win_(win) 243 | { 244 | view_->setUpdatesEnabled(false); 245 | win_->disconnect(view_, &paint_custom_words::view_selected, 246 | win_, &MainWindow::enable_folder_edit_ui); 247 | } 248 | 249 | ~guard_update() 250 | { 251 | view_->setUpdatesEnabled(true); 252 | win_->connect(view_, &paint_custom_words::view_selected, 253 | win_, &MainWindow::enable_folder_edit_ui); 254 | } 255 | 256 | paint_custom_words *view_; 257 | MainWindow *win_; 258 | }; 259 | 260 | guard_update guard(ui->list_view_folder, this); 261 | auto indexes = ui->list_view_folder->selectionModel()->selectedRows(); 262 | int offset = 0; 263 | for(auto const &var : indexes){ 264 | int const Index = var.row() - offset; 265 | folder_model_->removeRow(Index, {}); 266 | ++offset; 267 | } 268 | enable_folder_edit_ui(); 269 | } 270 | 271 | void MainWindow::duplicate_img_select(QModelIndex const &index) 272 | { 273 | if(index.isValid()){ 274 | auto const img_name_lf = 275 | duplicate_img_model_->data(duplicate_img_model_->index(index.row(), 0), 276 | Qt::DisplayRole).toString(); 277 | auto const img_name_rt = 278 | duplicate_img_model_->data(duplicate_img_model_->index(index.row(), 1), 279 | Qt::DisplayRole).toString(); 280 | img_lf_changed_ = pre_img_name_lf_ != img_name_lf ? true : false; 281 | img_rt_changed_ = pre_img_name_rt_ != img_name_rt ? true : false; 282 | pre_img_name_lf_ = img_name_lf; 283 | pre_img_name_rt_ = img_name_rt; 284 | bool can_view = view_duplicate_img(pre_img_name_lf_, 285 | img_lf_changed_, 286 | ui->gp_view_lf); 287 | if(!can_view){ 288 | QMessageBox::warning(this, tr("Error"), tr("Cannot open image %1").arg(img_name_lf)); 289 | return; 290 | } 291 | can_view = view_duplicate_img(pre_img_name_rt_, 292 | img_rt_changed_, 293 | ui->gp_view_rt); 294 | if(!can_view){ 295 | QMessageBox::warning(this, tr("Error"), tr("Cannot open image %1").arg(img_name_rt)); 296 | } 297 | } 298 | } 299 | 300 | bool MainWindow::view_duplicate_img(const QString &name, 301 | bool img_changed, 302 | QGraphicsView *view) 303 | { 304 | if(img_changed){ 305 | QImage img(name); 306 | if(!img.isNull()){ 307 | auto const suffix = QFileInfo(name).suffix(); 308 | auto const img_info = 309 | QString("%1 %2x%3 %4KB").arg(suffix). 310 | arg(img.width()).arg(img.height()). 311 | arg(qRound(QFile(name).size()/1024.0)); 312 | if(view == ui->gp_view_lf){ 313 | ui->lb_left_pic->setText(img_info); 314 | }else{ 315 | ui->lb_right_pic->setText(img_info); 316 | } 317 | view->scene()->clear(); 318 | view->scene()->addPixmap(QPixmap::fromImage(img)); 319 | view->fitInView(view->scene()->itemsBoundingRect(), 320 | Qt::KeepAspectRatio); 321 | }else{ 322 | return false; 323 | } 324 | }else{ 325 | view->fitInView(view->scene()->itemsBoundingRect(), 326 | Qt::KeepAspectRatio); 327 | } 328 | 329 | return true; 330 | } 331 | 332 | void MainWindow::on_pb_up_clicked() 333 | { 334 | auto const index = ui->list_view_folder->currentIndex(); 335 | auto const new_index = folder_model_->index(index.row()-1); 336 | ui->list_view_folder->setCurrentIndex(new_index); 337 | enable_folder_edit_ui(); 338 | } 339 | 340 | void MainWindow::on_pb_down_clicked() 341 | { 342 | auto const index = ui->list_view_folder->currentIndex(); 343 | auto const new_index = folder_model_->index(index.row()+1); 344 | ui->list_view_folder->setCurrentIndex(new_index); 345 | enable_folder_edit_ui(); 346 | } 347 | 348 | QString MainWindow::get_select_name(int col) 349 | { 350 | auto const index = ui->table_view_similar_pics->currentIndex(); 351 | if(index.isValid()){ 352 | auto const new_index = duplicate_img_model_->index(index.row(),col); 353 | return duplicate_img_model_->data(new_index, Qt::DisplayRole).toString(); 354 | }else{ 355 | return ""; 356 | } 357 | } 358 | 359 | void MainWindow::move_file(QString const &name) 360 | { 361 | QString const dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), 362 | "", 363 | QFileDialog::ShowDirsOnly 364 | | QFileDialog::DontResolveSymlinks); 365 | if(QFile::rename(name, dir + "/" + 366 | QFileInfo(name).fileName())){ 367 | remove_img_from_table(name); 368 | }else{ 369 | QMessageBox::warning(this, tr("Error"), 370 | tr("Cannot move the file, it may not exist anymore " 371 | "or accupied by other program")); 372 | } 373 | } 374 | 375 | void MainWindow::on_pb_lf_move_clicked() 376 | { 377 | move_file(get_select_name(0)); 378 | } 379 | 380 | void MainWindow::on_pb_rt_move_clicked() 381 | { 382 | move_file(get_select_name(1)); 383 | } 384 | 385 | void MainWindow::open_folder(QString const &name) 386 | { 387 | QFileDialog dialog; 388 | dialog.setFileMode(QFileDialog::ExistingFiles); 389 | auto const dir_path = QFileInfo(name).absoluteDir(); 390 | if(QFile::exists(dir_path.currentPath())){ 391 | dialog.setDirectory(dir_path); 392 | dialog.exec(); 393 | }else{ 394 | QMessageBox::warning(this, tr("Error"), 395 | tr("Cannot open dialog do not exist")); 396 | } 397 | } 398 | 399 | void MainWindow::on_pb_lf_browse_clicked() 400 | { 401 | open_folder(get_select_name(0)); 402 | } 403 | 404 | void MainWindow::on_pb_rt_browse_clicked() 405 | { 406 | open_folder(get_select_name(1)); 407 | } 408 | 409 | void MainWindow::remove_img_from_table(QString const &name) 410 | { 411 | int const cur_index = ui->table_view_similar_pics->currentIndex().row(); 412 | duplicate_img_model_->remove_img(name); 413 | int const row_count = duplicate_img_model_->rowCount(); 414 | if(row_count > 1){ 415 | auto index = duplicate_img_model_->index(cur_index, 0); 416 | if(index.isValid()){ 417 | ui->table_view_similar_pics->setCurrentIndex(index); 418 | duplicate_img_select(index); 419 | }else{ 420 | index = duplicate_img_model_->index(row_count-1, 0); 421 | ui->table_view_similar_pics->setCurrentIndex(index); 422 | duplicate_img_select(index); 423 | } 424 | }else if(row_count == 1){ 425 | auto const index = duplicate_img_model_->index(0, 0); 426 | ui->table_view_similar_pics->setCurrentIndex(index); 427 | duplicate_img_select(index); 428 | }else{ 429 | ui->gp_view_lf->scene()->clear(); 430 | ui->gp_view_rt->scene()->clear(); 431 | ui->lb_left_pic->clear(); 432 | ui->lb_right_pic->clear(); 433 | } 434 | } 435 | 436 | void MainWindow::delete_img(QString const &name) 437 | { 438 | if(QFile::remove(name)){ 439 | remove_img_from_table(name); 440 | }else{ 441 | QMessageBox::warning(this, tr("Error"), 442 | tr("Cannot remove img, it may " 443 | "not exist or occupied by other " 444 | "program")); 445 | } 446 | } 447 | 448 | void MainWindow::on_pb_lf_recycle_clicked() 449 | { 450 | delete_img(get_select_name(0)); 451 | } 452 | 453 | void MainWindow::on_pb_rt_recycle_clicked() 454 | { 455 | delete_img(get_select_name(1)); 456 | } 457 | 458 | void MainWindow::on_action_basic_setting_triggered() 459 | { 460 | basic_setting_dialog().exec(); 461 | } 462 | 463 | void MainWindow::on_action_exit_triggered() 464 | { 465 | close(); 466 | } 467 | 468 | void MainWindow::on_action_qt_triggered() 469 | { 470 | QMessageBox::aboutQt(this); 471 | } 472 | 473 | void MainWindow::on_action_fatcow_triggered() 474 | { 475 | QMessageBox::information(this, tr("Source"), 476 | tr("The icons are come from http://www.fatcow." 477 | "com/free-icons")); 478 | } 479 | 480 | void MainWindow::on_action_advance_setting_triggered() 481 | { 482 | advance_setting_dialog().exec(); 483 | } 484 | 485 | void MainWindow::on_action_visit_program_website_triggered() 486 | { 487 | QDesktopServices::openUrl(QUrl("https://github.com/" 488 | "stereomatchingkiss/similar_vision")); 489 | } 490 | 491 | void MainWindow::on_action_check_for_update_triggered() 492 | { 493 | qDebug()<<"start update"; 494 | 495 | setEnabled(false); 496 | if(can_update()){ 497 | QString const app_dir_path = QCoreApplication::applicationDirPath(); 498 | qDebug()<kill(); } 526 | 527 | private: 528 | QProcess *process_; 529 | }; 530 | kill_process kp(&process); 531 | 532 | process.start(app_dir_path + "/auto_updater/auto_updater", 533 | QStringList()<<"-n", 534 | QIODevice::ReadOnly); 535 | if(process.waitForFinished(-1)){ 536 | QString const process_output(process.readAll()); 537 | 538 | qDebug()<<"process output : "< 2 | 3 | advance_setting_dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1234 10 | 590 11 | 12 | 13 | 14 | Advance setting 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Hash algorithms 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Average hash 37 | 38 | 39 | true 40 | 41 | 42 | buttonGroup 43 | 44 | 45 | 46 | 47 | 48 | 49 | The lower the threshold,the more similar the images pair 50 | 51 | 52 | The lower the threshold,the more similar the images pair 53 | 54 | 55 | 100 56 | 57 | 58 | 5 59 | 60 | 61 | Qt::Horizontal 62 | 63 | 64 | 65 | 66 | 67 | 68 | The lower the threshold,the more similar the images pair 69 | 70 | 71 | The lower the threshold,the more similar the images pair 72 | 73 | 74 | 100 75 | 76 | 77 | 5 78 | 79 | 80 | 81 | 82 | 83 | 84 | Default value 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Block mean hash 0 101 | 102 | 103 | buttonGroup 104 | 105 | 106 | 107 | 108 | 109 | 110 | The lower the threshold,the more similar the images pair 111 | 112 | 113 | The lower the threshold,the more similar the images pair 114 | 115 | 116 | 100 117 | 118 | 119 | 12 120 | 121 | 122 | Qt::Horizontal 123 | 124 | 125 | 126 | 127 | 128 | 129 | The lower the threshold,the more similar the images pair 130 | 131 | 132 | The lower the threshold,the more similar the images pair 133 | 134 | 135 | 0 136 | 137 | 138 | 100 139 | 140 | 141 | 12 142 | 143 | 144 | 145 | 146 | 147 | 148 | Default value 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | Block mean hash 1 165 | 166 | 167 | buttonGroup 168 | 169 | 170 | 171 | 172 | 173 | 174 | The lower the threshold,the more similar the images pair 175 | 176 | 177 | The lower the threshold,the more similar the images pair 178 | 179 | 180 | 100 181 | 182 | 183 | 48 184 | 185 | 186 | Qt::Horizontal 187 | 188 | 189 | 190 | 191 | 192 | 193 | The lower the threshold,the more similar the images pair 194 | 195 | 196 | The lower the threshold,the more similar the images pair 197 | 198 | 199 | 100 200 | 201 | 202 | 48 203 | 204 | 205 | 206 | 207 | 208 | 209 | Default value 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | Marr-Hildreth hash 226 | 227 | 228 | buttonGroup 229 | 230 | 231 | 232 | 233 | 234 | 235 | The lower the threshold,the more similar the images pair 236 | 237 | 238 | The lower the threshold,the more similar the images pair 239 | 240 | 241 | 100 242 | 243 | 244 | 30 245 | 246 | 247 | Qt::Horizontal 248 | 249 | 250 | 251 | 252 | 253 | 254 | The lower the threshold,the more similar the images pair 255 | 256 | 257 | The lower the threshold,the more similar the images pair 258 | 259 | 260 | 100 261 | 262 | 263 | 30 264 | 265 | 266 | 267 | 268 | 269 | 270 | Default value 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | PHash 287 | 288 | 289 | buttonGroup 290 | 291 | 292 | 293 | 294 | 295 | 296 | The lower the threshold,the more similar the images pair 297 | 298 | 299 | The lower the threshold,the more similar the images pair 300 | 301 | 302 | 100 303 | 304 | 305 | 5 306 | 307 | 308 | Qt::Horizontal 309 | 310 | 311 | 312 | 313 | 314 | 315 | The lower the threshold,the more similar the images pair 316 | 317 | 318 | The lower the threshold,the more similar the images pair 319 | 320 | 321 | 100 322 | 323 | 324 | 5 325 | 326 | 327 | 328 | 329 | 330 | 331 | Default value 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | Hash accuracy 345 | 346 | 347 | 348 | 349 | 350 | 351 | 0 352 | 0 353 | 354 | 355 | 356 | Orientation 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | Qt::Horizontal 377 | 378 | 379 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | single_bar_plot 388 | QWidget 389 |
single_bar_plot.hpp
390 | 1 391 |
392 |
393 | 394 | 395 | 396 | buttonBox 397 | accepted() 398 | advance_setting_dialog 399 | accept() 400 | 401 | 402 | 257 403 | 580 404 | 405 | 406 | 157 407 | 274 408 | 409 | 410 | 411 | 412 | buttonBox 413 | rejected() 414 | advance_setting_dialog 415 | reject() 416 | 417 | 418 | 325 419 | 580 420 | 421 | 422 | 286 423 | 274 424 | 425 | 426 | 427 | 428 | hs_avg_hash 429 | valueChanged(int) 430 | spinBox 431 | setValue(int) 432 | 433 | 434 | 120 435 | 154 436 | 437 | 438 | 222 439 | 154 440 | 441 | 442 | 443 | 444 | spinBox 445 | valueChanged(int) 446 | hs_avg_hash 447 | setValue(int) 448 | 449 | 450 | 217 451 | 143 452 | 453 | 454 | 120 455 | 145 456 | 457 | 458 | 459 | 460 | hs_bmh_0 461 | valueChanged(int) 462 | spinBox_2 463 | setValue(int) 464 | 465 | 466 | 412 467 | 145 468 | 469 | 470 | 458 471 | 147 472 | 473 | 474 | 475 | 476 | spinBox_2 477 | valueChanged(int) 478 | hs_bmh_0 479 | setValue(int) 480 | 481 | 482 | 455 483 | 155 484 | 485 | 486 | 433 487 | 156 488 | 489 | 490 | 491 | 492 | hs_bmh_1 493 | valueChanged(int) 494 | spinBox_3 495 | setValue(int) 496 | 497 | 498 | 667 499 | 142 500 | 501 | 502 | 692 503 | 144 504 | 505 | 506 | 507 | 508 | spinBox_3 509 | valueChanged(int) 510 | hs_bmh_1 511 | setValue(int) 512 | 513 | 514 | 692 515 | 151 516 | 517 | 518 | 671 519 | 152 520 | 521 | 522 | 523 | 524 | hs_marr_hash 525 | valueChanged(int) 526 | spinBox_4 527 | setValue(int) 528 | 529 | 530 | 915 531 | 144 532 | 533 | 534 | 931 535 | 145 536 | 537 | 538 | 539 | 540 | spinBox_4 541 | valueChanged(int) 542 | hs_marr_hash 543 | setValue(int) 544 | 545 | 546 | 931 547 | 153 548 | 549 | 550 | 912 551 | 153 552 | 553 | 554 | 555 | 556 | hs_phash 557 | valueChanged(int) 558 | spinBox_5 559 | setValue(int) 560 | 561 | 562 | 1146 563 | 140 564 | 565 | 566 | 1170 567 | 143 568 | 569 | 570 | 571 | 572 | spinBox_5 573 | valueChanged(int) 574 | hs_phash 575 | setValue(int) 576 | 577 | 578 | 1179 579 | 153 580 | 581 | 582 | 1130 583 | 156 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 |
592 | --------------------------------------------------------------------------------