通过PHP在另一个图像中的大小和位置 [英] Size and position of one image in another via PHP

查看:150
本文介绍了通过PHP在另一个图像中的大小和位置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have two images(small and big). Big one contains a small one. Like if the small one is a photo and a big one is a page from the photo album.

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

How do I get coordinates of that small image in the big one using PHP? And also I need to know the size of that image in big one...so just a(x,y) coordinate of any angle and sizes of sides of that presentation of the small image...

(x,y,宽度,高度)

(x,y, width, height)

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

I've already asked the question like that and got a brilliant answer (here) but I've forgot to mention over there that the size of a small image could be different from the the size of that image in the big image...

并且如果有可能处理该图像的演示文稿大图像中的小图像可以覆盖其中一个角度...就像在这个示例中:

And also if it is possible to deal with a presentation of that small image in the big image can have something covering one of its angles... Like in this example:

小图像:

Small image:

大图:

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

Small image always has just a rectangular shape.

推荐答案

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

Alright, this answer does not perfectly answer the question, but it should give you a good start! I know I repeat myself in the code, but my goal was simply to get something working so you can build on it, this isn't production code!

前提条件

从大图开始:

我们需要尽可能找到这张照片的位置:

We need to find as best as possible the position of this other picture:

我决定将这个过程分解为许多子步骤,你可以根据你想要的代码来改进或删除它。

I decided to break the process into many substeps, which you could improve or remove depending on what you want the code to do.

For为了测试目的,我在不同的输入图像上测试了我的算法,所以你会看到一个变量来定义要加载的文件...

For testing purposes, I did test my algorithm on different input images so you'll see a variable defining what file to load...

我们从:

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

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

To have a good idea on our performances. Luckily, most of the algorithm was pretty fast so I didn't have to optimize more.

背景检测

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

I started by detecting the background color. I assumed that the background color would be the color most present in the picture. To do this, I only counted how many references of each color I could find in the large picture, sort it with decending values and took the first one as the background color (should allow the code to be adaptable if you changed the source pictures)

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

分区

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

My first step was to try to find all the regions where non background pixels were. With a little padding, I was able to group regions into bigger regions (so that a paragraph would be a single region instead of multiple individual letters). I started with a padding of 5 and got good enough results so I stuck with it.

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

This is broken into multiple function calls, so here we go:

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 这个像素。

Here, we iterate on every pixel of the picture, if its background color, we discard it, otherwise, we check if its position is already present in a region we found, if that's the case, we skip it too. Now, if we didn't skip the pixel, it means that it's a colored pixel that should be part of a region, so we start ExpandRegionFrom this pixel.

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

The code to check if we're inside a region is pretty simple:

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

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

Now, the expanding code will try to grow the region in each direction and will do so as long as it found new pixels to add to the region:

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 方法可能是用更干净的方式写的,但是我用快速的原型去了:

The ShouldExpand methods could have been written in a cleaner fashion, but I went for something fast to prototype with:

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

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

Now, to see if the algorithm was succesful, I added some debug code.

调试渲染

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

I created a second image to store debug info and store it on disk so I could later see my progress.

使用以下代码:

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

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

I could validate that my partitioning code was doing a decent job:

宽高比

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

I decided to filter out some regions based on aspect ratio (the ratio between the width and the height). Other filtering could be applied such as average pixel color or something, but the aspect ratio check was very fast so I used it.

我只是定义了一个窗口,其中区域将保留,如果他们的宽高比在最小值和最大值之间;

I simply defined a "window" where regions would be kept, if their aspect ration was between a minimum and maximum value;

$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 来电,我现在以蓝色绘制仍在列表中作为潜在位置的区域:

By adding the DrawRegions call, I now paint in blue the regions that are still in the list as potential positions:

如您所见,只剩下4个位置!

As you can see, only 4 position remains!

寻找角落

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

We're almost done! Now, what I'm doing is looking at the colors in the four corners from the small picture, and try to find the best matching pixel in the corners of the remaining regions. This code has the most potential to fail so if you have to invest time in improving the solution, this code would be a good candidate.

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

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

I tried to find the matching color by increasing "radially" (its basically squares) from the corners until I find a matching pixel (within a tolerance):

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频道,哎呀!

As you can see, I'm no PHP expert, I didn't know there was a built in function to get the rgb channels, oops!

最后通话

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

So now that the algorithm ran, let's see what it found using the following code:

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

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

imagedestroy($large2);

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

The output (which is pretty close to the real solution):

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

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

Giving this picture (the rectangle between 108,380 and 867,827 is drawn in green)

希望这会有所帮助!

这篇关于通过PHP在另一个图像中的大小和位置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