わかりました、この答えは質問に完全に答えるものではありませんが、良いスタートを切るはずです! コードの中で繰り返し言っていることはわかっていますが、私の目標は単に何かを機能させて、その上に構築できるようにすることでした。これは製品コードではありません!
前提条件
大きな画像から始めます:
この別の画像の位置を可能な限り見つける必要があります。
プロセスを多くのサブステップに分割することにしました。コードで何をしたいかによって、改善または削除できます。
テスト目的で、さまざまな入力画像でアルゴリズムをテストしたので、ロードするファイルを定義する変数が表示されます...
まず始めに:
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
は緑色で描かれています)
お役に立てれば!