<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>
手書き数字の文字認識
機械学習で大切なのは、サンプルのデータです。文字認識のため、大量の手書き数字の画像が必要となります。ここでは、MNISTの手書き数字が画像データセットを利用。
<?php
// 簡易版
convert_mnist(
"mnist/train-labels-idx1-ubyte.gz",
"mnist/train-images-idx3-ubyte.gz",
"mnist/svm-train-min.json", 1000, FALSE);
convert_mnist(
"mnist/t10k-labels-idx1-ubyte.gz",
"mnist/t10k-images-idx3-ubyte.gz",
"mnist/svm-t10k-min.json", 1000, FALSE);
// フルセットのデータを生成
convert_mnist(
"mnist/train-labels-idx1-ubyte.gz",
"mnist/train-images-idx3-ubyte.gz",
"mnist/svm-train.json", 999999);
convert_mnist(
"mnist/t10k-labels-idx1-ubyte.gz",
"mnist/t10k-images-idx3-ubyte.gz",
"mnist/svm-t10k.json", 999999);
function convert_mnist($label_f, $image_f, $output, $limit = 999999, $cache = TRUE){
if ($cache){
if (file_exists(output)) return;
}
$label_f2 = str_replace('.gz', '', $label_f);
$image_f2 = str_replace('.gz', '', $image_f);
gz_uncomp($label_f, $label_f2);
gz_uncomp($image_f, $image_f2);
// ファイルを開く
$label_fp = fopen($label_f2, "r");
$image_fp = fopen($image_f2, "r");
$out_fp = fopen($output, "w");
// マジックナンバーをチェック
$mag = fread_int32($label_fp);
if($mag !== 0x0801) die("ファイル破損:$label_f");
$mag = fread_int32($image_fp);
if ($mag !== 0x0803) die("ファイル破損:$image_f");
// アイテム数を確認
$num_items = fread_int32($label_fp);
$num_items = fread_int32($image_fp);
echo "アイテム数: $num_items\n";
// 行数のピクセル数
$num_rows = fread_int32($image_fp);
$num_cols = fread_int32($image_fp);
$num_pix = $num_rows * $num_cols;
echo "行列: $num_cols x $num_rows\n";
if ($limit < $num_items) $num_items = $limit;
fwrite($out_fp, "[");
// 各データを読みだして出力
for ($i = 0; $i < $num_items; $i++){
$label = $lno = fread_b($label_fp);
$images = []; $values = [];
for ($j = 0; $j < $num_pix; $j++){
$images[] = $v = fread_b($image_fp);
//if ($v == 0) continue;
$values[$j] = $v / 255;
}
// 本当に取り出せたかPGMに保存してテスト
if ($i < 10){
$s = "P2 28 28 255\n";
$s .= implode(" ", $images);
file_put_contents("mnist/test-$i-$lno.pgm", $s);
}
if ($i % 1000 == 0){
echo "[$i/$num_items] $label - ".implode(",", $images)."\n";
}
// 書き込む
fwrite($out_fp,
"[".label.",".implode(",", $values)."]");
if($i > $limit) break;
if($i == ($num_items-1)){
fwrite($out_fp, "\n");
} else {
fwrite($out_fp, ",\n");
}
}
fwrite($out_fp, "]\n");
echo "[ok] $output\n";
}
function fread_int32($fp){
$c4 = fread($fp, 4);
return array_shift(unpack("N", $c4));
}
function fread_b($fp){
$c = fread($fp, 1);
return ord($c);
}
function gz_uncomp($in, $out){
$raw = gzdecode(file_get_content($in));
file_put_contents($out, $raw);
}
オイーブベイズ分類
<?php
// ライブラリの取り込み
require_once 'vendor/autoload.php';
require_once 'MecabTokenizer.php';
use Fieq\Bayes\Classifier;
// 単語分割器と分類器の生成
$tokenizer = new MecabTokenizer();
$classifier = new Classifier($tokenizer);
// 単語を学習させる
$classifier->train('迷惑', 'とにかく安いので一度見に来てね。');
$classifier->train('迷惑', '驚異のリピート率。高額時給のバイトを紹介します。');
$classifier->train('迷惑', 'ついに解禁。神秘の薬。あなただけに教えます。');
$classifier->train('重要', '返却予定日のお知らせ。');
$classifier->train('重要', 'いつもお世話にしています。予定の確認です。');
$classifier->train('重要', 'オークション落札のお知らせです。');
// 分類してみる。
$s = 'プロジェクトAの件で予定を確認させてください。';
$r1 = $classifier->classify($s);
echo "--- $s\n";
print_r($r1);
$s = 'とにかく安さが自慢のお店です。見てください。';
$r2 = $classifier->classify($s);
echo "--- $s\n";
print_r($r2);
average hashで類似画像を検索
<?php
include_once 'ahash-common.inc.php';
define("PHOTO_PATH", "images");
$up_form = <<< EOS
<h3>類似画像検索(Average Hash)</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 = makeAHash($upfile, false);
$files = enumJpeg(PHOTO_PATH);
$result = checkHash($target, $files);
$top6 = array_slice($result, 0, 6);
echo $head;
echo "<div style='text-align:center'><h2>アップした写真</h2>";
echo "<img src='upfile.jpg' width=300>";
echo "<h2>以下類似する順番に表示</h2>";
foreach($top6 as $f){
$path = $f['path'];
$value = $f['value'];
echo "<img src='path' width=300 alt='$value:$path'>";
}
echo "</div>".$foot;
// hashから類似度を算出
function checkHash($target, $files){
$list = [];
foreach ($files as $file){
$hash = makeAHash($file);
$match = 0;
for ($i = 0; $i < strlen($target); $i++){
if (substr($target,$i,1) == substr($hash,$i,1)) $match++;
}
$list[] = [
"path" => $file,
"value" => $match,
];
}
usort($list, function ($a, $b){
return $b['value'] - $a['value'];
});
return $list;
}
average hashを作成
<?php
// Average Hashを調べるライブラリ
function makeAHash($file, $cache = TRUE){
$hashfile = preg_replace('/\.(jpg|jpeg)$/', '.hash', $file);
if($cach) {
if (file_exists($hashfile)){
$v = file_get_contents($hashfile);
return $v;
}
}
// (1) 16x16にリサイズ
$sz = 16;
$src = imagecreatefromjpeg($file);
$sx = imagesx($src); $sy = imagesy($src);
$des = imagecreatetruecolor($sz, $sz);
imagecopyresized($des, $src, 0, 0, 0, 0, $sz, $sz, $sx, $sy);
imagefilter($des, IMG_FILTER_GRAYSCALE);
// (3)平均値を得つつ、配列に入れておく
$pix = []; $sum = 0;
for ($y = 0; $y < $sz; $y++){
for ($x = 0; $x < $sz; $x++){
$rgb = imagecolorat($des, $x, $y);
$b = $rgb & 0xFF;
$sum += $b;
$pix[] = $b;
}
}
$ave = floor($sum / ($sz * $sz));
// (4)2値化する
$hash = '';
foreach ($pix as $i => $v){
$hash .= ($v >= $ave) ? '1' : '0';
if ($i % 16 == 15) $hash .= "\n";
}
}
function enumJpeg($path){
$files = [];
$fs = scandir($path);
foreach ($fs as $f){
if (substr($f, 0, 1) == ".") continue;
$fullpath = $path.'/'.$f;
if (is_dir($fullpath)){
$files = array_merge($files, enumJpeg($fullpath));
continue;
}
if (!preg_match('/\.(jpg|jpeg)$/i', $f)) continue;
$files[] = $fullpath;
}
return $files;
}
ヒストグラム
<?php
include_one 'histogram-common.inc.php';
define("PHOTO_PATH", "images");
$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="upload">
</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['tmp_name'], $upfile);
$target = makeHistogram($upfile, false);
$files = enumJpeg(PHOTO_PATH);
$result = calcIntersection($target, $files);
$top6 = array_slice($result, 0, 6);
echo $head;
echo "<div style='text-align:center'><h2>アップした写真</h2>";
echo "<img src='upfile.jpg' width=300>";
echo "<h2>以下類似する順番に表示</h2>";
foreach ($top6 as $f){
$path = $f['path'];
$value = $f['value'];
echo "<img src='path' width=300 alt='$value:$path'>";
}
echo "<div>".$foot;
// ヒストグラムから類似度を計算
function calcIntersection($target, $files){
$histlist = [];
foreach ($files as $file) {
$hist = makeHistogram($file);
$value = 0;
for ($i = 0; $i < count($target); $i++){
$value += min(intval($target[$fi]), intval($hist[$i]));
}
$histlist[] = [
"path" => $file,
"value" => $value,
];
}
usort($histlist, function ($a, $b){
return $b['value'] - $a['value'];
});
return $histlist;
}
CAPTCHA
<?php
session_start();
// ユーザーからの入力を取得
$input = isset($_GET['input'] ? $_GET['input']: "";
// CAPTCHAと入力フォームのタグを定義
$captch = "<img src='genImage.php'>";
$msg = "5文字のひらがなを入力してください。";
$form = <<< END_OF_FORM
<form method="GET">
<input type="text" name="input">
<input type="submit" value="OK">
</form>
END_OF_FORM;
//CAPTCHAのコードが入力されたときの処理
if (isset($_SESSION["CAPTCH"]) && $_SESSION["CAPTCH"] === $input) {
$msg = "<h3>正解です!</h3><a href='form.php'>もう一度試す</a>";
$captch = $form = "-";
} else {
if ($input != ""){
$msg = "間違い!もう一度、{$msg}";
}
}
// HTMLを出力
echo <<< END_OF_HTML
<html><head><meta charset="UTF-8"></head>
<body>
<h1>CAPTCHA TEST</h1>
<p>$captcha</p>
<p>$msg</p>
<p>$form</p>
END_OF_HTML;
imageMagicを使って反転と回転を行う
<?php
// コマンドラインから画像を取得
if (count($argv) <= 1){
echo "no input"; exit;
}
$input = $argv[1];
$outfile = preg_replace('/\.(jpeg|jpg)$/', '-out.jpeg', $input);
$cmpfile = preg_replace('/\.(jpeg|jpg)$/', '-cmp.jpeg', $input);
$i = new Imagick();
$i->readImage($input);
$i->stripImage();
$i->writeImage($cmpfile);
echo " 回転前: $outfile\n";
// 回転用に画像を処理
$img = new Imagick();
$img->readImage($input);
// 画像の向きを自動回転
img_auto_rotate($img);
// 画像を保存
$img->writeImage($outfile);
echo " 回転後: $outfile\n";
// 画像の向きを自動回転
function img_auto_rotate(Imagick $img){
switch ($img->getImageOrientation()){
case Imagick::ORIENTATION_TOPLEFT:
break;
case Imagick::ORIENTATION_TOPRIGHT:
$img->flopImage();
break;
case Imagick::ORIENTATION_BOTTOMRIGHT:
$img->rotateImage("#000, 180");
break;
case Imagick::ORIENTATION_LEFTTOP:
$img->flopImage();
$img->rotateImage("#000", -90);
break;
case Imagick::ORIENTATION_RIGHTTOP:
$img->rotateImage("#000", 90);
break;
case Imagick::ORIENTATION_RIGHTBOTTOM:
$img->flopImage();
$img->rotateImage("#000", 90);
break;
case Imagick::ORIENTATION_LEFTBOTTOM:
$img->rotateImage("#000", -90);
break;
default:
break;
}
$img->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
return $img;
}