通过PHP在另一个图像中的大小和位置

时间:2013-01-17 23:55:58

标签: php image coordinates boundary image-size

我有两张图片(小图和大图)。大一个包含一个小的。就好像小照片是一张照片,大照片是相册中的一页。

如何使用PHP获取大图像中小图像的坐标?而且我还需要知道大图像中图像的大小...所以只需要小图像呈现的任何角度和大小的(x,y)坐标...

(x,y,宽度,高度)

我已经问过这样的问题并得到了一个很好的答案(here)但是我忘了在那里提到一个小图像的大小可能与该图像的大小不同在大图中...

如果有可能处理大图像中的小图像的呈现可以覆盖其中一个角度......就像在这个例子中一样:

小图片: small image

大图: big image

小图片总是只有一个矩形。

2 个答案:

答案 0 :(得分:3)

好吧,这个答案没有完全回答这个问题,但它应该会给你一个良好的开端!我知道我在代码中重复了一遍,但我的目标只是让一些工作变得有效,这样你就可以在它上面构建,这不是生产代码!

<强>前提条件

从大图开始:

Large

我们需要尽可能找到其他图片的位置:

enter image description here

我决定将这个过程分解为许多子步骤,根据您希望代码执行的操作,您可以改进或删除这些子步骤。

出于测试目的,我在不同的输入图像上测试了我的算法,因此您将看到一个变量来定义要加载的文件...

我们从:

开始
function microtime_float()
{
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}

$time_start = microtime_float();

$largeFilename = "large.jpg";

$small = imagecreatefromjpeg("small.jpg");
$large = imagecreatefromjpeg($largeFilename);

imagedestroy($small);
imagedestroy($large);

$time_end = microtime_float();
echo "in " . ($time_end - $time_start) . " seconds\n";

对我们的表现有个好主意。幸运的是,大多数算法都非常快,所以我没有必要进行更多优化。

背景检测

我开始检测背景颜色。我假设背景颜色是图片中最常出现的颜色。为此,我只计算了我在大图片中可以找到的每种颜色的引用数量,使用降序值对其进行排序,并将第一个作为背景颜色(如果您更改了源图片,则应允许代码适应)

function FindBackgroundColor($image)
{
    // assume that the color that's present the most is the background color
    $colorRefcount = array();

    $width = imagesx($image);
    $height = imagesy($image);

    for($x = 0; $x < $width; ++$x)
    {
        for($y = 0; $y < $height; ++$y)
        {
            $color = imagecolorat($image, $x, $y);
            if(isset($colorRefcount[$color]))
                $colorRefcount[$color] = $colorRefcount[$color] + 1;
            else
                $colorRefcount[$color] = 1;
        }
    }

    arsort($colorRefcount);
    reset($colorRefcount);

    return key($colorRefcount);
}
$background = FindBackgroundColor($large); // Should be white

<强> Partitionning

我的第一步是尝试找到非背景像素所在的所有区域。通过一点填充,我能够将区域分组到更大的区域(因此段落将是单个区域而不是多个单独的字母)。我从5的填充开始,得到了足够好的结果,所以我坚持使用它。

这被分成多个函数调用,所以我们在这里:

function FindRegions($image, $backgroundColor, $padding)
{
    // Find all regions within image where colors are != backgroundColor, including a padding so that adjacent regions are merged together
    $width = imagesx($image);
    $height = imagesy($image);

    $regions = array();

    for($x = 0; $x < $width; ++$x)
    {
        for($y = 0; $y < $height; ++$y)
        {
            $color = imagecolorat($image, $x, $y);

            if($color == $backgroundColor)
            {
                continue;
            }

            if(IsInsideRegions($regions, $x, $y))
            {
                continue;
            }

            $region = ExpandRegionFrom($image, $x, $y, $backgroundColor, $padding);
            array_push($regions, $region);
        }
    }

    return $regions;
}

$regions = FindRegions($large, $background, 5);

在这里,我们迭代图片的每个像素,如果它的背景颜色,我们丢弃它,否则,我们检查它的位置是否已经存在于我们找到的区域中,如果是这样,我们跳过它也。现在,如果我们没有跳过像素,则意味着它应该是区域的一部分的彩色像素,所以我们开始ExpandRegionFrom这个像素。

