我有两个图像(小和大)。其中一个包含另一个。比如一张图片是一张照片,另一张是这张照片所在的相册页面的图片。我希望你明白我说的话。
那么如何使用 PHP 在大图像上获取小图像的坐标 (x,y)?
我有两个图像(小和大)。其中一个包含另一个。比如一张图片是一张照片,另一张是这张照片所在的相册页面的图片。我希望你明白我说的话。
那么如何使用 PHP 在大图像上获取小图像的坐标 (x,y)?
自己做很容易,不依赖gd
.
您需要注意的是,您很可能无法对每个像素进行简单的像素检查,因为过滤和压缩可能会稍微修改每个像素的值。
我在这里提出的代码很可能会很慢,如果性能是一个问题,你可以优化它或走捷径。希望代码能让您走上正确的轨道!
首先,让我们迭代我们的图片
$small = imagecreatefrompng("small.png");
$large = imagecreatefrompng("large.png");
$smallwidth = imagesx($small);
$smallheight = imagesy($small);
$largewidth = imagesx($large);
$largeheight = imagesy($large);
$foundX = -1;
$foundY = -1;
$keepThreshold = 20;
$potentialPositions = array();
for($x = 0; $x <= $largewidth - $smallwidth; ++$x)
{
for($y = 0; $y <= $largeheight - $smallheight; ++$y)
{
// Scan the whole picture
$error = GetImageErrorAt($large, $small, $x, $y);
if($error["avg"] < $keepThreshold)
{
array_push($potentialPositions, array("x" => $x, "y" => $y, "error" => $error));
}
}
}
imagedestroy($small);
imagedestroy($large);
echo "Found " . count($potentialPositions) . " potential positions\n";
这里的目标是找出像素的相似程度,如果它们有些相似,则保留潜在位置。在这里,我迭代了大图的每个像素,这可能是一个优化点。
现在,这个错误来自哪里?
获得可能性
red
我在这里所做的是遍历小图片和大图片中的“窗口”,检查,green
和blue
通道上有多少差异:
function GetImageErrorAt($haystack, $needle, $startX, $startY)
{
$error = array("red" => 0, "green" => 0, "blue" => 0, "avg" => 0);
$needleWidth = imagesx($needle);
$needleHeight = imagesy($needle);
for($x = 0; $x < $needleWidth; ++$x)
{
for($y = 0; $y < $needleHeight; ++$y)
{
$nrgb = imagecolorat($needle, $x, $y);
$hrgb = imagecolorat($haystack, $x + $startX, $y + $startY);
$nr = $nrgb & 0xFF;
$hr = $hrgb & 0xFF;
$error["red"] += abs($hr - $nr);
$ng = ($nrgb >> 8) & 0xFF;
$hg = ($hrgb >> 8) & 0xFF;
$error["green"] += abs($hg - $ng);
$nb = ($nrgb >> 16) & 0xFF;
$hb = ($hrgb >> 16) & 0xFF;
$error["blue"] += abs($hb - $nb);
}
}
$error["avg"] = ($error["red"] + $error["green"] + $error["blue"]) / ($needleWidth * $needleHeight);
return $error;
}
到目前为止,我们已经为大图片中可能包含小图片的每个“窗口”建立了一个潜在的错误值,如果它们看起来“足够好”,则将它们存储在一个数组中。
排序
现在,我们只需要对我们最好的匹配进行排序并保留最好的,它很可能是我们的小图片所在的位置:
function SortOnAvgError($a, $b)
{
if($a["error"]["avg"] == $b["error"]["avg"])
{
return 0;
}
return ($a["error"]["avg"] < $b["error"]["avg"]) ? -1 : 1;
}
if(count($potentialPositions) > 0)
{
usort($potentialPositions, "SortOnAvgError");
$mostLikely = $potentialPositions[0];
echo "Most likely at " . $mostLikely["x"] . "," . $mostLikely["y"];
}
例子
鉴于以下两张图片:
和
您应该有以下结果:
Found 5 potential positions
Most likely at 288,235
这与我们鸭子的位置完全对应。其他 4 个位置是上、下、左、右 1 个像素。
在为您完成一些优化工作后,我将编辑此条目,因为此代码对于大图像来说太慢了(PHP 的性能比我预期的还要差)。
编辑
首先,在做任何“优化”代码之前,我们需要数字,所以我添加了
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
$time_start = microtime_float();
和
$time_end = microtime_float();
echo "in " . ($time_end - $time_start) . " seconds\n";
最后有一个具体的想法是在算法过程中花费了多少时间。通过这种方式,我可以知道我的更改是改进了代码还是使代码变得更糟。鉴于当前包含这些图片的代码需要大约 45 分钟的时间来执行,我们应该能够大大改善这个时间。
一个不成功的尝试是缓存RGB
from$needle
以尝试加速GetImageErrorAt
功能,但它恶化了时间。
鉴于我们的计算是在几何尺度上进行的,我们探索的像素越多,花费的时间就越长......所以解决方案是跳过许多像素以尝试尽可能快地定位我们的图片,然后更准确地定位我们的立场。
我修改了错误函数以作为参数如何增加x
和y
function GetImageErrorAt($haystack, $needle, $startX, $startY, $increment)
{
$needleWidth = imagesx($needle);
$needleHeight = imagesy($needle);
$error = array("red" => 0, "green" => 0, "blue" => 0, "avg" => 0, "complete" => true);
for($x = 0; $x < $needleWidth; $x = $x + $increment)
{
for($y = 0; $y < $needleHeight; $y = $y + $increment)
{
$hrgb = imagecolorat($haystack, $x + $startX, $y + $startY);
$nrgb = imagecolorat($needle, $x, $y);
$nr = $nrgb & 0xFF;
$hr = $hrgb & 0xFF;
$ng = ($nrgb >> 8) & 0xFF;
$hg = ($hrgb >> 8) & 0xFF;
$nb = ($nrgb >> 16) & 0xFF;
$hb = ($hrgb >> 16) & 0xFF;
$error["red"] += abs($hr - $nr);
$error["green"] += abs($hg - $ng);
$error["blue"] += abs($hb - $nb);
}
}
$error["avg"] = ($error["red"] + $error["green"] + $error["blue"]) / ($needleWidth * $needleHeight);
return $error;
}
例如,传递2
将使函数返回速度快 4 倍,因为我们同时跳过x
和y
值。
我还stepSize
为主循环添加了一个:
$stepSize = 10;
for($x = 0; $x <= $largewidth - $smallwidth; $x = $x + $stepSize)
{
for($y = 0; $y <= $largeheight - $smallheight; $y = $y + $stepSize)
{
// Scan the whole picture
$error = GetImageErrorAt($large, $small, $x, $y, 2);
if($error["complete"] == true && $error["avg"] < $keepThreshold)
{
array_push($potentialPositions, array("x" => $x, "y" => $y, "error" => $error));
}
}
}
这样做,我能够以精确的代价将执行时间从 2657 秒减少到 7 秒。我增加了keepThreshold
以获得更多“潜在结果”。
现在我没有检查每个像素,我最好的答案是:
Found 8 potential positions
Most likely at 290,240
正如你所看到的,我们已经接近我们想要的位置,但它并不完全正确。
接下来我要做的是在这个“非常接近”的位置周围定义一个矩形,以探索stepSize
我们添加的每个像素。
我现在将脚本的下部更改为:
if(count($potentialPositions) > 0)
{
usort($potentialPositions, "SortOnAvgError");
$mostLikely = $potentialPositions[0];
echo "Most probably around " . $mostLikely["x"] . "," . $mostLikely["y"] . "\n";
$startX = $mostLikely["x"] - $stepSize + 1; // - $stepSize was already explored
$startY = $mostLikely["y"] - $stepSize + 1; // - $stepSize was already explored
$endX = $mostLikely["x"] + $stepSize - 1;
$endY = $mostLikely["y"] + $stepSize - 1;
$refinedPositions = array();
for($x = $startX; $x <= $endX; ++$x)
{
for($y = $startY; $y <= $endY; ++$y)
{
// Scan the whole picture
$error = GetImageErrorAt($large, $small, $x, $y, 1); // now check every pixel!
if($error["avg"] < $keepThreshold) // make the threshold smaller
{
array_push($refinedPositions, array("x" => $x, "y" => $y, "error" => $error));
}
}
}
echo "Found " . count($refinedPositions) . " refined positions\n";
if(count($refinedPositions))
{
usort($refinedPositions, "SortOnAvgError");
$mostLikely = $refinedPositions[0];
echo "Most likely at " . $mostLikely["x"] . "," . $mostLikely["y"] . "\n";
}
}
现在给了我这样的输出:
Found 8 potential positions
Most probably around 290,240
Checking between X 281 and 299
Checking between Y 231 and 249
Found 23 refined positions
Most likely at 288,235
in 13.960182189941 seconds
这确实是正确的答案,大约比初始脚本快 200 倍。
编辑 2
现在,我的测试用例有点太简单了……我把它改成了谷歌图片搜索:
寻找这张照片(它位于718,432
)
考虑到更大的图片尺寸,我们可以预期更长的处理时间,但算法确实在正确的位置找到了图片:
Found 123 potential positions
Most probably around 720,430
Found 17 refined positions
Most likely at 718,432
in 43.224536895752 seconds
编辑 3
我决定尝试我在评论中告诉你的选项,在执行查找之前按比例缩小图片,我得到了很好的结果。
我在第一个循环之前添加了这段代码:
$smallresizedwidth = $smallwidth / 2;
$smallresizedheight = $smallheight / 2;
$largeresizedwidth = $largewidth / 2;
$largeresizedheight = $largeheight / 2;
$smallresized = imagecreatetruecolor($smallresizedwidth, $smallresizedheight);
$largeresized = imagecreatetruecolor($largeresizedwidth, $largeresizedheight);
imagecopyresized($smallresized, $small, 0, 0, 0, 0, $smallresizedwidth, $smallresizedheight, $smallwidth, $smallheight);
imagecopyresized($largeresized, $large, 0, 0, 0, 0, $largeresizedwidth, $largeresizedheight, $largewidth, $largeheight);
对于他们的主循环,我使用调整后的宽度和高度对调整后的资产进行了迭代。然后,当添加到数组时,我将 and 加倍x
,y
给出以下内容:
array_push($potentialPositions, array("x" => $x * 2, "y" => $y * 2, "error" => $error));
其余代码保持不变,因为我们想要在真实大小的图片上进行精确定位。您所要做的就是在最后添加:
imagedestroy($smallresized);
imagedestroy($largeresized);
使用这个版本的代码和谷歌图像结果,我有:
Found 18 potential positions
Most around 720,440
Found 17 refined positions
Most likely at 718,432
in 11.499078989029 seconds
4倍性能提升!
希望这可以帮助
使用ImageMagick。
本页将为您解答:如何检测/计算大图片中是否存在小图片?