PHP: полный мануал с примерами, функциями и ООП
Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Дерево руководства
Глава 380 · После объектов, клонирования, массивов и исключений
Imagick: найти фрагмент изображения и проверить совпадение
Найти известную этикетку, получить координаты и карту, затем отклонить непохожий образец вместо ложной рамки.
Найти знакомый фрагмент и увидеть, что вернул метод
На изображении каталога есть небольшая известная этикетка. Нужно получить её координаты и нарисовать рамку, а не вручную угадывать x,y для каждого файла. У нас есть образец 40 × 24 и исходник 400 × 320. Поиск выполняется в заранее известной области 160 × 128; результат переводится назад в координаты исходника. Сразу видны сам шаблон, найденное место и настоящая карта соответствия.

Представьте прозрачную рамку, которую перемещают поверх рисунка и сравнивают пиксели образца с тем, что оказалось внутри. Это модель сдвига по изображению, а не узнавания смысла слова или предмета. Библиотека не читает «MUG» как OCR и не ищет автоматически повернутый или увеличенный логотип. Для сравнения здесь взят точный фрагмент той же непрозрачной RGB-картинки. Объект хранит изображение, массив хранит координаты, возвращаемое значение может стать отдельным новым объектом.
$offset = null;
$score = null;
$map = $region->subImageMatch($template, $offset, $score);$region — изображение, в котором ищем; -> вызывает его метод. $template — маленький образец. Переменные $offset и $score передаются, чтобы метод заполнил их результатами; знак & уже относится к объявлению параметров, его не добавляют в вызов. Выражение справа возвращает новый объект, который сохраняется в $map. Здесь три разных канала результата: карта, координаты и оценка. В самостоятельных программах дополнительно указаны метрика RMSE и порог, чтобы не полагаться на особенности значения по умолчанию.
Найти известный фрагмент и увидеть координаты с полной картой
В шаблоне 40 × 24 находятся пиксели известного фрагмента этикетки. На поясняющей панели показана отдельная копия для просмотра 4×; поиск использует исходные 40 × 24. Поиск в области 160 × 128 вернул локальные 40, 60; на исходнике это 80, 160. Красная рамка показывает найденный прямоугольник 40 × 24. Карта 160 × 128 создана полностью; отдельная копия для просмотра увеличена ближайшим соседом ровно вдвое и положена на белую панель.
<?php
declare(strict_types=1);
require __DIR__.'/subimage-fixture.php';
subimageRestore();
if (!method_exists(Imagick::class, 'subImageMatch')) { throw new RuntimeException('This example needs subImageMatch'); }
$base = subimageParts();
$region = clone $base;
$region->cropImage(160, 128, 40, 100);
$region->setImagePage(0, 0, 0, 0);
$template = clone $base;
$template->cropImage(40, 24, 80, 160);
$template->setImagePage(0, 0, 0, 0);
$sourceBefore = subimageHash($base); $regionBefore = subimageHash($region);
$offset = null; $score = null;
// In this exact IM7 implementation, -1 prevents early exit for nonnegative RMSE.
$map = $region->subImageMatch($template, $offset, $score, -1.0,
Imagick::METRIC_ROOTMEANSQUAREDERROR);
$global = $offset; $global['x'] += 40; $global['y'] += 100;
$marked = clone $base;
$outline = new ImagickDraw(); $outline->setFillColor('none');
$outline->setStrokeColor('#c72b32'); $outline->setStrokeWidth(2);
$outline->rectangle($global['x'], $global['y'],
$global['x']+$global['width']-1, $global['y']+$global['height']-1);
$marked->drawImage($outline);
$templatePanel = subimageTemplatePanel($template);
$mapPanel = subimageMapPanel($map);
subimageView($base, 'source-preview8.png');
subimageView($template, 'template.png');
subimageView($templatePanel, 'template-panel.png');
subimageView($marked, 'matched-context.png');
subimageView($map, 'similarity-map.png');
subimageView($mapPanel, 'map-panel.png');
$rects = subimageCompare([$base,$templatePanel,$marked,$mapPanel], 'subimage-first.png');
$sourceUnchanged = $sourceBefore===subimageHash($base);
$regionUnchanged = $regionBefore===subimageHash($region);
subimageDiagnostic('subimage-match-first', ['executed_api'=>'subImageMatch',
'threshold'=>-1.0, 'metric'=>Imagick::METRIC_ROOTMEANSQUAREDERROR,
'offset'=>$offset, 'global_offset'=>$global, 'score'=>$score,
'ROI'=>['x'=>40,'y'=>100,'width'=>160,'height'=>128],
'map_size'=>[$map->getImageWidth(),$map->getImageHeight()],
'map_display'=>['zoom'=>2,'x'=>40,'y'=>32,'width'=>320,'height'=>256],
'source_unchanged'=>$sourceUnchanged, 'region_unchanged'=>$regionUnchanged,
'panel_geometry'=>$rects, 'checks'=>['map_returned'=>$map instanceof Imagick,
'exact_match'=>$score===0.0,'correct_local_position'=>$offset===['x'=>40,'y'=>60,'width'=>40,'height'=>24],
'correct_global_position'=>$global===['x'=>80,'y'=>160,'width'=>40,'height'=>24],
'actual_map_size'=>$map->getImageWidth()===160&&$map->getImageHeight()===128,
'source_unchanged'=>$sourceUnchanged,'region_unchanged'=>$regionUnchanged]]);
echo "subimage-first.png\nsource-preview8.png\ntemplate.png\ntemplate-panel.png\nmatched-context.png\nsimilarity-map.png\nmap-panel.png\nmetrics.json\nlocal=40,60 global=80,160 RMSE=0.000000 map=160x128 full=1\n";
В шаблоне 40 × 24 находятся пиксели известного фрагмента этикетки. На поясняющей панели показана отдельная копия для просмотра 4×; поиск использует исходные 40 × 24. Поиск в области 160 × 128 вернул локальные 40, 60; на исходнике это 80, 160. Красная рамка показывает найденный прямоугольник 40 × 24. Карта 160 × 128 создана полностью; отдельная копия для просмотра увеличена ближайшим соседом ровно вдвое и положена на белую панель. Архив содержит example.php, source.png.b64 и subimage-fixture.php. Сохраните рядом, выполните php example.php с Imagick, поддержкой PNG и правом записи. Helper восстанавливает собственный PNG по SHA и накладывает на белую основу 400 × 320; вырезание области и сам subImageMatch видны в программе. Все три самостоятельные программы выполнены в указанной сборке. Значения конкретного примера не являются обещанием одинаковой оценки для произвольных фотографий.
Ожидаемый вывод
subimage-first.png
source-preview8.png
template.png
template-panel.png
matched-context.png
similarity-map.png
map-panel.png
metrics.json
local=40,60 global=80,160 RMSE=0.000000 map=160x128 full=1
Разобрать карту, параметры по ссылке и перевод координат

