Прореживание похожих изображений в датасете по перцептивному хешу. Написано для подготовки выборок под обучение Stable Diffusion / WAN / FLUX.
В папке с картинками для обучения обычно много почти одинаковых кадров: соседние кадры из видео, серийная съёмка, слегка перекадрированные варианты одного снимка. Модель переобучается на такой многократно повторённый ракурс, а разнообразие датасета страдает.
Утилита находит наиболее похожие друг на друга изображения и удаляет их, оставляя по одному представителю от каждой группы дублей.
Шаг 1 — хеширование. Каждый файл в директории открывается и прогоняется через перцептивный хеш (crate img_hash). Хеш — 64-битная подпись картинки, устойчивая к масштабированию, пережатию JPEG и мелким правкам: похожие изображения дают похожие хеши. Мера похожести — расстояние Хэмминга между хешами: 0 — хеши идентичны, 64 — противоположны. Хеши считаются один раз при загрузке.
Файлы, которые не удалось открыть как изображение, пропускаются с сообщением в stderr. Подпапки не обходятся.
Шаг 2 — жадное удаление. Пока не исчерпан лимит -n и находятся пары ближе порога -t:
- Выбирается кандидат на удаление (см. стратегии ниже).
- Считается расстояние от кандидата до ближайшего к нему изображения.
- Если это расстояние больше порога — похожих пар в наборе не осталось, работа заканчивается досрочно.
- Иначе файл удаляется, и следующая итерация пересчитывает картину уже без него.
--strategy |
Кандидат на удаление |
|---|---|
pairwise (по умолчанию) |
изображение, у которого есть самый близкий двойник в наборе |
central |
«самое среднее» изображение — с минимальной суммой расстояний до всех остальных |
pairwise бьёт точечно по дублирующимся парам, оставляя второго участника пары. central вычищает центр масс датасета — полезно, когда надо не убрать дубли, а увеличить разброс выборки.
Порог -t в обеих стратегиях проверяется по расстоянию до ближайшего соседа: сумма расстояний в central не сравнима с порогом напрямую.
cargo build --releaseimage-dedup <директория> [ОПЦИИ]
ОПЦИИ:
-n, --max-deletions <N> максимум удаляемых файлов (по умолчанию 20)
-t, --threshold <D> максимальное расстояние Хэмминга, при котором пара
считается дублем (по умолчанию 10)
--strategy <S> pairwise (по умолчанию) | central
--apply действительно удалять файлы
-h, --help справка
Без --apply утилита ничего не удаляет — только печатает список файлов, которые были бы удалены, и в каком порядке. Удаление необратимо и в корзину не кладёт, так что сначала всегда смотрите сухой прогон:
image-dedup ./dataset -t 6 # посмотреть, что уйдёт
image-dedup ./dataset -t 6 --apply # удалитьХеш 64-битный, осмысленный диапазон порога — примерно 0..20.
-t |
Что попадает под удаление |
|---|---|
0 |
только изображения с полностью совпавшим хешем |
2–5 |
пережатые/отмасштабированные копии одного кадра |
6–10 |
соседние кадры видео, серийная съёмка |
>12 |
начинают попадать просто похожие по композиции снимки |
Начинайте с сухого прогона на -t 5 и поднимайте, глядя на выводимые расстояния.
-n — верхний предел, страховка от того, чтобы утилита не выкосила полдатасета за один заход. Если порог достигнут раньше лимита, работа останавливается сама.
Хеширование файлов в ./dataset:
1jbF0aHiSyE= ./dataset/frame_001.png
1jbF0aHiSyE= ./dataset/frame_002.png
fJRchAmDkxQ= ./dataset/portrait.jpg
пропущен (не изображение): ./dataset/notes.txt
Загружено изображений: 3, пропущено файлов: 1
Режим сухого прогона: файлы не удаляются. Для удаления добавьте --apply.
Стратегия: pairwise, порог: 10, лимит удалений: 20
Было бы удалено (расстояние 0): ./dataset/frame_001.png
Осталось 2 изображений, ближайшая пара на расстоянии 27 — это больше порога 10. Останавливаемся.
Итог: к удалению 1 файл(ов), осталось изображений: 2
Ничего не удалено — это был сухой прогон. Повторите с --apply.
Rust 2021, без CLI-фреймворков. Зависимости: img_hash 3.2 (хеширование), image 0.23.14 (декодирование).
Алгоритм хеширования — не pHash. HasherConfig::new() даёт алгоритм по умолчанию, HashAlg::Gradient (родственник dHash). Он быстрее pHash и хорошо ловит пережатие и масштабирование, но хуже переносит поворот и сильную коррекцию яркости. Настоящий pHash включается через .preproc_dct() в load_images().
Сложность O(N · n²). Матрица расстояний пересчитывается на каждой итерации удаления. Хеши, к счастью, считаются один раз. Для сотен и тысяч картинок это незаметно, для десятков тысяч при большом -n — ощутимо; выполнение однопоточное.
img_hash 3.2 больше не поддерживается (последний релиз ~2020) и тянет за собой устаревший image 0.23. Актуальный форк — image_hasher, он работает со свежим image.
Обход не рекурсивный — файлы берутся только из указанной директории, подпапки игнорируются.