ニューラルネットワークを使用して元のキャプチャを作成します。 パート1

すべてのプログラマと同様に、最近、私は自分の「自転車」を発明したいと思っていました。 独自のCMS、フレームワークなどを発明する方法 関連性がなくなったため、私の視線はキャプチャに変わりました。 ここでは、2Dピクチャ、3Dピクチャ、サウンドキャプチャ、「正しい」ピクチャの選択といった、キャプチャが存在しないオリジナルを思いつくことができるようです。 しかし、私は、キャプチャの作成者が何らかの形で一方的に考えていること、つまり誰もがユーザーから(そして通常はテストフィールドで)一意に正しい答えを取得したいと考え、単純な形式でサーバーが答えを元のデータと比較するだけでよいと思いました! そこで、この問題を修正して、独自の「スマート」キャプチャを作成することにしました。





エントリー


私のアイデアはシンプルです。正方形、円形、三角形などの単純な形状をキャプチャの形でユーザーに表示し、ユーザーにこれらの同じ形状を描画するように依頼します。 もちろん、3つの数字では十分ではありませんが、任意の数量で組み合わせて表示できます。 そして、サーバー上で、ユーザーの応答を受け入れ(既に推測しているように)、ニューラルネットワークを使用してこれらの画像を認識します(そのため、3つの出力のみを使用して、ニューラルネットワークを容易にするためにこれまで3つの数字で停止しました)。

考え直すことなく、私はこのアイデアを実装し始めました。そして、このプロセスを記事で説明します...



実装


クライアント側から実装を始めましょう。

ファッションに敬意を表して、私はjavascript jQueryフレームワークを使用します。 タスクをさらに容易にするために、描画のコンテナとして使用されます。

すでにjQueryを使用することを決定しているため、コードの美しさと便利さのために、すべてをプラグインの形で整理します。同時に、この経験は初心者にとっても役立ちます。 最終的な形式では、次のようになります。

