あなたに次の選択肢を用意するサイト
Thoth Coworker
~ プログラミングの次++ ~
For
新社会人 / 新学生 / 新院生 / 新研究者
カテゴリ
WebサイトにQRコードスキャナを載せる
Facebookシェア Twitterツイート LINEで送る
P
ポイント
WebサイトにQRコードスキャナを載せる
とりあえずWebサイトにQRコードスキャナを搭載してみるための記事
  • Point 1
    WebサイトでQRコード読み取り
    ユーザがスマホにQRコードリーダーを持っていなくてもWebブラウザだけでQRコードの読み取りが可能です
  • Point 2
    画像内でのQRコードの位置も取得
    どこで作られても同じ文字列からは同じQRコードが作られます.また作るのに計算資源は必要ないためブラウザで作成することができます
  • Point 3
    jsQR.jsを使用
    WebブラウザでQRコードをスキャンできるjsQR.jsを使用します.他にもこういったライブラリはありますが、どれも同程度の使いやすさです.
P
ステップ概要
WebサイトにQRコードスキャナを載せる
とりあえずQRコードスキャナをブラウザで動かします.
読み取るだけでなくコードがどこにあるかを描画します
どういった処理になっているかを確認します.
アップロードしたファイルのQRコードを読み取って出力します
ファイルのQRコードを読み取って処理するところを追います.
Step
1
とりあえずWebサイトにQRコードスキャナを載せる
今回はjsQR.jsを用いてWebカメラに移ったQRコードをブラウザで読み取って結果を表示するプログラムを作成します.
今回は下記のサンプルに基づいて解説を行なっています.
: ブラウザで開く
https://github.com/cozmo/jsQR/tree/master/docs
まずはこれから用意するディレクトリ構成です.
今回Webサーバは立ち上げずに行います.
Folder : WebカメラQRコードを読み取って結果を表示するためのフォルダ構成
index.html
jsQR.js
jsQR.jsについては下記で取得して保存してください.
jsQR.jsのGithubソース
それでは今回使うindex.htmlのソースです.
下記をメモ帳でindex.htmlとして保存してください.
HTML : Webカメラで撮像した画像からQRコードを読み取るソース
index.html
    
<html>
  <head>
    <script src="./jsQR.js"></script>
  </head>
  <body style="max-width: 640px;margin: 0 auto;">
    <canvas id="canvas" style="width:100%"></canvas>
    <div id="output">
      <div id="outputMessage">No QR code detected.</div>
    </div>
    <script>
      var video = document.createElement("video");
      var canvasElement = document.getElementById("canvas");
      var canvas = canvasElement.getContext("2d");

      navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) {
          video.srcObject = stream;
          video.setAttribute("playsinline", true);
          video.play();
          requestAnimationFrame(tick);
      });
	
      function tick() {
          if (video.readyState === video.HAVE_ENOUGH_DATA) {
              canvasElement.height = video.videoHeight;
              canvasElement.width = video.videoWidth;
              canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
              var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
              var code = jsQR(imageData.data, imageData.width, imageData.height, {
                  inversionAttempts: "dontInvert",
              });
              if (code) {
                  document.getElementById("outputMessage").innerText = code.data;
              } else {
                  document.getElementById("outputMessage").innerText = "No QR code detected.";
              }
          }
          requestAnimationFrame(tick);
      }
    </script>
  </body>
</html>
    
  
上記ができたら、index.htmlをWebブラウザにドラッグアンドドロップしてみてください.
Webカメラの取得を許可すると画像の取得が始まります.もしQRコードが認識されればその結果が文字列と表示されます.

index.htmlをドラッグ&ドロップしてスキャンしたとき
これでとりあえずWebサイトでQRコードをスキャンするコードは作れました.
Step
2
コード解説:ビデオの読み取りとQRコードの認識
先のコードについて解説します.
このコードは全体として以下のようになっています.

スキャンするときの処理の概要

navigator.mediaDevices.getUserMediaにてvideoにWebカメラの画像を流すように設定して、自動再生するように設定します. その後何度も呼ばれることになるtick関数を呼びます.

    
      navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) {
          //videoにWebカメラで流れてくる情報(stream)を与えることを設定
          video.srcObject = stream;

          //再生領域内で再生
          video.setAttribute("playsinline", true);

          //ビデオを再生を開始
          video.play();

          //画面更新時にtick関数を呼ぶように設定
          requestAnimationFrame(tick);
      });
    
  
tick関数ではcanvasにvideoで得られた画像を取り出して貼り付けて描画します.
この処理をすることでvideoのあるフレームの画像が切り出せてImageDataを取り出せます.
取り出した画像をjsQRに渡して、もし結果が得られればそれを読み取り結果文字列として表示しています.
    
      function tick() {
          if (video.readyState === video.HAVE_ENOUGH_DATA) {
              //canvasのサイズをビデオサイズに統一
              canvasElement.height = video.videoHeight;
              canvasElement.width = video.videoWidth;

              //canvasにvideoのあるフレームを貼り付け
              canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);

              //videoから取り出したデータをimageDataに変換
              var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);

              //jsQRでQRコード読み取り処理を実施
              var code = jsQR(imageData.data, imageData.width, imageData.height, {
                  inversionAttempts: "dontInvert",
              });

              //読み取ったQRコードで結果表示を切り替え
              if (code) {
                  document.getElementById("outputMessage").innerText = code.data;
              } else {
                  document.getElementById("outputMessage").innerText = "No QR code detected.";
              }
          }
          requestAnimationFrame(tick);
      }
    
  
