php - PHP를 사용하여 다른 이미지에서 한 이미지의 위치 결정

기사 출처 php image coordinates

두 개의 이미지 (작고 큰)가 있습니다. 그들 중 하나는 다른 하나를 포함합니다. 하나의 이미지와 같은 것은 사진이고 다른 하나는이 사진이있는 포토 앨범 페이지의 사진입니다. 내가 한 말을 이해 하셨기를 바랍니다.

그렇다면 PHP를 사용하여 큰 이미지에서 작은 이미지의 좌표 (x, y)를 어떻게 얻습니까?
gd 이외의 외부 라이브러리에 의존하지 않고 혼자서 수행하는 것이 매우 쉽습니다.

주의해야 할 점은 필터링 및 압축이 각 픽셀의 값을 약간 수정할 수 있으므로 픽셀 당 간단한 픽셀 검사를 수행 할 수 없다는 것입니다.

여기서 제안하는 코드는 성능이 문제가되는 경우 최적화하거나 바로 가기를 사용할 수 있으므로 느릴 가능성이 높습니다. 바라건대, 코드가 당신을 올바른 길로 인도하기를 바랍니다!

먼저 사진을 반복 해 보겠습니다.

$small = imagecreatefrompng("small.png");
$large = imagecreatefrompng("large.png");

$smallwidth = imagesx($small);
$smallheight = imagesy($small);

$largewidth = imagesx($large);
$largeheight = imagesy($large);

$foundX = -1;
$foundY = -1;

$keepThreshold = 20;

$potentialPositions = array();

for($x = 0; $x <= $largewidth - $smallwidth; ++$x)
{
    for($y = 0; $y <= $largeheight - $smallheight; ++$y)
    {
        // Scan the whole picture
        $error = GetImageErrorAt($large, $small, $x, $y);
        if($error["avg"] < $keepThreshold)
        {
            array_push($potentialPositions, array("x" => $x, "y" => $y, "error" => $error));
        }
    }
}

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

echo "Found " . count($potentialPositions) . " potential positions\n";


여기서의 목표는 픽셀이 얼마나 유사한 지 확인하고, 다소 유사한 경우 잠재적 위치를 유지하는 것입니다. 여기서는 큰 그림의 모든 픽셀을 반복합니다. 이것이 최적화 지점이 될 수 있습니다.

자,이 오류의 원인은 어디입니까?

가능성 얻기

내가 여기서 한 것은 작은 그림과 큰 그림의 "창"을 반복하여 red, greenblue 채널에 얼마나 많은 차이가 있는지 확인하는 것입니다.

function GetImageErrorAt($haystack, $needle, $startX, $startY)
{
    $error = array("red" => 0, "green" => 0, "blue" => 0, "avg" => 0);
    $needleWidth = imagesx($needle);
    $needleHeight = imagesy($needle);

    for($x = 0; $x < $needleWidth; ++$x)
    {
        for($y = 0; $y < $needleHeight; ++$y)
        {
            $nrgb = imagecolorat($needle, $x, $y);
            $hrgb = imagecolorat($haystack, $x + $startX, $y + $startY);

            $nr = $nrgb & 0xFF;
            $hr = $hrgb & 0xFF;

            $error["red"] += abs($hr - $nr);

            $ng = ($nrgb >> 8) & 0xFF;
            $hg = ($hrgb >> 8) & 0xFF;

            $error["green"] += abs($hg - $ng);

            $nb = ($nrgb >> 16) & 0xFF;
            $hb = ($hrgb >> 16) & 0xFF;

            $error["blue"] += abs($hb - $nb);
        }
    }
    $error["avg"] = ($error["red"] + $error["green"] + $error["blue"]) / ($needleWidth * $needleHeight);
    return $error;
}


지금까지 작은 그림을 포함 할 수있는 큰 그림의 모든 "창"에 대해 잠재적 인 오류 값을 설정하고 "충분히 양호"해 보이는 경우 배열에 저장했습니다.

정렬

이제 우리는 가장 좋은 일치를 정렬하고 가장 좋은 것을 유지하기 만하면됩니다. 작은 그림이있는 위치 일 가능성이 높습니다.

function SortOnAvgError($a, $b)
{
    if($a["error"]["avg"] == $b["error"]["avg"])
    {
        return 0;
    }
    return ($a["error"]["avg"] < $b["error"]["avg"]) ? -1 : 1;
}

if(count($potentialPositions) > 0)
{
    usort($potentialPositions, "SortOnAvgError");
    $mostLikely = $potentialPositions[0];
    echo "Most likely at " . $mostLikely["x"] . "," . $mostLikely["y"];
}




