Как организовать поиск изображений в PHP: от базовых до продвинутых техник
Поиск изображений в PHP: обзор методов
При разработке веб-приложений часто возникает задача поиска изображений на сервере. Это может быть поиск по имени файла, по содержимому (дубликаты, похожие картинки) или по метаданным (EXIF). В статье рассматриваются различные подходы, от простого сканирования файловой системы до сравнения по перцептивному хэшу. Каждый вариант сопровождается примером кода и указанием возможных проблем.
Наиболее эффективный метод: сравнение по перцептивному хэшу (pHash)
Как найти похожие или дублирующиеся изображения в PHP без внешних библиотек?
Перцептивный хэш преобразует изображение в компактный битовый набор, устойчивый к небольшим изменениям (изменение размера, небольшое сжатие). Для сравнения используется расстояние Хэмминга: чем меньше различающихся бит, тем изображения более похожи. Реализация на чистом PHP с использованием расширения GD занимает минимум ресурсов и работает достаточно быстро для библиотек среднего размера.
// Функция вычисления перцептивного хэша
function perceptualHash(string $filename): string {
$img = imagecreatefromstring(file_get_contents($filename));
if (!$img) return '';
$width = 8; $height = 8;
$small = imagescale($img, $width, $height);
imagedestroy($img);
$pixels = [];
for ($y = 0; $y < $height; $y++) {
for ($x = 0; $x < $width; $x++) {
$rgb = imagecolorat($small, $x, $y);
$gray = ($rgb & 0xFF) * 0.299 + (($rgb >> 8) & 0xFF) * 0.587 + (($rgb >> 16) & 0xFF) * 0.114;
$pixels[] = $gray;
}
}
imagedestroy($small);
$avg = array_sum($pixels) / count($pixels);
$hash = '';
foreach ($pixels as $p) {
$hash .= ($p >= $avg) ? '1' : '0';
}
return $hash;
}
// Расстояние Хэмминга
function hammingDistance(string $hash1, string $hash2): int {
return count(array_diff_assoc(str_split($hash1), str_split($hash2)));
}
// Поиск дубликатов в папке
function findDuplicates(string $dir): array {
$hashes = [];
$duplicates = [];
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
foreach ($files as $file) {
if ($file->isFile() && preg_match('/\.(jpe?g|png|gif|webp)$/i', $file->getFilename())) {
$hash = perceptualHash($file->getPathname());
if (!$hash) continue;
foreach ($hashes as $existing => $path) {
if (hammingDistance($hash, $existing) <= 5) {
$duplicates[] = [$path, $file->getPathname()];
}
}
$hashes[$hash] = $file->getPathname();
}
}
return $duplicates;
}
Search index php topic (поиск темы в индексе php)
Пример использования: $dups = findDuplicates('/path/to/images'); вернёт массив пар путей одинаковых изображений.
imagecreatefromstring может не обработать повреждённый файл (выдаст false). Рекомендуется обернуть вызов в try-catch или проверять результат. Для больших папок (более 1000 изображений) вычисление хэша последовательно может быть медленным – стоит кешировать хэши в файл или базу данных. Перцептивный хэш не различает изображения с разным содержанием, но одинаковой средней яркостью – для более точной работы увеличивают размер миниатюры до 16x16.Вариант 1: Поиск по расширению файла с glob()
Как быстро найти все файлы изображений в одной директории?
Функция glob() с флагом GLOB_BRACE позволяет перечислить файлы по маске. Это самый простой способ, не требующий рекурсии.
$images = glob('images/*.{jpg,jpeg,png,gif,webp}', GLOB_BRACE);
Search php images (поиск изображений в php)
GLOB_BRACE не работает с относительными путями в некоторых конфигурациях. В Windows может потребоваться обратный слэш. Результат не включает подкаталоги (нерекурсивно). Если изображения имеют регистр, отличный от указанного, они не будут найдены (например, .JPG). Решение – использовать дополнительный шаблон для каждого расширения или применять итератор.Вариант 2: Рекурсивный поиск с RecursiveDirectoryIterator
Как найти изображения во всех подкаталогах?
Классы SPL (Standard PHP Library) позволяют легко обходить дерево каталогов. Комбинация RecursiveDirectoryIterator и RecursiveIteratorIterator даёт итератор по всем файлам. Фильтрация по расширению выполняется внутри цикла или через RegexIterator.
$directory = new RecursiveDirectoryIterator('/path/to/photos');
$iterator = new RecursiveIteratorIterator($directory);
$images = [];
foreach ($iterator as $file) {
if ($file->isFile() && in_array(strtolower($file->getExtension()), ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
$images[] = $file->getPathname();
}
}
Posts php search (поиск постов в php)
DirectoryIterator вручную с рекурсией через стек. Также следует учитывать права доступа – некоторые папки могут быть недоступны, вызов isFile() вызовет предупреждение. Желательно обернуть в @ или проверять на ошибки.Вариант 3: Поиск по EXIF метаданным
Как найти изображения, сделанные на определённую камеру или в определённый период?
Функция exif_read_data() извлекает метаданные из JPEG и TIFF. Можно отфильтровать файлы по модели камеры (Model), дате съёмки (DateTimeOriginal) и другим параметрам.
function findImagesByCamera(string $dir, string $cameraModel): array {
$result = [];
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)) as $file) {
if ($file->isFile() && in_array(strtolower($file->getExtension()), ['jpg', 'jpeg', 'tiff'])) {
$exif = @exif_read_data($file->getPathname(), 'IFD0', true);
if ($exif && isset($exif['IFD0']['Model'])) {
if (stripos($exif['IFD0']['Model'], $cameraModel) !== false) {
$result[] = $file->getPathname();
}
}
}
}
return $result;
}
Forum index php search (поиск на форуме (index) в php)
exif_read_data требует включения расширения EXIF (обычно включено). Некоторые файлы не содержат EXIF (например, PNG или WEBP) – для них функция вернёт false. В таких случаях лучше предварительно проверять формат. Кроме того, метаданные могут быть повреждены или перезаписаны. Использование @ подавляет предупреждения, но может скрыть реальные проблемы.Вариант 4: Поиск по имени файла с регулярными выражениями
Как отфильтровать изображения по шаблону имени (например, с префиксом 'IMG_' или содержащие '2024')?
Используя preg_grep() или фильтрацию в цикле, можно выбирать файлы, чьи имена соответствуют регулярному выражению. Это полезно, когда изображения имеют определённую схему именования от камеры или системы.
function findImagesByPattern(string $dir, string $pattern): array {
$files = [];
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
foreach ($iterator as $file) {
if ($file->isFile() && preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $file->getFilename())) {
if (preg_match($pattern, $file->getFilename())) {
$files[] = $file->getPathname();
}
}
}
return $files;
}
// Пример: найти все изображения, начинающиеся с 'IMG_'
$result = findImagesByPattern('/photos', '/^IMG_/i');
i помогает). При большом количестве файлов цикл может замедлиться. Более производительный подход – получить список имён через glob() с маской, если шаблон можно выразить через * и ?. Но glob не поддерживает произвольные регулярные выражения.Расширенные примеры поиска изображений
Пакетное вычисление хэшей и запись в файл
Для ускорения последующих поисков дубликатов полезно один раз вычислить хэши всех изображений и сохранить их в текстовом файле (например, в формате CSV). Затем при новом поиске загружать уже готовые значения.
// Сохранить хэши в файл
function buildHashDatabase(string $dir, string $outputFile): void {
$fp = fopen($outputFile, 'w');
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)) as $file) {
if ($file->isFile() && preg_match('/\.(jpe?g|png|gif|webp)$/i', $file->getFilename())) {
$hash = perceptualHash($file->getPathname());
if ($hash) {
fputcsv($fp, [$hash, $file->getPathname()]);
}
}
}
fclose($fp);
}
// Загрузить хэши из файла и найти дубликаты
function findDuplicatesFromFile(string $csvFile): array {
$entries = [];
$duplicates = [];
if (($fp = fopen($csvFile, 'r')) !== false) {
while (($row = fgetcsv($fp)) !== false) {
$hash = $row[0];
$path = $row[1];
foreach ($entries as $existingHash => $existingPath) {
if (hammingDistance($hash, $existingHash) <= 5) {
$duplicates[] = [$existingPath, $path];
}
}
$entries[$hash] = $path;
}
fclose($fp);
}
return $duplicates;
}
Пример содержимого CSV-файла: "1010101010101010","/images/photo1.jpg" "1100110011001100","/images/photo2.jpg" ... После загрузки может быть найдена пара: ["/images/photo1.jpg", "/images/photo_duplicate.jpg"] (расстояние Хэмминга = 3)
Поиск изображений по диапазону дат из EXIF
Допустим, требуется найти все снимки, сделанные за конкретный день или временной промежуток. EXIF хранит дату в поле DateTimeOriginal в формате "YYYY:MM:DD HH:MM:SS".
function findImagesByDateRange(string $dir, DateTime $start, DateTime $end): array {
$result = [];
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
foreach ($iterator as $file) {
if ($file->isFile() && preg_match('/\.(jpe?g|tiff)$/i', $file->getFilename())) {
$exif = @exif_read_data($file->getPathname());
if ($exif && isset($exif['DateTimeOriginal'])) {
$date = DateTime::createFromFormat('Y:m:d H:i:s', $exif['DateTimeOriginal']);
if ($date && $date >= $start && $date <= $end) {
$result[] = $file->getPathname();
}
}
}
}
return $result;
}
// Пример: найти фотографии за 1 января 2024 года
$start = new DateTime('2024-01-01 00:00:00');
$end = new DateTime('2024-01-01 23:59:59');
$photos = findImagesByDateRange('/camera', $start, $end);
Результат (массив путей):
[
"/camera/2024-01-01/IMG_001.jpg",
"/camera/2024-01-01/IMG_002.jpg",
...
]
Если ни один файл не содержит EXIF с датой, массив будет пуст.
Поиск изображений по цветовой гистограмме (приближённый)
Для простого поиска изображений со схожей цветовой палитрой можно вычислить средний цвет или доминирующие тона. Ниже приведён пример, который находит картинки, где средний цвет близок к заданному RGB.
function averageColor(string $filename): array {
$img = imagecreatefromstring(file_get_contents($filename));
if (!$img) return [0,0,0];
$w = imagesx($img); $h = imagesy($img);
$r = $g = $b = 0;
for ($y = 0; $y < $h; $y+=10) { // уменьшаем шаг для скорости
for ($x = 0; $x < $w; $x+=10) {
$rgb = imagecolorat($img, $x, $y);
$r += ($rgb >> 16) & 0xFF;
$g += ($rgb >> 8) & 0xFF;
$b += $rgb & 0xFF;
}
}
$count = ceil($w/10) * ceil($h/10);
imagedestroy($img);
return [intdiv($r, $count), intdiv($g, $count), intdiv($b, $count)];
}
function findImagesBySimilarColor(string $dir, array $targetColor, int $threshold = 30): array {
$result = [];
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)) as $file) {
if ($file->isFile() && preg_match('/\.(jpe?g|png|gif|webp)$/i', $file->getFilename())) {
$avg = averageColor($file->getPathname());
$diff = sqrt(pow($avg[0] - $targetColor[0], 2) + pow($avg[1] - $targetColor[1], 2) + pow($avg[2] - $targetColor[2], 2));
if ($diff <= $threshold) {
$result[] = $file->getPathname();
}
}
}
return $result;
}
// Найти изображения, близкие к светло-синему (150, 200, 255)
$similar = findImagesBySimilarColor('/gallery', [150, 200, 255], 40);
Пример вывода (первые несколько путей):
[
"/gallery/sky.jpg",
"/gallery/ocean.jpg",
"/gallery/wallpaper_blue.jpg"
]
При значениях порога > 100 могут вернуться почти все изображения, поэтому порог следует подбирать экспериментально.