Область поиска начинается в исходнике в точке (40, 100). После cropImage она имеет 160 × 128 пикселей; setImagePage(0,0,0,0) убирает старое виртуальное смещение. Внутри неё шаблон найден в (40, 60). Поэтому глобальные координаты: x = 40 + 40 = 80, y = 100 + 60 = 160. Ширина 40 и высота 24 описывают шаблон. Рамку рисуем до x+width−1 и y+height−1, потому что крайняя координата включена.
| Результат | Что с ним делать |
|---|---|
| $map:Imagick | Сохранить или проанализировать карту соответствия. В текущем fallback она имеет размер всего поискового изображения 160 × 128. |
| $offset=[x=>40,y=>60,width=>40,height=>24] | Привязать левый верхний угол шаблона. Это не положение его центра и не размеры карты. |
| $score=0.0 при RMSE | Точное совпадение в этом PNG-примере. Меньшая ошибка лучше, большой score может означать неприемлемый кандидат. |
| Глобальные 80, 160 | Прибавить смещение области поиска, затем работать с исходником 400 × 320. |
Для RMSE вычисленные точки карты тем светлее, чем меньше ошибка; точное совпадение белое. Но при threshold 0 библиотека прекращает дальнейшую работу после найденного нулевого значения. Невычисленные области могут сохранять пиксели исходной копии. Поэтому минимальный пример не выдаёт этот объект за полную серую карту. В примере с полной картой выбран −1: в точной реализации неотрицательная RMSE не достигнет этого порога и все положения будут посчитаны. Это объяснение текущего Core, а не универсальная рекомендация отрицательных порогов для любой метрики/версии.
Настоящий результат карты 160 × 128 сохранён без увеличения в similarity-map.png. Отдельная копия для просмотра увеличена FILTER_POINT ровно до 320 × 256 и лежит в 400 × 320 панели в (40, 32). Пиксельный поиск выполнялся на исходных размерах, не на этой копии. Шаблон для поиска остаётся 40 × 24 и сохранён отдельно в template.png. В демонстрационной панели показана отдельная копия, увеличенная ближайшим соседом в 4 раза до 160 × 96 и размещённая в (120, 112). Эта белая панель и увеличенная копия не участвуют в поиске.
Запустить минимальный поиск одной точной копии

Самая простая полезная задача — получить одно найденное положение точной копии. Вызов с threshold 0 может закончить поиск раньше, когда RMSE стала 0. Этого достаточно для этого примера: возвращены 40, 60 в области и 0 ошибки. Сразу переводим координаты в исходник и рисуем рамку на clone, сохраняя оригинал. Клонирование создаёт отдельное изображение для аннотации.
Получить координаты одной точной копии и отметить её
Минимальный вызов нашёл точный фрагмент: локальные 40, 60, глобальные 80, 160, RMSE=0. Возвращённый объект карты сохранён отдельно для диагностики; из-за ранней остановки threshold0 его нельзя показывать как полную карту всех положений. Видимый результат этого примера — рамка на исходном изображении.
<?php
declare(strict_types=1);
require __DIR__.'/subimage-fixture.php';
subimageRestore();
if (!method_exists(Imagick::class, 'subImageMatch')) { throw new RuntimeException('This example needs subImageMatch'); }
$base = subimageParts();
$region = clone $base;
$region->cropImage(160, 128, 40, 100);
$region->setImagePage(0, 0, 0, 0);
$template = clone $base;
$template->cropImage(40, 24, 80, 160);
$template->setImagePage(0, 0, 0, 0);
$offset = null; $score = null;
$map = $region->subImageMatch($template, $offset, $score, 0.0,
Imagick::METRIC_ROOTMEANSQUAREDERROR);
$global = $offset; $global['x'] += 40; $global['y'] += 100;
$marked = clone $base;
$outline = new ImagickDraw(); $outline->setFillColor('none');
$outline->setStrokeColor('#c72b32'); $outline->setStrokeWidth(2);
$outline->rectangle($global['x'], $global['y'],
$global['x']+$global['width']-1, $global['y']+$global['height']-1);
$marked->drawImage($outline);
subimageView($marked, 'subimage-minimal.png');
// Saved as a diagnostic only: threshold0 can leave parts of this map uncomputed.
subimageView($map, 'partial-map.png');
subimageDiagnostic('subimage-match-minimal', ['executed_api'=>'subImageMatch',
'threshold'=>0.0,'metric'=>Imagick::METRIC_ROOTMEANSQUAREDERROR,
'offset'=>$offset,'global_offset'=>$global,'score'=>$score,
'map_size'=>[$map->getImageWidth(),$map->getImageHeight()],
'full_map_claimed'=>false,'checks'=>['map_returned'=>$map instanceof Imagick,
'exact_match'=>$score===0.0,'correct_local_position'=>$offset===['x'=>40,'y'=>60,'width'=>40,'height'=>24],
'correct_global_position'=>$global===['x'=>80,'y'=>160,'width'=>40,'height'=>24],
'actual_map_size'=>$map->getImageWidth()===160&&$map->getImageHeight()===128]]);
echo "subimage-minimal.png\npartial-map.png\nmetrics.json\nlocal=40,60 global=80,160 RMSE=0.000000 full-map-claimed=0\n";
Минимальный вызов нашёл точный фрагмент: локальные 40, 60, глобальные 80, 160, RMSE=0. Возвращённый объект карты сохранён отдельно для диагностики; из-за ранней остановки threshold0 его нельзя показывать как полную карту всех положений. Видимый результат этого примера — рамка на исходном изображении. Архив содержит example.php, source.png.b64 и subimage-fixture.php. Сохраните рядом, выполните php example.php с Imagick, поддержкой PNG и правом записи. Helper восстанавливает собственный PNG по SHA и накладывает на белую основу 400 × 320; вырезание области и сам subImageMatch видны в программе. Все три самостоятельные программы выполнены в указанной сборке. Значения конкретного примера не являются обещанием одинаковой оценки для произвольных фотографий.
Ожидаемый вывод
subimage-minimal.png
partial-map.png
metrics.json
local=40,60 global=80,160 RMSE=0.000000 full-map-claimed=0
Красная рамка — это наше дополнительное рисование через ImagickDraw, она не появляется автоматически из subImageMatch. $map — другой объект; операция поиска не возвращает готовую картинку с рамкой. На одинаковом фоне могут быть несколько одинаковых мест: не обещаем, что при равной оценке всегда вернётся самый левый или верхний вариант. В этом собственном шаблоне результат конкретен и проверен запуском.
Отличить лучший кандидат от принятого совпадения