检查我们是否在某个地区内的代码非常简单:

function IsInsideRegions($regions, $x, $y)
{
    foreach($regions as $region)
    {
        if(($region["left"] <= $x && $region["right"] >= $x) && 
           ($region["bottom"] <= $y && $region["top"] >= $y))
        {
            return true;
        }
    }
    return false;
}

现在,扩展代码将尝试在每个方向上增加区域,只要它找到要添加到该区域的新像素,就会这样做:

function ExpandRegionFrom($image, $x, $y, $backgroundColor, $padding)
{
    $width = imagesx($image);
    $height = imagesy($image);

    $left = $x;
    $bottom = $y;
    $right = $x + 1;
    $top = $y + 1;

    $expanded = false;

    do
    {
        $expanded = false;

        $newLeft = ShouldExpandLeft($image, $backgroundColor, $left, $bottom, $top, $padding);
        if($newLeft != $left)
        {
            $left = $newLeft;
            $expanded = true;
        }

        $newRight = ShouldExpandRight($image, $backgroundColor, $right, $bottom, $top, $width, $padding);
        if($newRight != $right)
        {
            $right = $newRight;
            $expanded = true;
        }

        $newTop = ShouldExpandTop($image, $backgroundColor, $top, $left, $right, $height, $padding);
        if($newTop != $top)
        {
            $top = $newTop;
            $expanded = true;
        }

        $newBottom = ShouldExpandBottom($image, $backgroundColor, $bottom, $left, $right, $padding);
        if($newBottom != $bottom)
        {
            $bottom = $newBottom;
            $expanded = true;
        }
    }
    while($expanded == true);

    $region = array();
    $region["left"] = $left;
    $region["bottom"] = $bottom;
    $region["right"] = $right;
    $region["top"] = $top;

    return $region;
}

ShouldExpand方法本来可以用更干净的方式编写,但是我快速找到了原型:

function ShouldExpandLeft($image, $background, $left, $bottom, $top, $padding)
{
    // Find the farthest pixel that is not $background starting at $left - $padding closing in to $left
    for($x = max(0, $left - $padding); $x < $left; ++$x)
    {
        for($y = $bottom; $y <= $top; ++$y)
        {
            $pixelColor = imagecolorat($image, $x, $y);

            if($pixelColor != $background) 
            {
                return $x;
            }
        }
    }

    return $left;
}

function ShouldExpandRight($image, $background, $right, $bottom, $top, $width, $padding)
{
    // Find the farthest pixel that is not $background starting at $right + $padding closing in to $right
    $from = min($width - 1, $right + $padding);
    $to = $right;
    for($x = $from; $x > $to; --$x)
    {
        for($y = $bottom; $y <= $top; ++$y)
        {
            $pixelColor = imagecolorat($image, $x, $y);

            if($pixelColor != $background) 
            {
                return $x;
            }
        }
    }

    return $right;
}

function ShouldExpandTop($image, $background, $top, $left, $right, $height, $padding)
{
    // Find the farthest pixel that is not $background starting at $top + $padding closing in to $top
    for($x = $left; $x <= $right; ++$x)
    {
        for($y = min($height - 1, $top + $padding); $y > $top; --$y)
        {
            $pixelColor = imagecolorat($image, $x, $y);

            if($pixelColor != $background)
            {
                return $y;
            }
        }
    }

    return $top;
}

function ShouldExpandBottom($image, $background, $bottom, $left, $right, $padding)
{
    // Find the farthest pixel that is not $background starting at $bottom - $padding closing in to $bottom
    for($x = $left; $x <= $right; ++$x)
    {
        for($y = max(0, $bottom - $padding); $y < $bottom; ++$y)
        {
            $pixelColor = imagecolorat($image, $x, $y);

            if($pixelColor != $background)
            {
                return $y;
            }
        }
    }

    return $bottom;
}

现在,为了查看算法是否成功,我添加了一些调试代码。

调试渲染

我创建了第二个图像来存储调试信息并将其存储在磁盘上,以便我以后可以看到我的进度。

