<!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; }
マルコフ連鎖の実装
形態素解析をしてそれを辞書に登録し、辞書を元に作文を行います。
3) array_shift($tmp); set_word($dic, $tmp); if ($w == "。 "){ $tmp = ["@"]; continue; } } return $dic; } // 辞書に単語を登録 function set_word(&$dic, $tmp){ $w1 = $tmp[0]; $w2 = $tmp[1]; $w3 = $tmp[2]; if (empty($dic[$w1])) $dic[$w1] = []; if (empty($dic[$w1][$w2])) $dic[$w1][$w2] = []; if (empty($dic[$w1][$w2][$w3])) $dic[$w1][$w2][$w3] = 0; $dic[$w1][$w2][$w3]++; } // 辞書から作文する function make_sentence($dic, $count){ $ret = []; for ($i = 0; $i < $count; $i++){ $top = $dic["@"]; if (!$top) break; $w1 = choice_word($top); $w2 = choice_word($top[$w1]); $ret[] = $w1; $ret[] = $w2; for (;;) { $w3 = choice_word($dic[$w1][$w2]); $ret[] = $w3; if ($w3 == "。")break; $w1 = $w2; $w2 = $w3; } } return implode("", $ret); } function choice_word($o){ if (!is_array($o)) return "。 "; $ks = array_keys($o); return $ks[mt_rand(0, count($ks)-1)]; }
ASCIIコード表
<?php // 特殊記号 $sp = ['NULL','SOH','STX','ETX','EOT','ENQ','ACK','BEL','BS','HT','LF','VT', 'FF','CR','SO','SI','DLE','DC1','DC2','DC3','DC4','NAL','SYN','ETV','CAN'm 'EM','SUB','ESC','FS','GS','RS','US','SPC']; $sp[127] = 'DEL'; // header $html = "<tr><th></th>"; for($j = 0; $j < 16; $j++){ $html .= "<th>".sprintf("%X",$j)."</th>"; } $html .= "</tr>"; // characters for ($i = 0; $i <= 7; $i++){ $html .= "<tr><th>{$i}x</th>"; for ($j = 0; $j < 16; $j++){ $code = ($i * 16) + $j; $ch = isset($sp[$code]) ? $sp[$code] : chr($code); $html .= "<td>$ch</td>"; } $html .= "</tr>"; } echo <<< EOS <style> * { margin:0; padding:0; } table { margin: 12px; } td, th { font-size: 12px, padding: 4px; width: 32px; text-align: center; border-bottom: 1px solid silver; border-right: 1px solid silver; } th { color: white; background-color: blue; } </style> <table> $html </table> EOS;