Nexus 5 + JavaScript + 48時間=タッチ面?

数週間前、ミンスクでWTH.BYハッカソンが開催され、そこで参加することにしました。 彼の主なアイデアは、開発者向けのハッカソンであるということでした。 面白くて面白くしたいことは何でもできます。 収益化、投資、助言者なし。 すべてが楽しくてクールです!



実装に関する多くのアイデアがありましたが、それらはすべて「なんてこった!」のようなものには達しませんでした。 そのため、イベントの前夜に、DIYセクションの古いHabrの記事をめくって、「 マルチタッチテーブルの作成の経験 」という記事に出会いました。 これが非常に不足している「Wow!」の原因でした。そして、私は手元にあるものから遠いアナログを作ることにしました。



私の指先では、A3形式のガラス、普通紙、マーカー、携帯電話、ラップトップについてでした。 私はすぐにYegorの共犯者を見つけ、積極的な仕事を始めました。



 .   .    .     .





一般に、システムがタッチを認識するタッチサーフェスを作成することが決定されました。 これを行うために、私は家から普通のガラス、紙、マーカーを盗みました。 2枚の本にガラスを置き、テープを貼った紙を接着し、フロントカメラを上にした状態で電話を置きました。 カメラは下から画像をキャプチャし、連絡先の画像を認識してラップトップに転送します。 すでにその過程で、アイデアはわずかに変化しました。紙の上にマーカーで描かれたボタンを認識し、それらのクリックを判別することです。 まず第一に、これは手の影が原因で正確なタッチの場所を認識することが問題であるという事実のために起こりました。 しかし、マーカーで描かれたボタンははっきりと表示され、画像上で簡単に選択できました。



    .    .

私のプログラミングプロファイルがJavaScriptであることを考慮して、電話で開くWebページにすることにしました。 フロントカメラからのビデオ画像がキャプチャされ、ボタンが認識され、クリックが期待されます。 イベントが発生すると、ソケットを使用してラップトップ上の別のページに情報が送信され、注文したいことを行います。



このようなシステムは、いくつかの論理的な部分に分割できます。





各部分についてもう少し詳しく見てみましょう。



ビデオキャプチャー



getUserMediaメソッドを使用して、ビデオカメラから画像を取得し、それをビデオタグでブロードキャストできることは、あなたにとって秘密ではないでしょう。 したがって、ビデオタグを作成し、ユーザーにビデオをキャプチャする許可を求め、カメラで自分自身を確認します。



