2

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

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

(x,y, 宽度, 高度)

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

而且,如果可以处理大图像中那个小图像的呈现,可以有一些东西覆盖它的一个角度......就像在这个例子中:

小图: 小图

大图: 大图

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

4

2 回答 2

4

好吧,这个答案并不能完美地回答这个问题,但它应该给你一个好的开始!我知道我在代码中重复了自己,但我的目标只是让某些东西正常工作,以便您可以在其上进行构建,这不是生产代码!

前提条件

从大图开始:

大的

我们需要尽可能地找到另一张图片的位置:

在此处输入图像描述

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

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

我们从:

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

分区

我的第一步是尝试找到所有非背景像素所在的区域。通过一点填充,我能够将区域分组为更大的区域(这样一个段落将是一个区域而不是多个单独的字母)。我从 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");

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

分区

纵横比

我决定根据纵横比(宽高比)过滤掉一些区域。可以应用其他过滤,例如平均像素颜色或其他东西,但纵横比检查非常快,所以我使用了它。

我只是定义了一个“窗口”,如果区域的纵横比在最小值和最大值之间,则将在其中保留区域;

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

纵横比

如您所见,只剩下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);
}

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

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绿色绘制)

最终的

希望这可以帮助!

于 2013-01-18T14:43:54.370 回答
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);
            }
        }
    }

你得到你的图像的坐标。在此之后你只需要裁剪它。

于 2013-01-18T09:08:13.567 回答