다음 두 사진이 주어집니다.







결과는 다음과 같습니다.

Found 5 potential positions
Most likely at 288,235


오리의 위치와 정확히 일치합니다. 다른 4 개의 위치는 위, 아래, 왼쪽 및 오른쪽 1 픽셀입니다.

이 코드는 큰 이미지에 비해 너무 느리기 때문에 몇 가지 최적화 작업을 마친 후에이 항목을 편집 할 것입니다 (PHP는 예상보다 성능이 훨씬 떨어졌습니다).

편집하다

먼저 코드를 "최적화"하기 전에 숫자가 필요하므로

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

$time_start = microtime_float();




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


알고리즘을 수행하는 동안 얼마나 많은 시간이 소요되는지에 대한 구체적인 아이디어를 얻을 수 있습니다. 이렇게하면 변경 사항이 개선되는지 또는 코드가 더 나빠지는지 알 수 있습니다. 이 그림이있는 현재 코드를 실행하는 데 45 분 정도 소요되므로이 시간을 상당히 개선 할 수있을 것입니다.

성공하지 못한 잠정적 인 방법은 RGB 기능을 가속화하기 위해 $needle에서 GetImageErrorAt를 캐시하는 것이었지만 시간이 더 나빠졌습니다.

계산이 기하학적 인 척도에 있다는 점을 감안할 때, 더 많은 픽셀을 탐색할수록 시간이 더 오래 걸립니다. 따라서 해결책은 많은 픽셀을 건너 뛰어 가능한 한 빨리 그림을 찾은 다음 더 정확하게 조닝하는 것입니다. 우리의 위치.

xy를 증가시키는 방법을 매개 변수로 취하도록 오류 함수를 수정했습니다.

function GetImageErrorAt($haystack, $needle, $startX, $startY, $increment)
{
    $needleWidth = imagesx($needle);
    $needleHeight = imagesy($needle);

    $error = array("red" => 0, "green" => 0, "blue" => 0, "avg" => 0, "complete" => true);

    for($x = 0; $x < $needleWidth; $x = $x + $increment)
    {
        for($y = 0; $y < $needleHeight; $y = $y + $increment)
        {
            $hrgb = imagecolorat($haystack, $x + $startX, $y + $startY);
            $nrgb = imagecolorat($needle, $x, $y);

            $nr = $nrgb & 0xFF;
            $hr = $hrgb & 0xFF;

            $ng = ($nrgb >> 8) & 0xFF;
            $hg = ($hrgb >> 8) & 0xFF;

            $nb = ($nrgb >> 16) & 0xFF;
            $hb = ($hrgb >> 16) & 0xFF;

            $error["red"] += abs($hr - $nr);
            $error["green"] += abs($hg - $ng);
            $error["blue"] += abs($hb - $nb);
        }
    }

    $error["avg"] = ($error["red"] + $error["green"] + $error["blue"]) / ($needleWidth * $needleHeight);

    return $error;
}


예를 들어 2를 전달하면 xy 값을 모두 건너 뛰므로 함수가 4 배 더 빠르게 반환됩니다.

또한 메인 루프에 stepSize를 추가했습니다.

$stepSize = 10;

for($x = 0; $x <= $largewidth - $smallwidth; $x = $x + $stepSize)
{
    for($y = 0; $y <= $largeheight - $smallheight; $y = $y + $stepSize)
    {
        // Scan the whole picture
        $error = GetImageErrorAt($large, $small, $x, $y, 2);
        if($error["complete"] == true && $error["avg"] < $keepThreshold)
        {
            array_push($potentialPositions, array("x" => $x, "y" => $y, "error" => $error));
        }
    }
}


이를 통해 정확한 가격으로 실행 시간을 2657 초에서 7 초로 줄일 수있었습니다. 더 많은 "잠재적 인 결과"를 얻기 위해 keepThreshold를 늘 렸습니다.

이제 각 픽셀을 확인하지 않았으므로 가장 좋은 대답은 다음과 같습니다.

Found 8 potential positions
Most likely at 290,240


보시다시피 우리는 원하는 위치에 가깝지만 옳지 않습니다.

다음으로 할 일은이 "꽤 가까운"위치 주위에 사각형을 정의하여 추가 한 stepSize 내부의 모든 픽셀을 탐색하는 것입니다.

이제 스크립트의 아래 부분을 다음과 같이 변경합니다.

