Selaa lähdekoodia

Update:First release.

Satoshi Yoneda 1 kuukausi sitten
vanhempi
commit
cfc5a5ab4e
7 muutettua tiedostoa jossa 29 lisäystä ja 100 poistoa
  1. 2 1
      CMakeLists.txt
  2. 4 4
      README.md
  3. 0 63
      implementation_plan.md
  4. 1 1
      include/MainWindow.hpp
  5. 3 2
      src/CmdMain.cpp
  6. 19 10
      src/MainWindow.cpp
  7. 0 19
      task.md

+ 2 - 1
CMakeLists.txt

@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.21)
 project(DupFind VERSION 0.1.0 LANGUAGES CXX)
 
 set(CMAKE_CXX_STANDARD 20)
@@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
 # 最適化と popcnt 命令出力のためのフラグ設定
 if(MSVC)
     add_compile_options(/arch:AVX2)
+    set(X_VCPKG_APPLOCAL_DEPS_INSTALL ON)
 elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
     add_compile_options(-mpopcnt -msse4.2)
 endif()

+ 4 - 4
README.md

@@ -34,7 +34,7 @@ GUIのDupFindを起動すると次のようなウィンドウが表示されま
 
 #### Similarity Threshold
 
-検索する際の類似度の閾値を設定します。値が小さいほど類似度が高くなります。デフォルトは10です。
+検索する際の類似度の閾値を設定します。値が小さいほど類似度が高くなります。デフォルトは5です。
 
 #### Strict Mode
 
@@ -79,7 +79,7 @@ DupFindCmd search image_file
 
 #### th N
 
-類似度の閾値をNに設定します。Nには0から32の間の値を指定してください。デフォルトは10です。
+類似度の閾値をNに設定します。Nには0から32の間の値を指定してください。デフォルトは5です。
 
 #### strict on|off
 
@@ -169,7 +169,7 @@ Remove the selected directory from the search targets. Even if removed from the
 
 #### Similarity Threshold
 
-Set the threshold for similarity when searching. A smaller value indicates higher similarity. The default is 10.
+Set the threshold for similarity when searching. A smaller value indicates higher similarity. The default is 5.
 
 #### Strict Mode
 
@@ -214,7 +214,7 @@ Adds the specified `directory_path` to the search targets and stores its informa
 
 #### th N
 
-Sets the similarity threshold to `N`. Please specify a value between 0 and 32 for `N`. The default is 10.
+Sets the similarity threshold to `N`. Please specify a value between 0 and 32 for `N`. The default is 5.
 
 #### strict on|off
 

+ 0 - 63
implementation_plan.md

@@ -1,63 +0,0 @@
-# 実施計画 - 画像類似度検索ツール (C++)
-
-設定されたディレクトリ内の画像から、pHash および dHash アルゴリズムを用いて類似した画像を検索・提示する高性能な Windows アプリケーション。
-
-## ユーザー確認事項
-
-> [!IMPORTANT]
-> **GUI フレームワークの選択**: ユーザーインターフェースには **Qt 6** を採用します。これにより、Windows だけでなく **Linux でも動作するマルチプラットフォーム対応** が可能になります。
-
-> [!TIP]
-> **複数ディレクトリ対応**: 単一のディレクトリだけでなく、複数のルートディレクトリを登録し、それらを横断して類似画像を検索できるように設計します。
-
-> [!WARNING]
-> **依存関係**: このプロジェクトには **OpenCV** (画像処理用) と **SQLite** (ハッシュ保存用) が必要です。プロジェクト管理には **CMake** を使用します。
-
-## 提案される変更点
-
-### コアロジック (画像ハッシュ化と処理)
-高速なハッシュ生成エンジンを実装します。
-
-#### [NEW] `ImageHasher.hpp / .cpp`
-- `calculateDHash(cv::Mat image)` の実装: 9x8にリサイズ、グレースケール化、隣接画素の差分を計算。
-- `calculatePHash(cv::Mat image)` の実装: 32x32にリサイズ、グレースケール化、離散コサイン変換(DCT)、低周波成分(8x8)の抽出。
-- 最適化: `cv::parallel_for_` または C++ スレッドを使用して、複数の画像を同時に処理します。
-
-#### [NEW] `SimilaritySearch.hpp / .cpp`
-- ハミング距離計算の実装: 
-    - Windows (MSVC): `__popcnt64` を使用。
-    - Linux (GCC/Clang): `__builtin_popcountll` を使用。
-    - または C++20 の `std::popcount` を使用してクロスプラットフォーム化。
-- 検索アルゴリズム: データベース内の全ハッシュと比較し、しきい値以下のものを抽出。
-
-### データ管理
-数十万件のハッシュを効率的に保存・取得します。
-
-#### [NEW] `DatabaseManager.hpp / .cpp`
-- SQLite スキーマ: `images (id INTEGER PRIMARY KEY, path TEXT, dhash BLOB, phash BLOB, timestamp INTEGER)`。
-- パスにインデックスを貼り、差分スキャンを高速化。
-
-### ユーザーインターフェース (Qt 6)
-モダンでレスポシブな UI を構築します。
-
-#### [NEW] `MainWindow.ui / .cpp`
-- **ディレクトリ管理リスト**: 検索対象のディレクトリを追加・削除できるリストビュー。
-- スキャン/ハッシュ計算のプログレスバー。
-- 検索バー (選択した画像に似た画像を検索)。
-- **結果グリッド / 重複管理ビュー**: 
-    - グループ化された類似画像の表示 (類似度ベースのクラスタリング)。
-    - 各画像に対するサムネ表示とチェックボックス。
-    - **削除機能**: 選択した画像ファイルをディスクから慎重に削除(ゴミ箱へ移動または直接削除)し、データベースからもエントリを削除する機能。
-    - 一括選択、元のファイルを開く、プロパティ表示などのコンテキストメニュー。
-
-## 検証プラン
-
-### 自動テスト
-- 既知のサンプル画像を用いた `calculateDHash` と `calculatePHash` のユニットテスト。
-- パフォーマンス・ベンチマーク: 1,000枚のハッシュ化時間、10万件の検索時間を測定。
-
-### 手動確認
-1. アプリを起動し、多様な画像が含まれるディレクトリを選択。
-2. スキャンフェーズで全 CPU コアが有効活用されているか確認。
-3. 画像を選択し、リサイズや微細な加工が施された類似画像が正しく表示されるか確認。
-4. 1万枚以上の画像で UI の応答性が維持されているか確認。

+ 1 - 1
include/MainWindow.hpp

@@ -76,7 +76,7 @@ private:
   QSlider *m_thresholdSlider;  // ハミング距離しきい値の設定スライダー
   QLabel *m_thresholdLabel;    // しきい値の現在数値ラベル
   QCheckBox *m_strictCheckBox; // pHash, dHash両方を厳密チェックするか
-  int m_currentThreshold = 10;
+  int m_currentThreshold = 5;
   bool m_strictMode = false;
 
   // 非同期(バックグラウンド)処理用オブジェクト

+ 3 - 2
src/CmdMain.cpp

@@ -120,7 +120,8 @@ void cmdAdd(const QStringList &dirs) {
   QProcess::startDetached(program, workerArgs);
 }
 
-// バックグラウンドで画像ファイルのハッシュ値(dHash, pHash)を計算し、DBへ書き込む処理
+// バックグラウンドで画像ファイルのハッシュ値(dHash,
+// pHash)を計算し、DBへ書き込む処理
 // GUI動作との競合を避ける配慮などが実装されている
 void workerAdd(const QStringList &dirs) {
   // バックグラウンド処理のためCPU優先度を下げる
@@ -214,7 +215,7 @@ void cmdSearch(const QString &imageFile) {
 
   QString iniPath = QCoreApplication::applicationDirPath() + "/DupFind.ini";
   QSettings settings(iniPath, QSettings::IniFormat);
-  int threshold = settings.value("threshold", 10).toInt();
+  int threshold = settings.value("threshold", 5).toInt();
   bool strict = settings.value("strict_mode", false).toBool();
 
   DatabaseManager dbManager("dupfind_cache.db");

+ 19 - 10
src/MainWindow.cpp

@@ -174,7 +174,7 @@ void MainWindow::loadSettings() {
   QString iniPath = QCoreApplication::applicationDirPath() + "/DupFind.ini";
   QSettings settings(iniPath, QSettings::IniFormat);
 
-  m_currentThreshold = settings.value("threshold", 10).toInt();
+  m_currentThreshold = settings.value("threshold", 5).toInt();
   m_strictMode = settings.value("strict_mode", false).toBool();
   m_loadedDirs = settings.value("directories").toStringList();
 }
@@ -203,6 +203,7 @@ void MainWindow::onAddDirectory() {
       QFileDialog::getExistingDirectory(this, "Select Directory to Scan");
   if (!dir.isEmpty()) {
     m_dirList->addItem(dir);
+    saveSettings();
   }
 }
 
@@ -214,6 +215,7 @@ void MainWindow::onRemoveDirectory() {
     delete item;
     // キャッシュ再読み込み
     m_lastScannedImages = getFilteredImages();
+    saveSettings();
   }
 }
 
@@ -325,6 +327,9 @@ void MainWindow::onThresholdChanged(int value) {
   m_currentThreshold = value;
   m_thresholdLabel->setText(QString::number(value));
 
+  // フリーズして強制終了しても次回起動時に閾値が復元されるように保存
+  saveSettings();
+
   // 操作が止まるまで待機(デバウンス)
   m_searchTimer->start();
 }
@@ -373,7 +378,7 @@ void MainWindow::onSearchFinished() {
 
 void MainWindow::removeGroupFromView(int groupId) {
   if (groupId >= 0 && groupId < static_cast<int>(m_currentGroups.size())) {
-    for (const auto& img : m_currentGroups[groupId].images) {
+    for (const auto &img : m_currentGroups[groupId].images) {
       m_ignoredPaths.insert(img.path);
     }
     m_lastScannedImages = getFilteredImages();
@@ -411,7 +416,7 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
       QAction *copyAction = menu.addAction("Copy Full Path(&C)");
       menu.addSeparator();
       QAction *removeAction = menu.addAction("Remove from List(&R)");
-      
+
       QAction *selectedAction = menu.exec(ce->globalPos());
       if (selectedAction == copyAction) {
         QApplication::clipboard()->setText(path);
@@ -426,11 +431,12 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
 }
 
 // 同一・類似と判定された画像のグループを受け取り、UI上のグリッドレイアウトへ動的にサムネイルと削除候補のチェックボックスを描画する
-void MainWindow::updateResultGrid(const std::vector<DuplicateGroup> &groups, bool preserveState) {
+void MainWindow::updateResultGrid(const std::vector<DuplicateGroup> &groups,
+                                  bool preserveState) {
   // 状態の保存
   std::unordered_map<std::string, bool> previousState;
   if (preserveState) {
-    for (const auto& item : m_resultItems) {
+    for (const auto &item : m_resultItems) {
       if (item.checkbox) {
         previousState[item.path] = item.checkbox->isChecked();
       }
@@ -477,7 +483,8 @@ void MainWindow::updateResultGrid(const std::vector<DuplicateGroup> &groups, boo
       thumb->setProperty("filePath", QString::fromStdString(imgData.path));
       thumb->setProperty("groupId", groupId);
       thumb->installEventFilter(this);
-      thumb->setToolTip("Double click to open\nRight click to copy path or remove from list");
+      thumb->setToolTip(
+          "Double click to open\nRight click to copy path or remove from list");
 
       thumb->setText("Loading...");
       thumb->setAlignment(Qt::AlignCenter);
@@ -515,7 +522,8 @@ void MainWindow::updateResultGrid(const std::vector<DuplicateGroup> &groups, boo
         }
         return QImage();
       }).then(this, [safeThumb](QImage img) {
-        if (!safeThumb) return; // 既にUIがクリアされている場合は何もしない
+        if (!safeThumb)
+          return; // 既にUIがクリアされている場合は何もしない
 
         if (!img.isNull()) {
           QPixmap pix = QPixmap::fromImage(img);
@@ -528,7 +536,8 @@ void MainWindow::updateResultGrid(const std::vector<DuplicateGroup> &groups, boo
 
       // 自動チェック: 残す1枚(bestImage)以外を削除候補としてチェックする
       QCheckBox *cb = new QCheckBox("Delete candidate");
-      if (preserveState && previousState.find(imgData.path) != previousState.end()) {
+      if (preserveState &&
+          previousState.find(imgData.path) != previousState.end()) {
         cb->setChecked(previousState[imgData.path]);
       } else {
         if (&imgData != bestImage) {
@@ -630,8 +639,8 @@ void MainWindow::onDeleteSelected() {
 
     // リストをリフレッシュ
     auto images = getFilteredImages();
-    m_currentGroups = SimilaritySearch::findDuplicates(images, m_currentThreshold,
-                                                   m_strictMode);
+    m_currentGroups = SimilaritySearch::findDuplicates(
+        images, m_currentThreshold, m_strictMode);
     updateResultGrid(m_currentGroups);
   }
 }

+ 0 - 19
task.md

@@ -1,19 +0,0 @@
-# タスク: 画像類似度検索 Windows アプリの開発 (C++)
-
-## 計画
-- [x] 調査とプロジェクトアーキテクチャの検討 [x]
-- [x] 実施計画(日本語版)の作成 [x]
-- [x] ユーザーによる計画の承認 [x]
-
-## 実装 - コアロジック (CLI/ライブラリ)
-- [x] GUI フレームワーク (Qt 6) の選定とセットアップ [x]
-- [x] メインウィンドウ (ディレクトリ選択、進捗バー) の作成 [x]
-- [x] サムネイル・キャッシング・エンジンの実装 (高速スクロール用) [x]
-- [x] 結果表示ビュー (類似画像のグリッド表示 + グループ化) の作成 [x]
-- [x] 画像選択・削除の UI 機能(チェックボックス、一括削除ボタン)の実装 [x]
-- [x] ファイル削除の安全な実行 (ゴミ箱への移動機能など) の実装 [x]
-
-## 検証と最適化
-- [ ] 1万枚以上の画像を用いたパフォーマンス・テスト [ ]
-- [ ] マルチスレッド戦略の洗練 [ ]
-- [x] 最終ウォークスルー [x]