如何优化此图像"边缘检测"算法?

时间:2014-07-18 17:05:07

标签: php image-processing gd edge-detection

我有一个函数,给定一个带有透明背景和未知对象的图像,找到对象的顶部,左侧,右侧和底部边界。目的是让我可以简单地围绕对象的边界绘制一个框。我不是要检测物体的实际边缘 - 只是最顶部,最底部等等。

我的功能运行良好,但速度很慢,因为它会扫描图像中的每个像素。

我的问题是:使用库存PHP / GD功能,是否有更快,更有效的方法来检测图像中最上面,最左边,最右边和最下面的非透明像素?< / p>

有一个影响选项的问题:图像中的对象可能有透明的部分。例如,如果它是非填充形状的图像。

public static function getObjectBoundaries($image)
{
    // this code looks for the first non white/transparent pixel
    // from the top, left, right and bottom

    $imageInfo = array();
    $imageInfo['width'] = imagesx($image);
    $imageInfo['height'] = imagesy($image);

    $imageInfo['topBoundary'] = $imageInfo['height'];
    $imageInfo['bottomBoundary'] = 0;
    $imageInfo['leftBoundary'] = $imageInfo['width'];
    $imageInfo['rightBoundary'] = 0;

    for ($x = 0; $x <= $imageInfo['width'] - 1; $x++) {
        for ($y = 0; $y <= $imageInfo['height'] - 1; $y++) {
            $pixelColor = imagecolorat($image, $x, $y);
            if ($pixelColor != 2130706432) {        // if not white/transparent
                $imageInfo['topBoundary'] = min($y, $imageInfo['topBoundary']);
                $imageInfo['bottomBoundary'] = max($y, $imageInfo['bottomBoundary']);
                $imageInfo['leftBoundary'] = min($x, $imageInfo['leftBoundary']);
                $imageInfo['rightBoundary'] = max($x, $imageInfo['rightBoundary']);
            }
        }
    }

    return $imageInfo;
}

3 个答案:

答案 0 :(得分:1)

PHP中的函数调用很昂贵。每个像素调用imagecolorat()绝对会破坏性能。 PHP中的高效编码意味着找到可以某种方式完成工作的内置函数。以下代码使用调色板GD功能。一目了然它可能不直观,但逻辑实际上非常简单:代码一直在复制图像一行像素,直到它注意到它需要多种颜色来表示它们。

function getObjectBoundaries2($image) {
    $width = imagesx($image);
    $height = imagesy($image);

    // create a one-pixel high image that uses a PALETTE
    $line = imagecreate($width, 1);
    for($y = 0; $y < $height; $y++) {
        // copy a row of pixels into $line
        imagecopy($line, $image, 0, 0, 0, $y, $width, 1);

        // count the number of colors in $line
        // if it's one, then assume it's the transparent color
        $count = imagecolorstotal($line);
        if($count > 1) {
            // okay, $line has employed more than one color so something's there
            // look at the first color in the palette to ensure that our initial 
            // assumption was correct 
            $firstColor = imagecolorsforindex($line, 0);
            if($firstColor['alpha'] == 127) {
                $top = $y;
            } else {
                // it was not--the first color encountered was opaque
                $top = 0;
            }
            break;
        }
    }

    if(!isset($top)) {
        // image is completely empty
        return array('width' => $width, 'height' => $height);
    }

    // do the same thing from the bottom
    $line = imagecreate($width, 1);
    for($y = $height - 1; $y > $top; $y--) {
        imagecopy($line, $image, 0, 0, 0, $y, $width, 1);
        $count = imagecolorstotal($line);
        if($count > 1) {
            $firstColor = imagecolorsforindex($line, 0);
            if($firstColor['alpha'] == 127) {
                $bottom = $y;
            } else {
                $bottom = $height - 1;
            }
            break;
        }
    }
    $nonTransparentHeight = $bottom - $top + 1;

    // scan from the left, ignoring top and bottom parts known to be transparent
    $line = imagecreate(1, $nonTransparentHeight);
    for($x = 0; $x < $width; $x++) {
        imagecopy($line, $image, 0, 0, $x, $top, 1, $nonTransparentHeight);
        $count = imagecolorstotal($line);
        if($count > 1) {
            $firstColor = imagecolorsforindex($line, 0);
            if($firstColor['alpha'] == 127) {
                $left = $x;
            } else {
                $left = 0;
            }
            break;
        }
    }

    // scan from the right
    $line = imagecreate(1, $nonTransparentHeight);
    for($x = $width - 1; $x > $left; $x--) {
        imagecopy($line, $image, 0, 0, $x, $top, 1, $nonTransparentHeight);
        $count = imagecolorstotal($line);
        if($count > 1) {
            $firstColor = imagecolorsforindex($line, 0);
            if($firstColor['alpha'] == 127) {
                $right = $x;
            } else {
                $right = $width - 1;
            }
            break;
        }
    }

    return array('width' => $width, 'height' => $height, 'topBoundary' => $top, 'bottomBoundary' => $bottom, 'leftBoundary' => $left, 'rightBoundary' => $right);
}

