<?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));
}
Month: August 2016
パイチャート
円グラフを描画し、ブラウザーで確認
<?php
require_once 'vendor/davefx/phplot/phplot.php';
// サンプルデータ定義
$data = [
['ビール', 30],
['ワイン', 20],
['日本酒', 15],
['焼酎', 8],
['マッコリ', 4],
];
// 凡例はラベルの一次元配列なのでデータから生成する
$legend = [];
foreach($data as $d){
$legend[] = $d[0];
}
// 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[$i];
$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 = [
"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[$no]++;
}
}
// 正規化
$pixels = $sx * $sy;
for ($i = 0; $i < 64; $i++){
$his[$i] = $his[$i] / $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 = [
'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['text']) ? "" : $_POST['text'];
$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 = ['英語', 'タガログ語', 'インドネシア語'];
$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[$c]++;
}
}
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 = [[1,1],[1,0],[0,1],[0,0]];
foreach ($xor_pattern as $t){
$r = fann_run ($ann, $t);
$v = round($r[0]);
printf("%d %d => %d (%f)\n", $t[0], $t[1], $v, $r[0]);
}
fann_destroy($ann);
手書き認識Webインターフェイス
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>文字認識</title>
</head><body onload="init()" style="text-align:center;">
<h1>手書き文字認識(数字)</h1>
<div>
<!-- ユーザーからの入力を受け取るCanvas -->
<canvas id="main_cv" width="300" height="300"
style="border: solid 1px gray"></canvas>
<!-- 送信用にリサイズした結果を描画するCanvas -->
<canvas id="back_cv" width="28" height="28" style="border:red solid 1px"></canvas>
</div>
<div>
予測:<div id="result" sytle="font-size:5em">?</div>
<p><button onClick="resultCanvas()"
style="font-size:2em;">リセット</button></p>
</div>
<script type="text/javascript">
// 変数など
var main_cv, ctx, main_r, back_cv, back_ctx;
var isDown, s_pt, border = 20;
// 初期化
function init() {
// キャンバスのオブジェクトやコンテキストを取得
main_cv = $("#main_cv");
ctx = main_cv.getContext("2d");
// キャンバスの位置とサイズを取得
main_r = main_cv.getBoundingClientRect();
// リサイズ処理用のイメージ
back_cv = $("#back_cv");
back_ctx = back_cv.getContext("2d");
// マウスイベントの設定
main_cv.onmousedown = function (e) {
e.preventDefault();
isDown = true;
s_pt = getXY(e);
ctx.beginPath();
ctx.lineWidth = border;
ctx.lineCap = "round";
ctx.strokeStyle = "white";
ctx.moveTo(s_pt.x, s_pt.y);
};
main_cv.onmousemove = function (e) {
if (!isDown) return;
e.preventDefault();
var pt = getXY(e);
ctx.lineTo(pt.x, pt.y);
ctx.stroke();
s_pt = pt;
ctx.beginPath();
ctx.moveTo(pt.x, pt.y);
};
main_cv.onmouseup = function(e){
if (!isDown) return;
isDown = false;
ctx.closePath();
recognize();
};
main_cv.onmouseout = main_cv.onmouseup;
resetCanvas();
}
function getXY(e){
var x = e.clientX;
var y = e.clientY;
y -= main_r.left;
y -= main_r.top;
return {"x":x, "y":y};
}
// キャンバスの初期化
function resetCanvas(){
ctx.clearRect(0, 0, main_cv.width, main_cv.height);
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, main_cv.main, main_cv.height);
ctx.beginPath();
back_ctx.clearRect(0,0,back_cv.width,back_cv.height);
back_ctx.beginPath();
x_min = main_cv.width;
x_max = 0;
y_min = main_cv.height;
y_max = 0;
}
// コピー
function copyToBack() {
back_ctx.fillStyle = "black";
back_ctx.fillRect(0,0,28,28);
back_ctx.drawImage(main_cv,
0, 0, main_cv.width, main_cv.height,
0, 0, 28, 28)
}
// パターンを作成する
function getPixelData(){
//画像を28x28にリサイズ
copyToBack();
// 画像イメージを取得
var img = back_ctx.getImageData(0, 0, 28, 28).data;
var buf = [];
for (var i = 0; i < 28 * 28; i++){
var k = i * 4;
var r = img[k + 0];
var g = img[k + 1];
var b = img[k + 2];
var a = img[k + 3];
var v = (Math.floor((r + g + b) / 3.0) > 0.5) ? 1: 0;
buf.push(v);
}
return buf.join("");
}
function recognize() {
var pix = getPixelData();
var uri = "api.php?in=" + pix;
$ajax(ur, function(xhr, res){
$("#result").innerHTML = "" + res;
console.log("predict=" + res);
});
}
// DOMを返す
function $(q) { return document.querySelector(q); }
// Ajax関数
function $ajax(url, callback){
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() `
if (xhr.readyState == 4){
if (xhr.status == 200){
callback(xhr, xhr.responseText);
}
}
};
xhr.send(''); //通信を開始
return xhr;
}
</script>
</body>
</html>