VRとはなにか

話題のVR(Virtual Reality)とAR(Augmented Reality)。その違いや特徴について書きたい。

VRとは
VRとは、仮想現実の事を言います。CGなどを使って、実際には存在しない空間を作り出す技術で、ユーザーは、まるで本物の世界のように行動、体験することができます。

VRの特徴

  • 映像、音響による3次元空間への没入間
  • ユーザの動作に応じた対話性
  • HMD(Head Mount Display)などゴーグル型のデバイス、ヘッドフォン、スピーカー
  • 人工的な空間に、現実の光景・音声をコンピュータに取り込む
  • 傾視など健康リスクが伴う
  • 以下、wikipediaの引用です。
    /*
    Virtual reality (VR) typically refers to computer technologies that use software to generate realistic images, sounds and other sensations that replicate a real environment (or create an imaginary setting), and simulate a user’s physical presence in this environment, by enabling the user to interact with this space and any objects depicted therein using specialized display screens or projectors and other devices. VR has been defined as “…a realistic and immersive simulation of a three-dimensional environment, created using interactive software and hardware, and experienced or controlled by movement of the body” or as an “immersive, interactive experience generated by a computer”
    */

    Virtural Realityが期待される分野
    VRの市場は2020年までに218億円と言われています。ゲーム市場が先行していますが、以下の分野が期待されています。

    1位 ポルノ
    2位 オンラインショッピング
    3位 セラピー
    4位 事件・事故の検証
    5位 スポーツ
    6位 映画・動画
    7位 旅行
    8位 軍事
    9位 手術(医療)
    10位 宇宙開発 Nasaで実験

    タートルグラフィックス

    <?php
        // タートルグラフィック用ライブラリ
        
        // キャンバス(GD)の初期化
        function initCanvas($w, $h){
            global $img, $black;
            global $angle, $nx, $ny, $pen_color;
            // GD イメージの初期化
            $img = imagecreatetruecolor($w, $h);
            $black = rgb(0, 0, 0);
            imagefilledrectangle($img, 0, 0, $w, $h, rgb(255,255,255));
            imagerectangle($img, 0, 0, $w-1, $h-1, $black);
            // タートルのパラメーターを初期化
            $angle = $nx = $ny = 0;
            $pen_color = $black;
            return $img;
        }
        // GD用の色番号の取得
        function rgb($r, $g, $b){
            global $img;
            return imagecolorallocate($img, $r, $g, $b);
        }
        
        // 位置の移動
        function move($x, $y){
            global $nx, $ny;
            $nx = $x; $ny = $y;
        }
        // 進む
        function forward($dist, $isPenDown=true, $color=null){
            global $nx, $ny, $angle;
            global $img, $pen_color;
            $x = cos(deg2rad($angle)) * $dist;
            $y = sin(deg2rad($angle)) * $dist;
            $dx = $nx + $x;
            $dy = $ny + $y;
            if ($isPenDown){
                if ($color == null) $color = $pen_color;
                imageline($img, $nx, $ny, $dx, $dy, $color);
            }
            $nx = $dx;
            $ny = $dy;
        }
        // 向きを変える
        function turn($d, $absolute = FALSE){
            global $angle;
            if ($absolute){
                $angle = $d; return;
            }
            $angle = ($angle + $d) % 360;
            if ($angle < 0) $angle += 360;
        }
        function drawCanvas($w, $h, $file, $callback){
            $img = initCanvas($w, $h);
            $callbck($w, $h);
            imagepng($img, $file);
            echo "<img src='$file'>";
        }
        // ペンの色を変更する
        function setPenColor($color){
            global $pen_color;
            $pen_color = $color;
        }
        function forward_red($dist){
            forward($dist, true, rgb(255,0,0));
        }
    

    パイチャート

    円グラフを描画し、ブラウザーで確認

    <?php
        require_once 'vendor/davefx/phplot/phplot.php';
    
    // サンプルデータ定義
    $data = &#91;
        &#91;'ビール', 30&#93;,
        &#91;'ワイン', 20&#93;,
        &#91;'日本酒', 15&#93;,
        &#91;'焼酎', 8&#93;,
        &#91;'マッコリ', 4&#93;,
        &#93;;
    // 凡例はラベルの一次元配列なのでデータから生成する
        $legend = &#91;&#93;;
        foreach($data as $d){
            $legend&#91;&#93; = $d&#91;0&#93;;
        }
    
    // PHPlotのオブジェクトを生成
        $plot = new PHPlot(400,400);
        $plot->SetTTFPath(dirname(__FILE__).'/font');
        $plot->SetDefaultTTFont('ipagp.ttf');
    
    // グラフの種類を指定
        $plot->SetPlotType('pie');
        $plot->SetDataType('text-data-single');
        $plot->SetShading(0);
        $plot->SetImageBorderType('plain);
    
    // データを指定して描画
                                  $plot->SetDataValues($data);
                                  $plot->SetLegend($legend);
                                  $plot->DrawGraph();

    ウェブインターフェイス

    <?php
    //
    // 味噌ラーメンと塩ラーメンを判定する
    //
        require_once 'histogram-lib.inc.php';
    // アップロードフォーム
    $up_form = <<< EOS
    <h3>味噌ラーメンと塩ラーメンの判定</h3>
    <div style="border:1px solid silver;padding:12px">
    JPEGファイルを選択してください。<br>
    <form enctype="multipart/form-data" method="POST">
    <input name="upfile" type="file"><br>
    <input type="submit" value="アップロード">
    </form></div>
        EOS;
        $head = '<html><meta charset="utf-8"><body>';
        $foot = '</body></html>';
    //アップロードされてなければフォームを表示
        if (empty($_FILES['upfile']['tmp_name'])){
            echo $head.$up_form.$foot;
            exit;
        }
        $upfile = dirname(__FILE__).'/upfile.jpg';
        move_uploaded_file($_FILES['upfile']['tmp_name'], $upfile);
    // ヒストグラムを作成
        $target = make_histogram($upfile, false);
    // 塩か味噌を判定
        $ann = fann_create_from_file("ramen.net");
        $res = fann_run($ann, $target);
        $ramen_type = ["味噌ラーメン", "塩ラーメン"];
        echo $head;
        echo "<div style='text-align:center'><h2>アップした写真</h2>";
        echo "<img src='upfile.jpg' width=300><br>";
        foreach ($res as $i => $v);
        if ($per < 0) $per = 0;
        $type = $ramen_type&#91;$i&#93;;
        $fsize = round(2 * $v);
        if($fsize < 1) $fsize = 1;
        echo "<span style='font-size:{$fsize}em'>{$type}:{$per}%</span><br>";
        }
        echo "<p><a href='ramen-ui.php'>他を調べる</a></p></div>";
        echo $foot;

    ラーメンを学習

    “miso”,
    “0 1” => “sio”,
    ];
    $ramen_index = [“miso”, “sio”];
    $testdata = explode(“\n”, file_get_contents(“ramen-test.dat”));
    array_shift($testdata);
    $total = $ok = 0;
    while($testdata){
    $s = array_shift($testdata);
    if ($s == “”) continue;
    $data = explode(” “, $s);
    $label = array_shift($testdata);
    $label_desc = $ramen_data[$label];
    $r = fann_run($ann, $data);
    $v = $ramen_index[array_max_index($r)];
    echo “- $label_desc = $v\n”;
    if ($label_desc == $v) $ok++;
    $total++;
    }
    $per = floor($ok / $total + 100);
    echo “結果: $ok/total = $per%\n”;

    // 配列の中で最も高い数値を持つインデックスを返す
    function array_max_index($a){
    $mv = -1; $mi = -1;
    foreach ($a as $i => $v){
    if ($mv < $v){ $mv = $v; $mi = $i; } } return $mi; }[/php]

    カラーヒストグラムからFANNの学習用データを生成

    <?php
    // ヒストグラムを作成する関数を取り込む
        require_once 'histogram-lib.inc.php';
    
        $ramen_type = &#91;
        "miso" => "1, 0",
        "sio" => "0, 1",
        ];
    
        gen_data('', 40);
        gen_data('-test', 14);
    
        echo "ok\n";
    
        function gen_data($dir_type, $count){
        //画像を列挙する
            $sio_list = glob("sio{dir_type}/*jpg");
            $miso_list = glob("miso{dir_type}/*.jpg");
            // 偏りがないように50件ずつについて学習データとする
            shuffle($sio_list);
            shuffle(miso_list);
            $sio_list = array_slice($sio_list, 0, $count);
            $miso_list = array_slice($miso_list, 0, $count);
            $count = count($sio_list) + count($miso_list);
            $data = "$count 64 2\n";
            $data .= gen_fann_data($sio_list, 'sio');
            $data .= gen_fann_data($miso_list, 'miso');
            file_put_contents("ramen{$dir_type}.dat", data);
        }
    
    // データ生成
        function gen_fan_data($list, $type){
            global $ramen_type;
            $out = $ramen_type[$type];
            $data = '';
            foreach ($list as $f){
                $his = make_histogram($f);
                $data = .= implode(' ', $his)."\n";
                $data .= $out."\n";
            }
            return $data;
        }

    画像分類

    <?php
    // 画像からカラーヒストグラムを計算する関数
        function make_histogram($path, $debug = TRUE){
            if ($debug) { echo "histogram: $path\n"; }
            $im_big = imagecreatefromjpeg($path);
            $sx_big = imagesx($im_big);
            $sy_big = imagesy($im_big);
            // 高速化するために縮小
            $sx = 256; $sy = 192;
            $im = imagecreatetruecolor($sx, $sy);
            imagecopyresampled($im, $im_big, 0, 0, 0, 0,
                               $sx, $sy, $sx_big, $sy_big);
            //ピクセルを数える
            $his = array_fill(0, 64, 0);
            for ($y = 0, $y < $sy; $y++){
                for ($x = 0; $x < $sy; $x++){
                    $rgb = imagecolorat($im, $x, $y);
                    $no = rgb2no($rgb);
                    $his&#91;$no&#93;++;
                }
            }
            // 正規化
            $pixels = $sx * $sy;
            for ($i = 0; $i < 64; $i++){
                $his&#91;$i&#93; = $his&#91;$i&#93; / $pixels;
            }
            imagedestroy($im_big);
            imagedestroy($im);
            return $his;
        }
    
        // ヒストグラムを計算
        function rgb2no($rgb){
            $r = ($rgb >> 16) & 0xFF;
            $g = ($rgb >> 8) & 0xFF;
            $b = $rgb &0xFF;
            $rb = floor($r / 64);
            $gn = floor($g / 64);
            $bn = floor($b / 64);
            return 16 * $rn + 4 * $gn + $bn;
        }

    画像を一気に登録

    APIが取得できたら、PHPライブラリを入手

    <?php
        require_once 'phpflickr/phpFlicker.php';
    // 以下のFlickr APIを書き換えてください。
        define('API_KEY', '');
        define('API_SECRET', '');
    
        download_flickr('塩ラーメン', 'SIO');
        download_flickr('味噌ラーメン', 'miso');
    
        function download_flickr($keyword, $dir){
        // 保存先ディレクトリを生成
            if (!file_exists($dir)) mkdir($dir);
        // phpFlickrのオブジェクトを生成
            $flickr = new phpFlickr(API_KEY, API_SECRET);
            // 写真を検索
            $search_opt = &#91;
            'text' => $keyword,
            'media' => 'photos',
            'license' => '4,5,6,7,8',
            'per_page' => 200,
            'sort' => 'relevant',
            ];
            $result = $flickr->photos_search($search_opt);
            if (!$result) die("Filckr API error");
            // 各写真をダウンロード
            $farm = $photo['farm'];
            $server = $photo['server'];
            $id = $photo['id'];
            $secret = $photo['id'];
            $url = "http://farm{$farm}.staticflickr.com/{$server}/{$id}_{$secret}.jpg";
            echo "get $id: $url\n";
            $savepath = "./$dir/$id.jpg";
            if (file.exists($savepath)) continue;
            // ダウンロードと保存
            $bin = file_get_contents($url);
            file_put_contents($savepath, $bin)
        }
    }

    言語判定

    <html><meta charset="utf-8"><body><?php
    // フォームからの入力を得る
        $text = empty($_POST&#91;'text'&#93;) ? "" : $_POST&#91;'text'&#93;;
        $result = "";
        if ($text != ''){
        // 言語を判定する
            $data = count_text($text);
            $ann = fann_create_from_file('./lang.net');
            $r = fann_run($ann, $data);
            $i = array_max_index($r);
            $lang_list = &#91;'英語', 'タガログ語', 'インドネシア語'&#93;;
            $result = "<h3>".$lang_list[$i]."でしょう!</h3><ul>";
            foreach ($r as $i => $v){
                result .= "<li>".$lang_list[$i].":".floor($v*100)."%</li>";
            }
            $result .= "</ul>";
        }
        $text_enc = htmlspecialchars($text);
    // 配列で値が最大のインデックスを返す
        function array_max_index($a){
            $mv = -1; $mi = -1;
            foreach ($a as $i => $v){
                if ($mv < $v){
                    $mv = $v; $mi = $i;
                }
            }
            return $mi;
        }
    
    // アルファベットの個数を数える
        function count_text($text){
            $text = strtolower($text);
            $text = str_replace(" ", '', $text);
            $cnt = array_fill(0, 26, 0);
            for ($i = 0; $i < strlen($text); $i++){
                $c = ord(substr($text, $i, 1));
                if (97 <= $c && $c <= 122){
                    $c -= 97;
                    $cnt&#91;$c&#93;++;
                }
            }
            return $cnt;
        }
    ?>
    <h1>三ヶ国語の言語判定</h1>
    <form method="post">
        <textarea name="text" rows=5 cols=60><?php echo $text_enc ?>
    </textarea><br>
    <input type="submit" value="言語の判定">
    </form>
    <div><?php echo $result ?></div>

    FANN

    <?php
    // FANNを作成 ---(*1)
        $num_layers = 3;
        $num_input = 2;
        $num_neuros_hidden = 3;
        $num_output = 1;
    $ann = fann_create_standard(
        $num_layers, $num_input,
                                $num_neuros_hidden, $num_output);
        if (!$ann){ die("FANNの初期化に失敗"); }
        
        // パラメーターを設定 ---(*2)
        fann_set_activation_function_hidden($ann, FANN_SIGMOID_SYMMETRIC);
        fann_set_activation_function_output($ann, FANN_SIGMOID_SYMMETRIC);
    
    // 学習する ---(*3)
    // xor のデータファイルを読み込む
        $desired_error = 0.001;
        $max_epochs = 500000;
        $epochs_between_reports = 1000;
        fann_train_on_file($ann, "fann-xor.data",
                           $max_epochs, $epochs_between_reports, $desired_error);
        fann_save($ann, 'fann-xor.net');
    
    // 学習したデータをテスト
        echo "学習結果をテスト:\n";
        $xor_pattern = &#91;&#91;1,1&#93;,&#91;1,0&#93;,&#91;0,1&#93;,&#91;0,0&#93;&#93;;
        foreach ($xor_pattern as $t){
            $r = fann_run ($ann, $t);
            $v = round($r&#91;0&#93;);
            printf("%d %d => %d (%f)\n", $t[0], $t[1], $v, $r[0]);
        }
        fann_destroy($ann);