4

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

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

4

2 回答 2

11

自己做很容易,不依赖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我在这里所做的是遍历小图片和大图片中的“窗口”,检查,greenblue通道上有多少差异:

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 分钟的时间来执行,我们应该能够大大改善这个时间。

一个不成功的尝试是缓存RGBfrom$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

现在,我的测试用例有点太简单了……我把它改成了谷歌图片搜索:

谷歌图片搜索

寻找这张照片(它位于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 加倍xy给出以下内容:

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倍性能提升!

希望这可以帮助

于 2013-01-17T15:46:04.343 回答
2

使用ImageMagick

本页将为您解答:如何检测/计算大图片中是否存在小图片?

于 2013-01-15T20:08:59.660 回答