使用以下代码:

$large2 = imagecreatefromjpeg($largeFilename);
$red = imagecolorallocate($large2, 255, 0, 0);
$green = imagecolorallocate($large2, 0, 255, 0);
$blue = imagecolorallocate($large2, 0, 0, 255);

function DrawRegions($image, $regions, $color)
{
    foreach($regions as $region)
    {
        imagerectangle($image, $region["left"], $region["bottom"], $region["right"], $region["top"], $color);
    }
}

DrawRegions($large2, $regions, $red);

imagejpeg($large2, "regions.jpg");

我可以验证我的分区代码做得不错:

Partitions

宽高比

我决定根据纵横比(宽度和高度之间的比率)过滤掉一些区域。可以应用其他过滤,例如平均像素颜色等,但是宽高比检查非常快,所以我使用它。

我只是定义了一个&#34;窗口&#34;如果区域比例在最小值和最大值之间,则保留区域;

$smallAspectRatio = imagesx($small) / imagesy($small);

function PruneOutWrongAspectRatio($regions, $minAspectRatio, $maxAspectRatio)
{
    $result = array();
    foreach($regions as $region)
    {   
        $aspectRatio = ($region["right"] - $region["left"]) / ($region["top"] - $region["bottom"]);
        if($aspectRatio >= $minAspectRatio && $aspectRatio <= $maxAspectRatio)
        {
            array_push($result, $region);
        }
    }

    return $result;
}

$filterOnAspectRatio = true;

if($filterOnAspectRatio == true)
{
    $regions = PruneOutWrongAspectRatio($regions, $smallAspectRatio - 0.1 * $smallAspectRatio, $smallAspectRatio + 0.1 * $smallAspectRatio);
    DrawRegions($large2, $regions, $blue);
}

imagejpeg($large2, "aspectratio.jpg");

通过添加DrawRegions调用,我现在以蓝色绘制仍在列表中的区域作为潜在位置:

Aspect Ratio

如你所见,只有4个位置!

寻找角落

我们差不多完成了!现在,我正在做的是从小图片中查看四个角落中的颜色,并尝试在剩余区域的角落中找到最佳匹配像素。这段代码最有可能失败,所以如果你不得不花时间去改进解决方案,那么这段代码将是一个很好的选择。

function FindCorners($large, $small, $regions)
{
    $result = array();

    $bottomLeftColor = imagecolorat($small, 0, 0);
    $blColors = GetColorComponents($bottomLeftColor);
    $bottomRightColor = imagecolorat($small, imagesx($small) - 1, 0);
    $brColors = GetColorComponents($bottomRightColor);
    $topLeftColor = imagecolorat($small, 0, imagesy($small) - 1);
    $tlColors = GetColorComponents($topLeftColor);
    $topRightColor = imagecolorat($small, imagesx($small) - 1, imagesy($small) - 1);
    $trColors = GetColorComponents($topRightColor);

    foreach($regions as $region)
    {
        $bottomLeft = null;
        $bottomRight = null;
        $topLeft = null;
        $topRight = null;

        $regionWidth = $region["right"] - $region["left"];
        $regionHeight = $region["top"] - $region["bottom"];

        $maxRadius = min($regionWidth, $regionHeight);

        $topLeft = RadialFindColor($large, $tlColors, $region["left"], $region["top"], 1, -1, $maxRadius);
        $topRight = RadialFindColor($large, $trColors, $region["right"], $region["top"], -1, -1, $maxRadius);
        $bottomLeft = RadialFindColor($large, $blColors, $region["left"], $region["bottom"], 1, 1, $maxRadius);
        $bottomRight = RadialFindColor($large, $brColors, $region["right"], $region["bottom"], -1, 1, $maxRadius);

        if($bottomLeft["found"] && $topRight["found"] && $topLeft["found"] && $bottomRight["found"])
        {
            $left = min($bottomLeft["x"], $topLeft["x"]);
            $right = max($bottomRight["x"], $topRight["x"]);
            $bottom = min($bottomLeft["y"], $bottomRight["y"]);
            $top = max($topLeft["y"], $topRight["y"]);
            array_push($result, array("left" => $left, "right" => $right, "bottom" => $bottom, "top" => $top));
        }
    }

    return $result;
}