if(count($potentialPositions) > 0)
{
    usort($potentialPositions, "SortOnAvgError");
    $mostLikely = $potentialPositions[0];
    echo "Most probably around " . $mostLikely["x"] . "," . $mostLikely["y"] . "\n";

    $startX = $mostLikely["x"] - $stepSize + 1; // - $stepSize was already explored
    $startY = $mostLikely["y"] - $stepSize + 1; // - $stepSize was already explored

    $endX = $mostLikely["x"] + $stepSize - 1;
    $endY = $mostLikely["y"] + $stepSize - 1;

    $refinedPositions = array();

    for($x = $startX; $x <= $endX; ++$x)
    {
        for($y = $startY; $y <= $endY; ++$y)
        {
            // Scan the whole picture
            $error = GetImageErrorAt($large, $small, $x, $y, 1); // now check every pixel!
            if($error["avg"] < $keepThreshold) // make the threshold smaller
            {
                array_push($refinedPositions, array("x" => $x, "y" => $y, "error" => $error));
            }
        }
    }

    echo "Found " . count($refinedPositions) . " refined positions\n";
    if(count($refinedPositions))
    {
        usort($refinedPositions, "SortOnAvgError");
        $mostLikely = $refinedPositions[0];
        echo "Most likely at " . $mostLikely["x"] . "," . $mostLikely["y"] . "\n";
    }
}


이제 다음과 같은 출력을 제공합니다.

Found 8 potential positions
Most probably around 290,240
Checking between X 281 and 299
Checking between Y 231 and 249
Found 23 refined positions
Most likely at 288,235
in 13.960182189941 seconds


실제로 정답은 초기 스크립트보다 약 200 배 빠릅니다.

편집 2

이제 내 테스트 케이스가 너무 단순했습니다 ... Google 이미지 검색으로 변경했습니다.



이 사진을 찾는 중 (718,432에 있음)



더 큰 사진 크기를 고려하면 처리 시간이 더 길어질 수 있지만 알고리즘은 올바른 위치에서 사진을 찾았습니다.

Found 123 potential positions
Most probably around 720,430
Found 17 refined positions
Most likely at 718,432
in 43.224536895752 seconds


편집 3

나는 코멘트에서 말한 옵션을 사용하여 찾기를 실행하기 전에 사진을 축소하기로 결정했으며 그 결과 훌륭한 결과를 얻었습니다.

첫 번째 루프 전에이 코드를 추가했습니다.

$smallresizedwidth = $smallwidth / 2;
$smallresizedheight = $smallheight / 2;

$largeresizedwidth = $largewidth / 2;
$largeresizedheight = $largeheight / 2;

$smallresized = imagecreatetruecolor($smallresizedwidth, $smallresizedheight);
$largeresized = imagecreatetruecolor($largeresizedwidth, $largeresizedheight);

imagecopyresized($smallresized, $small, 0, 0, 0, 0, $smallresizedwidth, $smallresizedheight, $smallwidth, $smallheight);
imagecopyresized($largeresized, $large, 0, 0, 0, 0, $largeresizedwidth, $largeresizedheight, $largewidth, $largeheight);


그리고 그들을 위해 메인 루프는 크기가 조정 된 너비와 높이로 크기가 조정 된 자산을 반복했습니다. 그런 다음 배열에 추가 할 때 xy를 두 배로 늘려 다음을 제공합니다.

array_push($potentialPositions, array("x" => $x * 2, "y" => $y * 2, "error" => $error));


실제 크기의 사진에서 정확한 위치를 지정하기를 원하므로 나머지 코드는 동일하게 유지됩니다. 마지막에 추가하기 만하면됩니다.

imagedestroy($smallresized);
imagedestroy($largeresized);


이 버전의 코드를 사용하여 Google 이미지 결과와 함께 다음을 수행했습니다.

Found 18 potential positions
Most around 720,440
Found 17 refined positions
Most likely at 718,432
in 11.499078989029 seconds


4 배의 성능 향상!

도움이 되었기를 바랍니다

이 질문에 대해 Stack Overflow에서 비슷한 토론을 찾았습니다: https://stackoverflow.com/questions/14345256/

관련 기사
php - 예를 들어 3의 루트는 2입니다.
php - Codeigniter 포함보기
php - Amazon MWS 제품 카테고리의 전체 목록은 어디에서 얻을 수 있습니까? [닫은]
php - MYSQL 연도의 중단 날짜 중에서 선택
php - PHP를 통해 wait_timeout 및 max_allowed_packet을 변경하는 방법이 있습니까? [복제]
php - .csv 파일을 생성 할 수 없습니다 : PHP
php - PHP를 통해 프로세스에 신호 보내기
php - 컬 페이지 제목
php - Zend Framework 2-주석 양식이 작동하지 않습니다.
php - 인수에서 문자열 연결 (PHP)