ゼロと無限の間に

フリーでオープンソースなJavaScriptとかPHPとか。

ユーザ用ツール

サイト用ツール


php-tool-box:image-diff

PHPで写真などの画像から背景だけを消し去る - ImageDiff.php

コメントと更新履歴はゼロと無限の間のログ » ImageDiff.phpでどうぞ。


PHPとGD2を使って、画像から背景部分だけを取り除き、前景のみを得る実験。
結果はこのとおり、ちょっと精度に欠ける。

写真での実験結果はこちら。ImageDiffで写真を扱った結果

なお、今回作ったImageDiffクラスは対象の画像形式はPNGだが、該当箇所を変更すればJPEGとかGIFでもできるはず。

やり方

対象となる画像ファイルと、その画像ファイルの背景のみの画像ファイルを用意する。
後は下記のようにImageDiffクラスに任せる。

require('ImageDiff.php');
$dir = '/tmp';
ImageDiff::$colorThreshold = 1;
ImageDiff::draw("$dir/src.png", "$dir/background.png", "$dir/result.png");

ImageDiffクラスのpublic変数の値を調整すれば精度を変えられる。
また、draw()の第4引数に画像ファイルのパスを指定すると、取得した前景の画像をその画像の上に載せることができる。

手ブレ補正

手ブレした写真など、比較する画像の位置にずれがある場合は、ImageDiff::$maxSlippageにピクセル数をセットすることによりブレを補正できる。

明るさ補正

蛍光灯の明かりで撮影した写真などは彩度は同じでも明度が変わってきてしまうが、ImageDiffでは彩度の違いにより背景かどうかを判断するようにしているので影響は少ない。

ソースコード

<?php
/**
 *	ImageDiff.php
 *	@version	0.1.0
 */
class ImageDiff {
    /** 違う色と判断する閾値(大きいほど大きな色の違いを求める) */
    public static $colorThreshold = 10;
    /** ブレを修正する範囲(単位=ピクセル数。大きいと時間がかかる) */
    public static $maxSlippage = 0;
    /** 画像を滑らかにする度合い(大きくすると画像が鮮明になりノイズが増える) */
    public static $smooth = 3;
 
    /**
     *    前景のみをくり抜く
     *    @param    string    $srcPath    対象となる画像のファイルパス
     *    @param    string    $difPath    背景のみの画像のファイルパス
     *    @param    string    $dstPath    差分画像の出力先のファイルパス
     *    @param    string    $basePath    (省略可)差分画像を埋め込む画像のファイルパス
     */
    public static function draw($srcPath, $difPath, $dstPath, $basePath = null) {
        list($w, $h) = getimagesize($srcPath);
 
        $srcImg = imagecreatefrompng($srcPath);
        $difImg = imagecreatefrompng($difPath);
 
        if ($basePath) {
            $canvas = imagecreatefrompng($basePath);
        } else {
            $canvas = imagecreatetruecolor($w, $h);
            imagefill($canvas, 0, 0, imagecolorallocate($canvas, 255, 255, 255));
        }
 
        $s = ImageDiff::$maxSlippage;
        $rect = array($s, $s, $w - $s, $h - $s);    //対象範囲の長方形
 
        $slippage = ImageDiff::_getPoint($srcImg, $difImg, $rect);
 
        ImageDiff::_forAllPoint($srcImg, $difImg, $rect, $slippage, $canvas, false);
 
        imagefilter($canvas, IMG_FILTER_SMOOTH, ImageDiff::$smooth);    //ノイズを減らす
 
        imagepng($canvas, $dstPath);
 
        imagedestroy($srcImg);
        imagedestroy($difImg);
        imagedestroy($canvas);
    }
    /**
     *    画像間のブレが一番小さくなるずらし方を探す
     */
    private static function _getPoint($srcImg, $difImg, $rect) {
        $s = ImageDiff::$maxSlippage;
        $record = -1;
        //X軸、Y軸についてずらしながら探す
        for ($x = -$s; $x <= $s; $x++) {
            for ($y = -$s; $y <= $s; $y++) {
                $slippage = array($x, $y);
                $sum = ImageDiff::_forAllPoint($srcImg, $difImg, $rect, $slippage, false, $record);
                if (($sum > 0 && $sum < $record) || $record < 0) {
                    $record = $sum;    //記録更新
                    $best = $slippage;
                }
                echo "$x,$y => $sum\n";    //途中経過を報告
            }
        }
        echo "[Best] {$best[0]},{$best[1]} => $record";    //結果報告
        return $best;
    }
    /**
     *    全ピクセルについて処理する
     */
    private static function _forAllPoint($srcImg, $difImg, $rect, $slippage, $canvas, $record) {
        $sum = 0;
        for ($x = $rect[0]; $x < $rect[2]; $x++) {
            for ($y = $rect[1]; $y < $rect[3]; $y++) {
                $diff = ImageDiff::_calcDiff($srcImg, $difImg, $x, $y, $slippage);
                if ($canvas) {    //前景のコピーの場合
                    if ($diff > ImageDiff::$colorThreshold) {    //閾値を超えたら前景とみなす
                        imagecopy($canvas, $srcImg, $x, $y, $x, $y, 1, 1);
                    }
                } else {    //差分合計の計測の場合
                    $sum += $diff;
                    if ($record > 0 && $record <= $sum) {    //もう記録更新が無理な場合は中止
                        return -1;
                    }
                }
            }
        }
        return $sum;
    }
    /**
     *    特定のポイントについて2つの画像の色の差の偏りを計算する
     */   
    private static function _calcDiff($srcImg, $difImg, $x, $y, $slippage) {
        $srcColor = ImageDiff::_getRGB($srcImg, $x, $y);
        $difColor = ImageDiff::_getRGB($difImg, $x + $slippage[0], $y + $slippage[1]);
 
        $diff = array();
        for ($i = 0; $i < 3; $i++) {    //RGBそれぞれについて差を求める
            $diff[$i] = $srcColor[$i] - $difColor[$i];
        }
 
        $sum = 0;
        for ($i = 0; $i < 3; $i++) {    //RGB間の差の偏りを求める
            $sum += abs($diff[$i] - $diff[($i + 1) % 3]);
        }
        return $sum;
    }
    /**
     *    色情報(RGB)を取得する
     */
    private static function _getRGB($rsc, $x, $y) {
        $rgb = imagecolorat($rsc, $x, $y);
        for ($i = 0; $i < 3; $i++) {
            $color[$i] = ($rgb >> ($i * 8)) & 0xFF;
        }
        return $color;
    }
}
php-tool-box/image-diff.txt · 最終更新: 2008/03/15 23:25 by dgbadmin

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki