2

私は2つの画像(小と大)を持っています。大きなものには小さなものが含まれています。小さい方が写真で、大きい方がフォトアルバムのページのように。

PHPを使用して、大きな画像でその小さな画像の座標を取得するにはどうすればよいですか? また、大きな画像のサイズを知る必要があります...小さな画像のプレゼンテーションの側面の角度とサイズの(x、y)座標だけです...

(x,y, 幅, 高さ)

私はすでにそのような質問をして、すばらしい答えを得ました (ここ) が、小さな画像のサイズが大きな画像のその画像のサイズと異なる可能性があることをそこに言及するのを忘れていました...

また、大きな画像でその小さな画像のプレゼンテーションを処理できる場合は、その角度の 1 つをカバーする何かを持つことができます...この例のように:

小さい画像: 小さな画像

大きな画像: 大きな画像

小さい画像は常に長方形の形をしています。

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

ここで、アルゴリズムが成功したかどうかを確認するために、デバッグ コードを追加しました。

レンダリングのデバッグ

2 番目のイメージを作成してデバッグ情報を保存し、それをディスクに保存して、後で進行状況を確認できるようにしました。

次のコードを使用します。

$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,380との間の長方形867,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 に答える