いくつかのコード
var video = (function() { var video = document.createElement("video"); video.setAttribute("width", options.width.toString()); video.setAttribute("height", options.height.toString()); video.className = (!options.showVideo) ? "hidden" : ""; video.setAttribute("loop", ""); video.setAttribute("muted", ""); container.appendChild(video); return video })(), initVideo = function() { // initialize web camera or upload video video.addEventListener('loadeddata', startLoop); window.navigator.webkitGetUserMedia({video: true}, function(stream) { try { video.src = window.URL.createObjectURL(stream); } catch (error) { video.src = stream; } setTimeout(function() { video.play(); }, 500); }, function (error) {}); }; //... initVideo();
      
      









ビデオから単一のフレームを取得するには、canvasとdrawImageメソッドを使用します。 このメソッドは、ビデオタグを最初のパラメーターとして使用し、キャンバス内の指定されたビデオから現在のフレームを描画できます。 これがまさに必要なものです。 この操作を定期的に繰り返します。



 var captureFrame = function() { ctx.drawImage(video, 0, 0, options.width, options.height); return ctx.getImageData(0, 0, options.width, options.height); }; window.setInterval(function() { captureFrame(); }, 50);
      
      







画像の前処理



これで、canvas要素があり、その中にビデオストリームの現在のフレームがあります。 次のタスクは、描画されたボタンの認識です。

実際、ctx.getImageData(...)メソッドがデータを返すビューは、タスクを解決するにはまったく不便です。 したがって、輪郭の直接検索に進む前に、画像を便利な形式にします。



getImageDataメソッドは、各ピクセルのチャネルが順番に記述されている大きなデータ配列を返します。 そして、便利なフォーマットとは、ピクセルの2次元配列を意味します。 直感的で、作業がずっと楽です。



      -








データを便利な形式に変換する小さな関数を作成します。 紙を通過する画像は白黒に非常に似ていることに留意してください。 したがって、各ピクセルについて、チャネルの平均量を計算し、結果の配列に書き込みます。 その結果、各ピクセルが0〜255の値で表される配列を取得します。座標により、目的のピクセルを参照してその値を取得できます:data [y] [x]。











さらに進んで、ピクセルごとに255の値が多すぎると判断しました。 輪郭とクリックの認識には、1と0の2つの値で十分です。そのため、プロジェクトでは、ピクセルの配列と可変の制限を受け取るgetContours関数が登場しました。 特定のピクセルの値が変数の制限よりも大きい場合、ゼロ(明るいシート)に変わります。それ以外の場合は、1(アウトラインまたは指の一部)になります。











GetContours関数コード
 var getContours = function(matrix, limit) { var x, y; for (y = 0; y < options.height; y++) { for (x = 0; x < options.width; x++) { matrix[y][x] = (matrix[y][x] > limit) ? 0 : 1; } } return matrix; };
      
      









これで、画像は便利な形式で表示され、ボタンを見つける準備ができました。



     -. ,     .








輪郭検索



画像の輪郭やオブジェクトを認識したことがありますか? これをやったことがない。 クイックグーグルは、OpenCVが問題なくこれらの問題を解決する必要があることを示しました。 実際、移植されたライブラリにはいくつかの制限があり、分類子を訓練する必要があることが判明しました。 Grailsを使用してランディングページを作成するようなものでした。

これが、より単純なソリューションの検索を続け、バグアルゴリズムに出くわした理由です(これが一般的な名前であるかどうかはわかりませんが、記事ではそのように呼ばれています)。



このアルゴリズムにより、ゼロと1の配列内の閉ループを認識することができます。 重要な要件は、境界線が少なくとも2ピクセルの厚さであることでした。 そうしないと、アルゴリズムのロジックが無限ループに陥り、その後の結果がすべて発生します。 しかし、マーカーによって描かれたボタンの境界線は2ピクセルよりもはるかに太かったので、これは問題にはなりませんでした。 アルゴリズムの残りは非常に簡単です:















したがって、入力を受け取って回路を見つける関数があります。 単純化するために、タスクは長方形に制限されていました。 したがって、輪郭点から2つの境界点を見つけます。 ボタンの形状に関係なく、ボタンが内接する長方形を取得します。



  ,








しかし、ワンボタンインターフェイスが必要なのは誰ですか? もしそうなら、完全に! そのため、描画されたすべてのボタンを見つけるというタスクが発生しました。 解決策は単純であることが判明しました。ボタンを見つけて配列に格納し、データ内のボタンで長方形をゼロで埋めます。 配列が空になるまで検索を繰り返します。 その結果、見つかったすべてのボタンを含む配列を取得します。











ところで、アルゴリズムのテスト中に、1枚のガラスが破損しました。 幸いなことに、夕方遅くに家に帰りました。 朝、私自宅の窓から別の窓を取り出し 、開発を続けました。



 . ,     .    ,    .








指がループ内にあるかどうかの判別



ボタンを押すとどうなりますか? すべてがシンプルであることが判明しました。 ボタンが見つかったら、その中の黒い点の合計を計算します。 このボタンハッシュを自分で呼び出しました。 そのため、ボタンを押すと、ボタンのハッシュが目立った量だけ増加し、ランダムノイズ、干渉、および紙と電話の最小の相互移動を明らかに超えます。 各フレームで、既存のボタンのハッシュを読み取り、元の値と比較する必要があることがわかります。





  .    .








これがタッチスクリーンです。



ボーリングモード
もちろん、探究心は、そのようなアプローチが誤検知の大きな範囲であることを理解します。 誤って近くのボタンの上に影を作成した場合、それも押されます。

まあ、一般的には、はい。 これに対処するには、追加のチェックを設定します。 たとえば、ゼロと1から2番目のデータの配列を作成できますが、黒の制限はより厳しくなります。 その場合、画像には「最も黒い」色のみが残ります。 これにより、指が紙に触れた場所だけがデータに残り、影がなくなると想定できます。

さて、または、あなたはハッカソンのルールを使用して「あなたがしたいことをして」、それが意図されていると言うことができます。





クライアントページにイベントを渡す



Socket.ioが何であるかは誰もが知っていると確信しています。 まだわからない場合は、 サイトhttp://socket.io/でそれらを読むことができます。 要するに、これはnode.jsサーバーとクライアントの間で双方向のデータ交換を可能にするライブラリです。 私たちの場合、これらを使用して、最小限の遅延でイベント情報をサーバー経由で別のWebページに送信します。



映像



コメントの質問、ビデオはどこにあるかを待つことなく、システムの動作を示すビデオを紹介します。







結論







便利なリンク






All Articles