我有两张图片(小图和大图)。其中一个包含另一个。像一张图像的东西是一张照片,另一张图片是这张照片所在的photoalbum页面的图片。我希望你明白我说的话。
那么如何使用PHP获取大图像上的小图像的坐标(x,y)?
答案 0 :(得分:9)
您可以自行轻松完成,而不依赖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
频道有多少差异:< / p>
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
缓存$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);
对于他们的主循环我迭代了调整大小的宽度和高度的资源。然后,在添加到数组时,我将x
和y
加倍,给出以下内容:
array_push($potentialPositions, array("x" => $x * 2, "y" => $y * 2, "error" => $error));
其余的代码保持不变,因为我们想要在真实尺寸的图片上进行精确定位。您所要做的就是在最后添加:
imagedestroy($smallresized);
imagedestroy($largeresized);
使用此版本的代码,使用google图片结果,我有:
Found 18 potential positions
Most around 720,440
Found 17 refined positions
Most likely at 718,432
in 11.499078989029 seconds
性能提升4倍!
希望这有帮助
答案 1 :(得分:2)
使用 ImageMagick 。
此页面将为您提供答案:How can I detect / calculate if a small pictures is present inside a bigger picture?