В проверке наличия метки опасно принять любые координаты за успех. Функция locateExactPatch возвращает карту, оценку, координаты и отдельное решение accepted. Сначала проверяется, что прямоугольник полностью внутри области. Затем требуется конечная RMSE ≤ 1e−9. Это наша строгая политика для точных копий в PNG, а не рекомендуемый порог для любых фотографий и не процент уверенности.
Отклонить непохожий шаблон и перехватить слишком большой
Непохожий пурпурный шаблон всё равно получил ближайшие координаты 102, 41 внутри области поиска. RMSE≈0.473729 не проходит строгую политику точных копий ≤1e−9, поэтому рамка на исходнике не нарисована. Шаблон 161 × 129 больше области 160 × 128 и вызывает настоящую ImagickException. Исходные пиксели сохранены.
<?php
declare(strict_types=1);
require __DIR__.'/subimage-fixture.php';
subimageRestore();
if (!method_exists(Imagick::class, 'subImageMatch')) { throw new RuntimeException('This example needs subImageMatch'); }
$base = subimageParts();
$region = clone $base;
$region->cropImage(160, 128, 40, 100);
$region->setImagePage(0, 0, 0, 0);
$template = clone $base;
$template->cropImage(40, 24, 80, 160);
$template->setImagePage(0, 0, 0, 0);
function locateExactPatch(Imagick $region, Imagick $template): array {
$offset = null; $score = null;
$map = $region->subImageMatch($template, $offset, $score, -1.0,
Imagick::METRIC_ROOTMEANSQUAREDERROR);
$inside = $offset['x']>=0 && $offset['y']>=0
&& $offset['x']+$offset['width']<=$region->getImageWidth()
&& $offset['y']+$offset['height']<=$region->getImageHeight();
// The application's strict policy for exact copies, not a confidence percentage.
$accepted = is_finite($score) && $score <= 1e-9 && $inside;
return ['offset'=>$offset,'score'=>$score,'inside'=>$inside,
'accepted'=>$accepted,'map'=>$map];
}
$before = subimageHash($region); $sourceBefore = subimageHash($base);
$badTemplate = new Imagick(); $badTemplate->newImage(40,24,'magenta');
$result = locateExactPatch($region, $badTemplate);
$map = $result['map'];
$marked = clone $base;
if ($result['accepted']) {
$global = $result['offset']; $global['x']+=40; $global['y']+=100;
$outline = new ImagickDraw(); $outline->setFillColor('none');
$outline->setStrokeColor('#c72b32'); $outline->setStrokeWidth(2);
$outline->rectangle($global['x'],$global['y'],
$global['x']+$global['width']-1,$global['y']+$global['height']-1);
$marked->drawImage($outline);
}
$oversizeError='not caught';
$oversized=new Imagick(); $oversized->newImage(161,129,'magenta');
$unusedOffset=null; $unusedScore=null;
try { $region->subImageMatch($oversized,$unusedOffset,$unusedScore,0.0,
Imagick::METRIC_ROOTMEANSQUAREDERROR); }
catch (ImagickException $error) { $oversizeError=get_class($error); }
$unchanged=$before===subimageHash($region)&&$sourceBefore===subimageHash($base);
$templatePanel=subimageTemplatePanel($badTemplate); $mapPanel=subimageMapPanel($map);
subimageView($badTemplate,'bad-template.png');
subimageView($templatePanel,'bad-template-panel.png');
subimageView($marked,'rejected-context.png');
subimageView($map,'bad-similarity-map.png');
subimageView($mapPanel,'bad-map-panel.png');
$rects=subimageCompare([$templatePanel,$marked,$mapPanel],'subimage-workflow.png');
subimageDiagnostic('subimage-match-workflow',['executed_api'=>'subImageMatch',
'threshold'=>-1.0,'metric'=>Imagick::METRIC_ROOTMEANSQUAREDERROR,
'offset'=>$result['offset'],'score'=>$result['score'],'inside'=>$result['inside'],
'accepted'=>$result['accepted'],'acceptance_max_RMSE'=>1e-9,
'oversize_error'=>$oversizeError,'source_unchanged'=>$unchanged,
'map_size'=>[$map->getImageWidth(),$map->getImageHeight()],
'map_display'=>['zoom'=>2,'x'=>40,'y'=>32,'width'=>320,'height'=>256],
'panel_geometry'=>$rects,'checks'=>['bad_match_rejected'=>$result['accepted']===false,
'candidate_still_returned'=>isset($result['offset']['x'],$result['offset']['y']),
'candidate_inside'=>$result['inside']===true,'bad_score_exceeds_policy'=>$result['score']>1e-9,
'oversize_error_caught'=>$oversizeError==='ImagickException','source_unchanged'=>$unchanged]]);
echo "subimage-workflow.png\nbad-template.png\nbad-template-panel.png\nrejected-context.png\nbad-similarity-map.png\nbad-map-panel.png\nmetrics.json\n";
printf("candidate=%d,%d RMSE=%.6f accepted=%d oversize=%s source-unchanged=%d\n",
$result['offset']['x'],$result['offset']['y'],$result['score'],
(int)$result['accepted'],$oversizeError,(int)$unchanged);
Непохожий пурпурный шаблон всё равно получил ближайшие координаты 102, 41 внутри области поиска. RMSE≈0.473729 не проходит строгую политику точных копий ≤1e−9, поэтому рамка на исходнике не нарисована. Шаблон 161 × 129 больше области 160 × 128 и вызывает настоящую ImagickException. Исходные пиксели сохранены. Архив содержит example.php, source.png.b64 и subimage-fixture.php. Сохраните рядом, выполните php example.php с Imagick, поддержкой PNG и правом записи. Helper восстанавливает собственный PNG по SHA и накладывает на белую основу 400 × 320; вырезание области и сам subImageMatch видны в программе. Все три самостоятельные программы выполнены в указанной сборке. Значения конкретного примера не являются обещанием одинаковой оценки для произвольных фотографий.
Ожидаемый вывод
subimage-workflow.png
bad-template.png
bad-template-panel.png
rejected-context.png
bad-similarity-map.png
bad-map-panel.png
metrics.json
candidate=102,41 RMSE=0.473729 accepted=0 oversize=ImagickException source-unchanged=1
У пурпурного шаблона действительно нашёлся лучший кандидат внутри границ: 102, 41. Но его ошибка ≈ 0.473729 слишком велика, поэтому accepted=false. Вторая панель намеренно остаётся без рамки: сообщение «метка не подтверждена» полезнее ложного успеха. Карта плохого кандидата всё равно существует. Отдельный шаблон 161 × 129 больше области 160 × 128; он намеренно передан библиотеке и вызвал ImagickException. В обычном сервисе это можно проверить до дорогостоящего вызова и вернуть понятную ошибку размера.
При реальных скриншотах сначала согласуйте масштаб, цветовую модель, прозрачность и формат. JPEG-сжатие, сглаживание шрифта или изменение масштаба может дать ненулевую ошибку даже визуально похожему элементу. Подберите критерий на своих положительных и отрицательных случаях; порог 1e−9 этой демонстрации рассчитан только на точную копию. Прозрачный спрайт с произвольным фоном требует отдельно продуманной маски, а не безусловного сравнения невидимых RGB-пикселей.
Сохранить полный официальный контракт и понять границы поиска
subImageMatch ищет положение маленького известного изображения внутри текущего. Это полезно для контроля известной метки на изображении или проверки фиксированного элемента в скриншоте при одинаковом масштабе. Метод возвращает лучший кандидат даже при плохом соответствии; наличие координат само по себе не означает, что нужная метка найдена.
Официальная базовая сигнатура
Imagick::subImageMatch(Imagick $Imagick, array &$offset = ?, float &$similarity = ?): ImagickВ официальной странице описаны три аргумента, первый называется $Imagick. Параметры $offset и $similarity передаются по ссылке: после успешного вызова библиотека записывает результаты в переданные переменные. В месте вызова дополнительный & не ставят. Начальные null нужны как удобные пустые значения перед вызовом, после него offset станет массивом, similarity — числом.
Точная сигнатура проверяемой Imagick 3.8.1
public function subimageMatch(Imagick $image, &$offset = null, &$similarity = null, float $threshold = 0.0, int $metric = 0): Imagick| Часть вызова | Практический смысл |
|---|---|
| $image: Imagick | Маленький шаблон, который ищут в текущем изображении. Здесь 40 × 24, одинаковые ориентация, масштаб и RGB-основа. |
| &$offset | Ассоциативный массив x, y, width, height. x, y — левый верхний угол найденного шаблона в том изображении, на котором вызван метод, не центр объекта. |
| &$similarity | Числовая оценка метрики. При явно выбранной RMSE меньшая ошибка лучше, 0 означает точное совпадение. Это не вероятность и не процент уверенности. |
| $threshold: float, default 0.0 | Порог ранней остановки вычислений. Он не заменяет собственное решение приложения «принять/отвергнуть». Для текущей реализации и неотрицательной RMSE используем −1.0 только в ограниченном учебном поиске, чтобы получить полную карту. |
| $metric: int | В stub по умолчанию 0, но фактическая IM7-обёртка при пропущенном аргументе начинает с RootMeanSquaredErrorMetric. В примерах явно указана METRIC_ROOTMEANSQUAREDERROR, чтобы смысл оценки не зависел от этого различия. |
| Возвращённый Imagick | Новый объект карты соответствия, не сам найденный фрагмент и не bool. Исходное изображение не заменяется картой. |
| Ошибки | Пустые изображения, слишком большой шаблон и ошибка вычисления могут вызвать ImagickException. Ошибка чтения пути происходит до поиска. |
Официальная доступность: PECL imagick 3 >= 3.3.0. Точная обёртка включается с ImageMagick 6.5.2. Дополнительные threshold/metric описаны здесь по исходникам Imagick 3.8.1; на IM6 в этой обёртке они не передаются старому MagickSimilarityImage. Не переносите поведение пятиаргументного IM7-вызова на любую старую сборку.
Сохранённый официальный пример и его неточности
Оригинал создаёт копию, вырезает 40 × 40 в (250, 110), добавляет виньетку и вызывает поиск. Возвращённую карту кладёт в $comparison, задаёт ей PNG, но затем выводит $imagick->getImageBlob() — байты исходного объекта. Поэтому этот текст сам по себе не показывает полученную карту. Для вывода карты нужен $comparison->getImageBlob() после согласования формата и Content-Type. В описании параметра similarity тоже ошибочно сказано о новом изображении: фактическая обёртка записывает туда float, а изображение возвращает через return. Раздел ReturnValues в XML пуст. Все эти исходные части сохранены ниже, а наши пояснения и рабочие варианты отделены от них.
Полный официальный XML и оригинальный пример без сокращений
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<refentry xml:id="imagick.subimagematch" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<refnamediv>
<refname>Imagick::subImageMatch</refname>
<refpurpose>Searches for a subimage in the current image and returns a similarity image</refpurpose>
</refnamediv>
<refsect1 role="description">
&reftitle.description;
<methodsynopsis>
<modifier>public</modifier> <type>Imagick</type><methodname>Imagick::subImageMatch</methodname>
<methodparam><type>Imagick</type><parameter>Imagick</parameter></methodparam>
<methodparam choice="opt"><type>array</type><parameter role="reference">offset</parameter></methodparam>
<methodparam choice="opt"><type>float</type><parameter role="reference">similarity</parameter></methodparam>
</methodsynopsis>
<para>
Searches for a subimage in the current image and returns a similarity image such that an exact match location is completely white and if none of the pixels match, black, otherwise some gray level in-between.
You can also pass in the optional parameters bestMatch and similarity. After calling the function similarity will be set to the 'score' of the similarity between the subimage and the matching position in the larger image, bestMatch will contain an associative array with elements x, y, width, height that describe the matching region.
</para>
</refsect1>
<refsect1 role="parameters">
&reftitle.parameters;
<variablelist>
<varlistentry>
<term><parameter>Imagick</parameter></term>
<listitem>
<para>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><parameter>offset</parameter></term>
<listitem>
<para>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><parameter>similarity</parameter></term>
<listitem>
<para>
A new image that displays the amount of similarity at each pixel.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 role="returnvalues">
&reftitle.returnvalues;
<para>
</para>
</refsect1>
<refsect1 role="examples">
&reftitle.examples;
<para>
<example>
<title> <function>Imagick::subImageMatch</function></title>
<programlisting role="php">
<![CDATA[
<?php
function subImageMatch($imagePath) {
$imagick = new \Imagick(realpath($imagePath));
$imagick2 = clone $imagick;
$imagick2->cropimage(40, 40, 250, 110);
$imagick2->vignetteimage(0, 1, 3, 3);
$similarity = null;
$bestMatch = null;
$comparison = $imagick->subImageMatch($imagick2, $bestMatch, $similarity);
$comparison->setImageFormat('png');
header("Content-Type: image/png");
echo $imagick->getImageBlob();
}
?>
]]>
</programlisting>
</example>
</para>
</refsect1>
</refentry>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->
Ограничение области поиска сокращает число проверяемых положений и помогает уложиться в разумную работу с известной зоной кадра. Полный перебор большого изображения с большим шаблоном может быть дорогим: на каждое положение приходится сравнение многих пикселей. Не переносите скорость этого 160 × 128 примера на тысячи полноразмерных фотографий. Сначала используйте знания о расположении, измерьте свою нагрузку и сохраняйте исходник. Если нужен поиск масштаба, поворота или смыслового объекта, выберите соответствующий алгоритм, а не произвольный fuzz.
Упражнение: перенесите начало области поиска из (40, 100) в (60, 120), сохранив известный шаблон 80, 160. Предлагаемое решение: новая локальная позиция будет 20, 40, а глобальная останется 80, 160; прибавляйте именно актуальное смещение. Проверьте, что вся область и весь шаблон остаются внутри исходника. Само изменение координат области не должно менять размер или масштаб шаблона.
Полный helper собственного входа и поясняющих панелей
<?php
declare(strict_types=1);
function subimageRestore():void {$raw=base64_decode((string)file_get_contents(__DIR__.'/source.png.b64'),true);if($raw===false||hash('sha256',$raw)!=='f51e5780445e0f5130a7f23112b2a00618035afd5545b8e3042c7ccff09b617c'||file_put_contents(__DIR__.'/source.png',$raw)===false){throw new RuntimeException('Own source missing or changed');}}
function subimageHash(Imagick $image):string {return hash('sha256',json_encode($image->exportImagePixels(0,0,$image->getImageWidth(),$image->getImageHeight(),'RGBA',Imagick::PIXEL_CHAR),JSON_THROW_ON_ERROR));}
function subimagePixels(Imagick $image):array {return ['width'=>$image->getImageWidth(),'height'=>$image->getImageHeight(),'RGBA8_hash'=>subimageHash($image)];}
function subimageOpaque(Imagick $image):Imagick {$view=new Imagick();$view->newImage($image->getImageWidth(),$image->getImageHeight(),'white');$view->compositeImage($image,Imagick::COMPOSITE_OVER,0,0);$view->setImageType(Imagick::IMGTYPE_TRUECOLOR);return $view;}
function subimageView(Imagick $image,string $filename):void {$view=subimageOpaque($image);$view->setImageFormat('png');$view->setImageDepth(8);$view->setOption('png:bit-depth','8');$view->setOption('png:color-type','2');$view->setOption('png:exclude-chunk','all');if(!$view->writeImage(__DIR__.'/'.$filename)){throw new RuntimeException('PNG preview write failed');}}
function subimageParts():Imagick {
$source=new Imagick(__DIR__.'/source.png');$base=new Imagick();$base->newImage(400,320,'white');$base->compositeImage($source,Imagick::COMPOSITE_OVER,0,0);$base->setImagePage(0,0,0,0);return $base;
}
function subimageTemplatePanel(Imagick $template):Imagick {if($template->getImageWidth()!==40||$template->getImageHeight()!==24){throw new RuntimeException('Expected the actual40x24 template');}$display=clone $template;$display->resizeImage(160,96,Imagick::FILTER_POINT,1.0);$display->setImagePage(0,0,0,0);$panel=new Imagick();$panel->newImage(400,320,'white');$panel->compositeImage($display,Imagick::COMPOSITE_OVER,120,112);return $panel;}
function subimageMapPanel(Imagick $map):Imagick {if($map->getImageWidth()!==160||$map->getImageHeight()!==128){throw new RuntimeException('Expected the actual160x128ROI map');}$display=clone $map;$display->resizeImage(320,256,Imagick::FILTER_POINT,1.0);$display->setImagePage(0,0,0,0);$panel=new Imagick();$panel->newImage(400,320,'white');$panel->compositeImage($display,Imagick::COMPOSITE_OVER,40,32);return $panel;}
function subimageCompare(array $images,string $filename):array {$board=new Imagick();$board->newImage(400*count($images),320,'white');$rects=[];foreach($images as $i=>$image){if($image->getImageWidth()!==400||$image->getImageHeight()!==320){throw new RuntimeException('Expected a complete 400x320 panel');}$board->compositeImage($image,Imagick::COMPOSITE_OVER,400*$i,0);$rects[]=['x'=>400*$i,'y'=>0,'width'=>400,'height'=>320];}subimageView($board,$filename);return $rects;}
function subimageDiagnostic(string $id,array $data):void {$doc=['schema'=>'aen-subimage-match-program-diagnostics-v1','example_id'=>$id]+$data;if(file_put_contents(__DIR__.'/metrics.json',json_encode($doc,JSON_PRETTY_PRINT|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR)."\n")===false){throw new RuntimeException('Diagnostic write failed');}}
Helper не содержит вызова subImageMatch и не подменяет его результат: он восстанавливает PNG, готовит белую основу, сохраняет RGB8 и создаёт явно описанные просмотры. В веб-ответе отдавайте нужный PNG отдельно с Content-Type: image/png; текстовую диагностику и байты карты не смешивайте. Далее: области и виртуальная страница, рисование и композиция, цветовые модели и исключения.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
Imagick::subImageMatchКоординаты известного фрагмента, ошибка RMSE и отдельная карта соответствия
PECL imagick 3 >= 3.3.0 · imagick
Imagick::subImageMatch(Imagick $Imagick, array &$offset = ?, float &$similarity = ?): ImagicksubImageMatch ищет положение маленького известного изображения внутри текущего. Это полезно для контроля известной метки на изображении или проверки фиксированного элемента в скриншоте при одинаковом масштабе. Метод возвращает лучший кандидат даже при плохом соответствии; наличие координат само по себе не означает, что нужная метка найдена.
Официальная базовая сигнатура
Imagick::subImageMatch(Imagick $Imagick, array &$offset = ?, float &$similarity = ?): ImagickВ официальной странице описаны три аргумента, первый называется $Imagick. Параметры $offset и $similarity передаются по ссылке: после успешного вызова библиотека записывает результаты в переданные переменные. В месте вызова дополнительный & не ставят. Начальные null нужны как удобные пустые значения перед вызовом, после него offset станет массивом, similarity — числом.
Точная сигнатура проверяемой Imagick 3.8.1
public function subimageMatch(Imagick $image, &$offset = null, &$similarity = null, float $threshold = 0.0, int $metric = 0): Imagick| Часть вызова | Практический смысл |
|---|---|
| $image: Imagick | Маленький шаблон, который ищут в текущем изображении. Здесь 40 × 24, одинаковые ориентация, масштаб и RGB-основа. |
| &$offset | Ассоциативный массив x, y, width, height. x, y — левый верхний угол найденного шаблона в том изображении, на котором вызван метод, не центр объекта. |
| &$similarity | Числовая оценка метрики. При явно выбранной RMSE меньшая ошибка лучше, 0 означает точное совпадение. Это не вероятность и не процент уверенности. |
| $threshold: float, default 0.0 | Порог ранней остановки вычислений. Он не заменяет собственное решение приложения «принять/отвергнуть». Для текущей реализации и неотрицательной RMSE используем −1.0 только в ограниченном учебном поиске, чтобы получить полную карту. |
| $metric: int | В stub по умолчанию 0, но фактическая IM7-обёртка при пропущенном аргументе начинает с RootMeanSquaredErrorMetric. В примерах явно указана METRIC_ROOTMEANSQUAREDERROR, чтобы смысл оценки не зависел от этого различия. |
| Возвращённый Imagick | Новый объект карты соответствия, не сам найденный фрагмент и не bool. Исходное изображение не заменяется картой. |
| Ошибки | Пустые изображения, слишком большой шаблон и ошибка вычисления могут вызвать ImagickException. Ошибка чтения пути происходит до поиска. |
Официальная доступность: PECL imagick 3 >= 3.3.0. Точная обёртка включается с ImageMagick 6.5.2. Дополнительные threshold/metric описаны здесь по исходникам Imagick 3.8.1; на IM6 в этой обёртке они не передаются старому MagickSimilarityImage. Не переносите поведение пятиаргументного IM7-вызова на любую старую сборку.
Сохранённый официальный пример и его неточности
Оригинал создаёт копию, вырезает 40 × 40 в (250, 110), добавляет виньетку и вызывает поиск. Возвращённую карту кладёт в $comparison, задаёт ей PNG, но затем выводит $imagick->getImageBlob() — байты исходного объекта. Поэтому этот текст сам по себе не показывает полученную карту. Для вывода карты нужен $comparison->getImageBlob() после согласования формата и Content-Type. В описании параметра similarity тоже ошибочно сказано о новом изображении: фактическая обёртка записывает туда float, а изображение возвращает через return. Раздел ReturnValues в XML пуст. Все эти исходные части сохранены ниже, а наши пояснения и рабочие варианты отделены от них.
Полный официальный XML и оригинальный пример без сокращений
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<refentry xml:id="imagick.subimagematch" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<refnamediv>
<refname>Imagick::subImageMatch</refname>
<refpurpose>Searches for a subimage in the current image and returns a similarity image</refpurpose>
</refnamediv>
<refsect1 role="description">
&reftitle.description;
<methodsynopsis>
<modifier>public</modifier> <type>Imagick</type><methodname>Imagick::subImageMatch</methodname>
<methodparam><type>Imagick</type><parameter>Imagick</parameter></methodparam>
<methodparam choice="opt"><type>array</type><parameter role="reference">offset</parameter></methodparam>
<methodparam choice="opt"><type>float</type><parameter role="reference">similarity</parameter></methodparam>
</methodsynopsis>
<para>
Searches for a subimage in the current image and returns a similarity image such that an exact match location is completely white and if none of the pixels match, black, otherwise some gray level in-between.
You can also pass in the optional parameters bestMatch and similarity. After calling the function similarity will be set to the 'score' of the similarity between the subimage and the matching position in the larger image, bestMatch will contain an associative array with elements x, y, width, height that describe the matching region.
</para>
</refsect1>
<refsect1 role="parameters">
&reftitle.parameters;
<variablelist>
<varlistentry>
<term><parameter>Imagick</parameter></term>
<listitem>
<para>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><parameter>offset</parameter></term>
<listitem>
<para>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><parameter>similarity</parameter></term>
<listitem>
<para>
A new image that displays the amount of similarity at each pixel.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 role="returnvalues">
&reftitle.returnvalues;
<para>
</para>
</refsect1>
<refsect1 role="examples">
&reftitle.examples;
<para>
<example>
<title> <function>Imagick::subImageMatch</function></title>
<programlisting role="php">
<![CDATA[
<?php
function subImageMatch($imagePath) {
$imagick = new \Imagick(realpath($imagePath));
$imagick2 = clone $imagick;
$imagick2->cropimage(40, 40, 250, 110);
$imagick2->vignetteimage(0, 1, 3, 3);
$similarity = null;
$bestMatch = null;
$comparison = $imagick->subImageMatch($imagick2, $bestMatch, $similarity);
$comparison->setImageFormat('png');
header("Content-Type: image/png");
echo $imagick->getImageBlob();
}
?>
]]>
</programlisting>
</example>
</para>
</refsect1>
</refentry>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->
Получить координаты одной точной копии и отметить её
Минимальный вызов нашёл точный фрагмент: локальные 40, 60, глобальные 80, 160, RMSE=0. Возвращённый объект карты сохранён отдельно для диагностики; из-за ранней остановки threshold0 его нельзя показывать как полную карту всех положений. Видимый результат этого примера — рамка на исходном изображении.
<?php
declare(strict_types=1);
require __DIR__.'/subimage-fixture.php';
subimageRestore();
if (!method_exists(Imagick::class, 'subImageMatch')) { throw new RuntimeException('This example needs subImageMatch'); }
$base = subimageParts();
$region = clone $base;
$region->cropImage(160, 128, 40, 100);
$region->setImagePage(0, 0, 0, 0);
$template = clone $base;
$template->cropImage(40, 24, 80, 160);
$template->setImagePage(0, 0, 0, 0);
$offset = null; $score = null;
$map = $region->subImageMatch($template, $offset, $score, 0.0,
Imagick::METRIC_ROOTMEANSQUAREDERROR);
$global = $offset; $global['x'] += 40; $global['y'] += 100;
$marked = clone $base;
$outline = new ImagickDraw(); $outline->setFillColor('none');
$outline->setStrokeColor('#c72b32'); $outline->setStrokeWidth(2);
$outline->rectangle($global['x'], $global['y'],
$global['x']+$global['width']-1, $global['y']+$global['height']-1);
$marked->drawImage($outline);
subimageView($marked, 'subimage-minimal.png');
// Saved as a diagnostic only: threshold0 can leave parts of this map uncomputed.
subimageView($map, 'partial-map.png');
subimageDiagnostic('subimage-match-minimal', ['executed_api'=>'subImageMatch',
'threshold'=>0.0,'metric'=>Imagick::METRIC_ROOTMEANSQUAREDERROR,
'offset'=>$offset,'global_offset'=>$global,'score'=>$score,
'map_size'=>[$map->getImageWidth(),$map->getImageHeight()],
'full_map_claimed'=>false,'checks'=>['map_returned'=>$map instanceof Imagick,
'exact_match'=>$score===0.0,'correct_local_position'=>$offset===['x'=>40,'y'=>60,'width'=>40,'height'=>24],
'correct_global_position'=>$global===['x'=>80,'y'=>160,'width'=>40,'height'=>24],
'actual_map_size'=>$map->getImageWidth()===160&&$map->getImageHeight()===128]]);
echo "subimage-minimal.png\npartial-map.png\nmetrics.json\nlocal=40,60 global=80,160 RMSE=0.000000 full-map-claimed=0\n";
Минимальный вызов нашёл точный фрагмент: локальные 40, 60, глобальные 80, 160, RMSE=0. Возвращённый объект карты сохранён отдельно для диагностики; из-за ранней остановки threshold0 его нельзя показывать как полную карту всех положений. Видимый результат этого примера — рамка на исходном изображении. Архив содержит example.php, source.png.b64 и subimage-fixture.php. Сохраните рядом, выполните php example.php с Imagick, поддержкой PNG и правом записи. Helper восстанавливает собственный PNG по SHA и накладывает на белую основу 400 × 320; вырезание области и сам subImageMatch видны в программе. Все три самостоятельные программы выполнены в указанной сборке. Значения конкретного примера не являются обещанием одинаковой оценки для произвольных фотографий.
Ожидаемый вывод
subimage-minimal.png
partial-map.png
metrics.json
local=40,60 global=80,160 RMSE=0.000000 full-map-claimed=0
Отклонить непохожий шаблон и перехватить слишком большой
Непохожий пурпурный шаблон всё равно получил ближайшие координаты 102, 41 внутри области поиска. RMSE≈0.473729 не проходит строгую политику точных копий ≤1e−9, поэтому рамка на исходнике не нарисована. Шаблон 161 × 129 больше области 160 × 128 и вызывает настоящую ImagickException. Исходные пиксели сохранены.
<?php
declare(strict_types=1);
require __DIR__.'/subimage-fixture.php';
subimageRestore();
if (!method_exists(Imagick::class, 'subImageMatch')) { throw new RuntimeException('This example needs subImageMatch'); }
$base = subimageParts();
$region = clone $base;
$region->cropImage(160, 128, 40, 100);
$region->setImagePage(0, 0, 0, 0);
$template = clone $base;
$template->cropImage(40, 24, 80, 160);
$template->setImagePage(0, 0, 0, 0);
function locateExactPatch(Imagick $region, Imagick $template): array {
$offset = null; $score = null;
$map = $region->subImageMatch($template, $offset, $score, -1.0,
Imagick::METRIC_ROOTMEANSQUAREDERROR);
$inside = $offset['x']>=0 && $offset['y']>=0
&& $offset['x']+$offset['width']<=$region->getImageWidth()
&& $offset['y']+$offset['height']<=$region->getImageHeight();
// The application's strict policy for exact copies, not a confidence percentage.
$accepted = is_finite($score) && $score <= 1e-9 && $inside;
return ['offset'=>$offset,'score'=>$score,'inside'=>$inside,
'accepted'=>$accepted,'map'=>$map];
}
$before = subimageHash($region); $sourceBefore = subimageHash($base);
$badTemplate = new Imagick(); $badTemplate->newImage(40,24,'magenta');
$result = locateExactPatch($region, $badTemplate);
$map = $result['map'];
$marked = clone $base;
if ($result['accepted']) {
$global = $result['offset']; $global['x']+=40; $global['y']+=100;
$outline = new ImagickDraw(); $outline->setFillColor('none');
$outline->setStrokeColor('#c72b32'); $outline->setStrokeWidth(2);
$outline->rectangle($global['x'],$global['y'],
$global['x']+$global['width']-1,$global['y']+$global['height']-1);
$marked->drawImage($outline);
}
$oversizeError='not caught';
$oversized=new Imagick(); $oversized->newImage(161,129,'magenta');
$unusedOffset=null; $unusedScore=null;
try { $region->subImageMatch($oversized,$unusedOffset,$unusedScore,0.0,
Imagick::METRIC_ROOTMEANSQUAREDERROR); }
catch (ImagickException $error) { $oversizeError=get_class($error); }
$unchanged=$before===subimageHash($region)&&$sourceBefore===subimageHash($base);
$templatePanel=subimageTemplatePanel($badTemplate); $mapPanel=subimageMapPanel($map);
subimageView($badTemplate,'bad-template.png');
subimageView($templatePanel,'bad-template-panel.png');
subimageView($marked,'rejected-context.png');
subimageView($map,'bad-similarity-map.png');
subimageView($mapPanel,'bad-map-panel.png');
$rects=subimageCompare([$templatePanel,$marked,$mapPanel],'subimage-workflow.png');
subimageDiagnostic('subimage-match-workflow',['executed_api'=>'subImageMatch',
'threshold'=>-1.0,'metric'=>Imagick::METRIC_ROOTMEANSQUAREDERROR,
'offset'=>$result['offset'],'score'=>$result['score'],'inside'=>$result['inside'],
'accepted'=>$result['accepted'],'acceptance_max_RMSE'=>1e-9,
'oversize_error'=>$oversizeError,'source_unchanged'=>$unchanged,
'map_size'=>[$map->getImageWidth(),$map->getImageHeight()],
'map_display'=>['zoom'=>2,'x'=>40,'y'=>32,'width'=>320,'height'=>256],
'panel_geometry'=>$rects,'checks'=>['bad_match_rejected'=>$result['accepted']===false,
'candidate_still_returned'=>isset($result['offset']['x'],$result['offset']['y']),
'candidate_inside'=>$result['inside']===true,'bad_score_exceeds_policy'=>$result['score']>1e-9,
'oversize_error_caught'=>$oversizeError==='ImagickException','source_unchanged'=>$unchanged]]);
echo "subimage-workflow.png\nbad-template.png\nbad-template-panel.png\nrejected-context.png\nbad-similarity-map.png\nbad-map-panel.png\nmetrics.json\n";
printf("candidate=%d,%d RMSE=%.6f accepted=%d oversize=%s source-unchanged=%d\n",
$result['offset']['x'],$result['offset']['y'],$result['score'],
(int)$result['accepted'],$oversizeError,(int)$unchanged);
Непохожий пурпурный шаблон всё равно получил ближайшие координаты 102, 41 внутри области поиска. RMSE≈0.473729 не проходит строгую политику точных копий ≤1e−9, поэтому рамка на исходнике не нарисована. Шаблон 161 × 129 больше области 160 × 128 и вызывает настоящую ImagickException. Исходные пиксели сохранены. Архив содержит example.php, source.png.b64 и subimage-fixture.php. Сохраните рядом, выполните php example.php с Imagick, поддержкой PNG и правом записи. Helper восстанавливает собственный PNG по SHA и накладывает на белую основу 400 × 320; вырезание области и сам subImageMatch видны в программе. Все три самостоятельные программы выполнены в указанной сборке. Значения конкретного примера не являются обещанием одинаковой оценки для произвольных фотографий.
Ожидаемый вывод
subimage-workflow.png
bad-template.png
bad-template-panel.png
rejected-context.png
bad-similarity-map.png
bad-map-panel.png
metrics.json
candidate=102,41 RMSE=0.473729 accepted=0 oversize=ImagickException source-unchanged=1
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