$closeOnCorners = true;
if($closeOnCorners == true)
{
    $regions = FindCorners($large, $small, $regions);
    DrawRegions($large2, $regions, $green);
}

我试图通过增加&#34;径向&#34;来找到匹配的颜色。 (它基本上是正方形)从角落直到找到匹配的像素(在公差范围内):

function GetColorComponents($color)
{
    return array("red" => $color & 0xFF, "green" => ($color >> 8) & 0xFF, "blue" => ($color >> 16) & 0xFF);
}

function GetDistance($color, $r, $g, $b)
{
    $colors = GetColorComponents($color);

    return (abs($r - $colors["red"]) + abs($g - $colors["green"]) + abs($b - $colors["blue"]));
}

function RadialFindColor($large, $color, $startx, $starty, $xIncrement, $yIncrement, $maxRadius)
{
    $result = array("x" => -1, "y" => -1, "found" => false);
    $treshold = 40;
    for($r = 1; $r <= $maxRadius; ++$r)
    {
        $closest = array("x" => -1, "y" => -1, "distance" => 1000);
        for($i = 0; $i <= $r; ++$i)
        {
            $x = $startx + $i * $xIncrement;
            $y = $starty + $r * $yIncrement;

            $pixelColor = imagecolorat($large, $x, $y);

            $distance = GetDistance($pixelColor, $color["red"], $color["green"], $color["blue"]);
            if($distance < $treshold && $distance < $closest["distance"])
            {
                $closest["x"] = $x;
                $closest["y"] = $y;
                $closest["distance"] = $distance;
                break;
            }
        }

        for($i = 0; $i < $r; ++$i)
        {   
            $x = $startx + $r * $xIncrement;
            $y = $starty + $i * $yIncrement;

            $pixelColor = imagecolorat($large, $x, $y);

            $distance = GetDistance($pixelColor, $color["red"], $color["green"], $color["blue"]);
            if($distance < $treshold && $distance < $closest["distance"])
            {
                $closest["x"] = $x;
                $closest["y"] = $y;
                $closest["distance"] = $distance;

                break;
            }
        }

        if($closest["distance"] != 1000)
        {
            $result["x"] = $closest["x"];
            $result["y"] = $closest["y"];
            $result["found"] = true;
            return $result;
        }
    }

    return $result;
}

正如你所看到的,我不是PHP专家,我也不知道有一个内置功能来获取rgb频道,哎呀!

最后通话

现在算法运行了,让我们看看它使用以下代码找到了什么:

foreach($regions as $region)
{
    echo "Potentially between " . $region["left"] . "," . $region["bottom"] . " and " . $region["right"] . "," . $region["top"] . "\n";
}

imagejpeg($large2, "final.jpg");

imagedestroy($large2);

输出(非常接近真实解决方案):

Potentially between 108,380 and 867,827
in 7.9796848297119 seconds

给出这张照片(108,380867,827之间的矩形以绿色绘制)

Final

希望这有帮助!

答案 1 :(得分:0)

如果没有颜色(图像周围有白色和黑色,但您可以修改脚本以使其以不同方式工作),我的解决方案可以正常工作

    $width = imagesx($this->img_src);
    $height = imagesy($this->img_src);

    // navigate through pixels of image
    for ($y = 0; $y < $height; $y++) {
        for ($x=0; $x < $width; $x++) {
            list($r, $g, $b) = imagergbat($this->img_src, $x, $y);
            $black = 0.1;
            $white = 0.9;
            // calculate if the color is next to white or black, if not register it as a good pixel
            $gs = (($r / 3) + ($g / 3) + ($b / 3);
            $first_pixel = array();
            if ($gs > $white &&  $gs < $black) {
                // get coordinate of first pixel (left top)
                if (empty($first_pixel))
                    $first_pixel = array($x, $y);
                // And save last_pixel each time till the last one
                $last_pixel = array($x, $y);
            }
        }
    }

您可以获得图像的坐标。你必须在此之后裁剪它。