<!DOCTYPE html> <html> <head> <title>Demo NNCaptcha</title> <script src="http://code.jquery.com/jquery-latest.js"></script> <script type="text/javascript" src="nnCaptcha.js"></script> </head> <body> <div id="nnCaptcha"></div> <script type="text/javascript"> $(document).ready(function(){ //  $("#nnCaptcha").nnCaptcha(); }); </script> </body> </html>
      
      





プラグインの空の「空白」から始めましょう。

 (function($) { //    var methods = { //   init:function(params) { var options = $.extend({}, defaults, params); } }; $.fn.nnCaptcha = function(method){ if (this.length != 1) { $.error('not 1 element!'); return; } //   if ( methods[method] ) { //    ,    //  ,       // this      return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { //     ,    //    return methods.init.apply( this, arguments ); } else { //     $.error( ' "' + method + '"  ' ); } }; })(jQuery);
      
      







以下に、jqueryの典型的なプラグインを示します。これにより、内部メソッドを呼び出してパラメーターを渡すことができます。次に例を示します。

 $("#nnCaptcha").nnCaptcha('reset',1); //  reset   1 (   “”)
      
      





メインの初期化メソッドを見てみましょう。 その中で、N列のテーブルを作成し、2行で、上部に画像を表示し、下部にユーザーがそれらを描画します。 必要なすべてのハンドラーをハングアップし、すべてをドキュメントのDOMに入れます。



テーブルを構築することから始めましょう。 簡単な方法(既成のhtmlコードを挿入)ではなく、$()関数の機能を知っているため、その場でテーブルを作成しました。 結果は、可変数の列を持つテーブルを構築するかなり重要な方法でした:

  //     table this.append('<table class="tbCaptcha"><tbody></tbody></table>'); //   2   countCanvas  this.find("table.tbCaptcha").find('tbody') .append(function($){ var tr = $('<tr>'); for (i=0;i<countCanvas;i++) tr.append($('<td>') .append($('<img>') .attr('src', 'nnCaptcha.php?image=get') ) ) return tr; }($) ).append(function($){ var tr = $('<tr>'); for (i=0;i<countCanvas;i++) tr.append($('<td>') .append($('<canvas>') .attr('class', 'captcha') ) ) return tr; }($) );
      
      





私の方法が誰かに役立つことを願っています。特に、コードを終了して任意の行数を追加する場合は。



ここで最も興味深いのは、キャンバスに描画する機能を実装することです。

主なものは、キャンバスコンテキストオブジェクトを取得することです。

 var ctx = canvas.getContext("2d");
      
      





そして、線を描くには、これらのメソッドのみが必要です。

 beginPath(); //         moveTo(x, y); //      lineTo(x, y); //       stroke(); // 
      
      





mousedown、mouseup、mousemoveのイベントにそれらを正しく挿入するためだけに残ります。途中で他のアクションを実行します。

  //     canvas this.find("canvas.captcha").each(function(i) { this.width = width; this.height = height; var ctx = this.getContext("2d"); var elem = this; elCanvas[i] = elem; var drawing = false; pixCanvas[i] = createArrayPix(); //      $(this).bind("mousedown.nnCaptcha",function(e){ //    canvas var offset = $(elem).offset(); var x = e.pageX - offset.left; var y = e.pageY - offset.top; //      canvas pixCanvas[i][x][y] = 1; ctx.beginPath(); ctx.strokeStyle = options.selectedColor; ctx.lineWidth = options.selectedWidth; ctx.moveTo(x, y); drawing = true; elem.style.cursor = 'crosshair'; }); $(this).bind("mouseup.nnCaptcha",function(e){ if (drawing) { var offset = $(elem).offset(); var x = e.pageX - offset.left; var y = e.pageY - offset.top; ctx.lineTo(x, y); drawing = false; pixCanvas[i][x][y] = 1; } }); $(this).bind("mousemove.nnCaptcha",function(e){ if (drawing) { var offset = $(elem).offset(); var x = e.pageX - offset.left; var y = e.pageY - offset.top; ctx.lineTo(x, y); ctx.stroke(); ctx.moveTo(x, y); pixCanvas[i][x][y] = 1; } elem.style.cursor = 'crosshair'; }); });
      
      





プラグインのinitメソッドの準備ができたので、すべてが機能するように、グローバル変数と関数をプラグインに追加します。

  //    var defaults = { selectedColor:'#000000',selectedWidth:1 }; var countCanvas = 3; var elCanvas = []; var width = 100; var height = 100; var pixCanvas = []; function createArrayPix(){ var a=new Array (width); for (i = 0; i < width; i++) { a[i]=new Array (height); for (j = 0; j < height; j++) { a[i][j] = 0; } } return a; }
      
      





これらが追加される理由を説明します。 ユーザーがキャンバスに描画するときに、描画された(マウスが歩いた)すべての座標がバイナリマトリックスに入力されるようにするアクション-pixCanvas [Ncanvas] [x] [y] (「1」-座標「drawn」、「0」-いいえ)

もちろん、次の方法を使用して、PNGのキャンバスに画像を保存する方がはるかに簡単でした。

 canvas.toDataURL("image/png");
      
      





base64のキャンバスイメージをデータの形式で返します。サーバーに送信してデコードできるURLですが、ニューラルネットワークへの入力を取得するには、サーバー上のPNGイメージを処理する必要があることがわかります。

クライアント側では、バイナリマトリックスをすぐに作成して、ほぼ同じ方法でサーバーに送信する方が簡単です。

 ajax:function(n) { $.post("test.php", { matrix: JSON.stringify(pixCanvas[n]) } ); }
      
      





もちろん、ajaxなしでも実行できます。フォームに非表示の入力を作成し、そこにjson文字列を配置します。

次に、すべてのプラグインコードをまとめて収集します。

 (function($) { //    var defaults = { selectedColor:'#000000',selectedWidth:1 }; var countCanvas = 3; var elCanvas = []; var width = 100; var height = 100; var pixCanvas = []; function createArrayPix(){ var a=new Array (width); for (i = 0; i < width; i++) { a[i]=new Array (height); for (j = 0; j < height; j++) { a[i][j] = 0; } } return a; } //    var methods = { //   init:function(params) { //  ,      var options = $.extend({}, defaults, params); //     table this.append('<table class="tbCaptcha"><tbody></tbody></table>'); //   2   countCanvas  this.find("table.tbCaptcha").find('tbody') .append(function($){ var tr = $('<tr>'); for (i=0;i<countCanvas;i++) tr.append($('<td>') .append($('<img>') .attr('src', 'nnCaptcha.php?image=get') ) ) return tr; }($) ).append(function($){ var tr = $('<tr>'); for (i=0;i<countCanvas;i++) tr.append($('<td>') .append($('<canvas>') .attr('class', 'captcha') ) ) return tr; }($) ); //     canvas this.find("canvas.captcha").each(function(i) { this.width = width; this.height = height; var ctx = this.getContext("2d"); var elem = this; elCanvas[i] = elem; var drawing = false; pixCanvas[i] = createArrayPix(); $(this).bind("mousedown.nnCaptcha",function(e){ var offset = $(elem).offset(); var x = e.pageX - offset.left; var y = e.pageY - offset.top; pixCanvas[i][x][y] = 1; ctx.beginPath(); ctx.strokeStyle = options.selectedColor; ctx.lineWidth = options.selectedWidth; ctx.moveTo(x, y); drawing = true; elem.style.cursor = 'crosshair'; }); $(this).bind("mouseup.nnCaptcha",function(e){ if (drawing) { var offset = $(elem).offset(); var x = e.pageX - offset.left; var y = e.pageY - offset.top; ctx.lineTo(x, y); drawing = false; pixCanvas[i][x][y] = 1; } }); $(this).bind("mousemove.nnCaptcha",function(e){ if (drawing) { var offset = $(elem).offset(); var x = e.pageX - offset.left; var y = e.pageY - offset.top; ctx.lineTo(x, y); ctx.stroke(); ctx.moveTo(x, y); pixCanvas[i][x][y] = 1; } elem.style.cursor = 'crosshair'; }); }); return this; }, test:function(n,x,y) { alert(pixCanvas[n][x][y]); }, ajax:function(n) { $.post("test.php", { name: JSON.stringify(pixCanvas[n]) } ); }, //  reset:function(n) { //alert(n); if (n !== undefined){ var c = elCanvas[n]; var ctx = c.getContext("2d"); ctx.clearRect(0, 0, c.width, c.height); pixCanvas[n] = createArrayPix(); } else { $.each(elCanvas,function(i) { var ctx = this.getContext("2d"); ctx.clearRect(0, 0, this.width, this.height); pixCanvas[i] = createArrayPix(); }); } } }; $.fn.nnCaptcha = function(method){ if (this.length != 1) { $.error('not 1 element!'); return; } //   if ( methods[method] ) { //    ,    //  ,       // this      return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { //     ,    //   init return methods.init.apply( this, arguments ); } else { //     $.error( ' "' + method + '"  ' ); } }; })(jQuery);
      
      





もちろん、多くはまだ「生」であり、私は常にすべてを仕上げていますが、私はその考えが明確だと思います。 ここでプラグインのライブ作品を見ることができます 。 このコードのもう1つの「欠点」は、現在のブラウザーと最新のブラウザーでのみ機能することです(たとえば、同じキャンバスまたは「JSON.stringify()」のため)。ただし、IE8のようなブラウザーのすべてを簡単に再作成できますが...



一般化


さらなる実践が示しているように、100x100の正方形を選択すると、ニューラルネットワークの入力数-10,000が得られます。これは非常に多くのことです。 おそらく、キャプチャの正方形は小さくなり(はい、少なくとも50x50)、すべてがサーバーの処理速度に依存します...



Googleのニューラルネットワークの実装として、「ANN-PHP 5.xの人工ニューラルネットワーク」を見つけました。

最初は、ネームスペースを使用してPHP 5.3の「ライブ」サンプル実装を使用するのは興味深いものでしたが、10入力3出力のネットワークをトレーニングしようとすると、10分以上かかり、実際のサンプルがどれだけトレーニングされるか想像できます。 一般的に、ここで自転車を作り直す必要があるかもしれません。 もちろん、誰にでもよく知られているFANNライブラリを使用できますが、第一にPHP 5.3とはほとんど友達ではありません。第二に、Windowsにはコンパイル済みのソリューションがなく(そして実際には必要ありません)、ホスティングプロバイダーはホスティングにインストールしませんが、VDSはまだありません。 また、キャプチャを配布する場合、FANNは要件を入力する必要があります。

サーバー上のPHPにバインドする必要はありませんが、他のサーバー言語の利点で十分です...

キャプチャに戻りますが、この形式ではボットで簡単に破損することは明らかです。3つの「正しい」バイナリマトリックスをサーバーに送信するだけです(タスクは非常に単純なので、図の認識については言及しません)。 もちろん、サーバー上では、0.95を超える確率から結果から除外できます(ユーザーはPicassoを描画する可能性が低く、ボットのみがマウスで正しく描画できます)が、信頼性のために、サーバーが発行するキー関数でマトリックスを暗号化する必要があると思います(必ずしも同じではありません)、当然、判読不能でパックされた形式です。

これで、ボットが画像を正しく認識しても、正しいバイナリマトリックスをサーバーに送信できなくなります。

その後、さらに多くを思いつくことができます:

  1. 描画される図形の歪み。
  2. 異なるブロックの異なる色。
  3. バスティングからのセッション保護。
  4. 入れ子になった図形(および描画する必要があります);
およびその他の機能。

もちろん、答えがjavascriptによって形成されていることは既にです。少なくとも、原始的なスパムボットからの保護はすでに存在しています。 誰かがこの特定のキャプチャのハッキングに意図的に従事するまで、特に暗号アルゴリズムのハッキングの置き換え/列挙を提供する場合(非常に多くを発明することができます)、一部のスパムボットがそれをバイパスすることはほとんどありません。



おわりに


記事はもちろん粗雑であることが判明しましたが、キャプチャを書くプロセスはまだ進行中であり、私の頭の中で常に新しい考えが生じています。 したがって、この記事は間違いなく最後の記事ではありません。キャプチャの保護、「正しい」実装の強化、およびハッキングの方法についてのコメントや提案を聞く準備ができています。



PSキャプチャの写真は、一例としてPaint'eで描かれています! 生成アルゴリズムはまだ発明されていません。



All Articles