确定其他与PHP一个图像的位置(Determine position of one image in

2019-07-18 06:29发布

我有两个图像(大,小)。 其中一个包含另一个。 像一个形象的东西是照片,另外一个就是这张照片位于画册的页面的图片。 我希望你明白我说的话。

那么,如何得到一个小图像的坐标(X,Y)上使用PHP的大单?

Answer 1:

这是很容易做你自己,而无需依赖比其他外部库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通道:

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功能,但它恶化了时间。

鉴于我们的计算是对几何的规模,我们探索像素越多,时间越长......这样的解决方案是跳过多少像素,试图尽可能快地我们的图片,然后在更精确地定位防区我们的立场。

我修改了误差函数取作为参数如何递增的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

我决定尝试我告诉你,在注释的选项,在执行find之前按比例缩小的图片,我曾与它的伟大成果。

我第一个循环之前添加以下代码:

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

使用这个版本的代码,与谷歌图像结果,我有:

Found 18 potential positions
Most around 720,440
Found 17 refined positions
Most likely at 718,432
in 11.499078989029 seconds

性能提高4倍!

希望这可以帮助



Answer 2:

使用ImageMagick的

本页将给你回答: 我如何检测/计算,如果一个小图片是存在一个更大的图片里面?



文章来源: Determine position of one image in another with PHP