使用PHP确定一个图像在另一个图像中的位置

时间:2013-01-15 19:20:41

标签: php image coordinates

我有两张图片(小图和大图)。其中一个包含另一个。像一张图像的东西是一张照片,另一张图片是这张照片所在的photoalbum页面的图片。我希望你明白我说的话。

那么如何使用PHP获取大图像上的小图像的坐标(x,y)?

2 个答案:

答案 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";

这里的目标是找出像素的相似程度,如果它们有些相似,则保持潜在的位置。在这里,我迭代大图片的每个像素,这可能是一个优化点。

现在,这个错误来自哪里?

获得可能性

我在这里做的是迭代小图片和大图片中的“窗口”,检查redgreenblue频道有多少差异:< / 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"];
}

示例

鉴于以下两张图片:

Large

Small

您应该得到以下结果:

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功能,但这会加剧时间。

鉴于我们的计算是在几何尺度上,我们探索的像素越多,所需的时间就越长......因此解决方案是跳过许多像素以尽可能快地找到我们的图片,然后更准确区域在我们的位置。

我修改了错误函数,作为参数如何递增xy

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倍,因为我们会跳过xy值。

我还为主循环添加了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

现在,我的测试用例有点过于简单......我将其更改为谷歌图片搜索:

Google Image Search

正在寻找这张照片(它位于718,432

The Duck

考虑到更大的图片尺寸,我们可以期待更长的处理时间,但算法确实找到了正确位置的图片:

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);

对于他们的主循环我迭代了调整大小的宽度和高度的资源。然后,在添加到数组时,我将xy加倍,给出以下内容:

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?