処理は非常にシンプルだったかと思います.
大事なのは最後のjsQRの関数を読んで処理しているところです.
Step
3
読み取った領域をQRコード画像の上に描画する
次に検出した領域を赤線で括るサンプルに変更します.
先ほどのコードに赤線を書く箇所を書きたします.
HTML : Webカメラで撮像した画像からQRコードを読み取り赤枠で囲むソース
index.html
    
<html>
  <head>
    <script src="./jsQR.js"></script>
  </head>
  <body style="max-width: 640px;margin: 0 auto;">
    <canvas id="canvas" style="width:100%"></canvas>
    <div id="output">
      <div id="outputMessage">No QR code detected.</div>
    </div>
    <script>
      var video = document.createElement("video");
      var canvasElement = document.getElementById("canvas");
      var canvas = canvasElement.getContext("2d");

      function drawLine(begin, end, color) {
	  canvas.beginPath();
	  canvas.moveTo(begin.x, begin.y);
	  canvas.lineTo(end.x, end.y);
	  canvas.lineWidth = 4;
	  canvas.strokeStyle = color;
	  canvas.stroke();
      }
      
      navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) {
	  video.srcObject = stream;
	  video.setAttribute("playsinline", true);
	  video.play();
	  requestAnimationFrame(tick);
      });
      
      function tick() {
	  if (video.readyState === video.HAVE_ENOUGH_DATA) {
              canvasElement.height = video.videoHeight;
              canvasElement.width = video.videoWidth;
              canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
              var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
              var code = jsQR(imageData.data, imageData.width, imageData.height, {
		  inversionAttempts: "dontInvert",
              });
              if (code) {
		  drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58");
		  drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58");
		  drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58");
		  drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58");
		  document.getElementById("outputMessage").innerText = code.data;
              } else {
		  document.getElementById("outputMessage").innerText = "No QR code detected.";
              }
	  }
	  requestAnimationFrame(tick);
      }
    </script>
  </body>
</html>
    
  
drawLine関数とそれを呼び出しているところが今回書き足した部分です.
ここではjsQRの返り値codeの中からcodeのlocation情報を取り出してそれをcanvas上で赤線として記載しています. 4隅の座標が得られているので適切に四角形が描画できます.

スキャン結果
Step
4
アップロードしたファイルのQRコードを読み取る
次はWebカメラではなく、自分の持っている画像ファイルをアップロードして読み取るサンプルです.
ユーザは自分でファイルを選択して
HTML : パソコンから選んだ画像からQRコードを読み取り赤枠で囲むソース
index.html
    
<html>
  <head>
    <script src="./jsQR.js"></script>
  </head>
  <body style="max-width: 640px;margin: 0 auto;">
    <canvas id="canvas" style="width:100%"></canvas>
    <input type="file" id="fileInput" name="file" accept="image/*" />
    <div id="output">
      <div id="outputMessage">No QR code detected.</div>
    </div>
    <script>
      var canvasElement = document.getElementById("canvas");
      var canvas = canvasElement.getContext("2d");
      var img = new Image();

      function drawLine(begin, end, color) {
	  canvas.beginPath();
	  canvas.moveTo(begin.x, begin.y);
	  canvas.lineTo(end.x, end.y);
	  canvas.lineWidth = 4;
	  canvas.strokeStyle = color;
	  canvas.stroke();
      }
      
      function recog() {	  
          var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
          var code = jsQR(imageData.data, imageData.width, imageData.height, {
	      inversionAttempts: "dontInvert",
          });
          if (code) {
	      drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58");
	      drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58");
	      drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58");
	      drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58");
	      document.getElementById("outputMessage").innerText = code.data;
          } else {
	      document.getElementById("outputMessage").innerText = "No QR code detected.";
          }
      }

      document.getElementById("fileInput").addEventListener('change', (e) => {
          let files = e.target.files;
          if (files.length > 0) {
	      img.src = URL.createObjectURL(files[0]);
	      img.onload = function() {
		  canvasElement.width = img.width;
		  canvasElement.height = img.height;
		  canvas.drawImage(img, 0, 0, canvasElement.width, canvasElement.height);
		  recog();
	      };
          }
      }, false);
      
    </script>
  </body>
</html>
    
  
実際に実行すると以下のように適切に読み込めていることがわかります.
スキャン結果
Step
5
コード解説:画像の読み込みとQRコードの認識
ビデオの時からの変更点は多くありません.
ビデオの読み込み関連を削除して、ファイルの読み込みの完了を受け取るコールバックを設定したのみです.

下記の箇所でファイルの読み込みとそれが完了したら一時的にImageオブジェクトに書き込んでいます.その書き込みが完了したらcanvasに書き写しています. その後recog関数を呼び出して認識と結果の書き込みを行なっています. もともとtick関数だったものをrecog関数としてだけでコードの変更はありません.
    
      //fileInputのinputタグで更新があったら
      document.getElementById("fileInput").addEventListener('change', (e) => {
          let files = e.target.files;

          //fileInputからファイルが取得できたら
          if (files.length > 0) {
              //imgオブジェクトにURL形式に変換して代入
              img.src = URL.createObjectURL(files[0]);

              //imgオブジェクトで上記の画像が読み込みが終わったら
              img.onload = function() {
                  //canvasのサイズを変更して画像を書き写します.
                  canvasElement.width = img.width;
		  canvasElement.height = img.height;
		  canvas.drawImage(img, 0, 0, canvasElement.width, canvasElement.height);

                  //canvasに書かれた画像を認識します
                  recog();
	      };
          }
      }, false);
    
  
Done