答案 1 :(得分:0)

我认为你可以一个接一个地测试4个边,一旦找到像素就停止。 对于顶部边界(未经测试的代码):

// false so we can test it's value
$bound_top = false;
// The 2 loops have 2 end conditions, if end of row/line, or pixel found
// Loop from top to bottom
for ($y = 0; $y < $img_height && $bound_top === false; $y++) {
    // Loop from left to right (right to left would work to)
    for ($x = 0; $x < $img_width && $bound_top === false; $x++) {
        if (imageColorAt($img, $x, $y) != 2130706432) {
            $bound_top = $y;
        }
    }
}

在循环之后,如果$bound_top仍然是false,请不要检查其他边,检查所有像素,图像为空。如果没有,就为其他方面做同样的事情。

答案 2 :(得分:0)

不是每个像素都需要检查。以下代码检查从左到右的列以获取leftBoundary,从右到左获取rightBoundary,从上到下的行(排除我们已经检查过的像素)以获得topBoundary,类似地使用bottomBoundary。

function get_boundary($image)
{
    $imageInfo = array();
    $imageInfo['width'] = imagesx($image);
    $imageInfo['height'] = imagesy($image);

    for ($x = 0; $x < $imageInfo['width']; $x++) {
        if (!is_box_empty($image, $x, 0, 1, $imageInfo['height'])) {
            $imageInfo['leftBoundary'] = $x;
            break;
        }
    }

    for ($x = $imageInfo['width']-1; $x >= 0; $x--) {
        if (!is_box_empty($image, $x, 0, 1, $imageInfo['height'])) {
            $imageInfo['rightBoundary'] = $x;
            break;
        }
    }

    for ($y = 0; $y < $imageInfo['height']; $y++) {
        if (!is_box_empty($image, $imageInfo['leftBoundary'], $y, $imageInfo['rightBoundary']-$imageInfo['leftBoundary']+1, 1)) {
            $imageInfo['topBoundary'] = $y;
            break;
        }
    }

    for ($y = $imageInfo['height']-1; $y >= 0; $y--) {
        if (!is_box_empty($image, $imageInfo['leftBoundary'], $y, $imageInfo['rightBoundary']-$imageInfo['leftBoundary']+1, 1)) {
            $imageInfo['bottomBoundary'] = $y;
            break;
        }
    }

    return $imageInfo;
}

function is_box_empty($image, $x, $y, $w, $h)
{
    for ($i = $x; $i < $x+$w; $i++) {
        for ($j = $y; $j < $y+$h; $j++) {
            $pixelColor = imagecolorat($image, $i, $j);
            if ($pixelColor != 2130706432) {        // if not white/transparent
                return false;
            }
        }
    }

    return true;
}