ประยุกต์ scan qrcode ผ่าน webcam โดยใช้ jsQR Library

เขียนเมื่อ 3 ปีก่อน โดย Ninenik Narkdee
qrcode qrcode scanner jsqr

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ qrcode qrcode scanner jsqr

ดูแล้ว 20,870 ครั้ง


เนื้อหาตอนต่อไปนี้ จะให้แนวทางการประยุกต์ใช้งาน
jsQR Library หรือ QR code reading library ที่ใช้สำหรับอ่านค่า
qrcode ดูข้อมูลเพิ่มเติมได้ที่ jsQR
    
    ในเว็บไซต์จะมี ตัวอย่าง deomo ที่เราสามารถนำมาปรับใช้งานได้เลย เป็นการใช้งาน
webcam หรือกล้องของเรา ให้ทำการจับภาพจาก stream ของ video มาแสดงในเว็บ
จากนั้นตรวจสอบ หรือ detect qrcode จากภาพนั้นๆ ถ้ามี และแสดงค่าออกมา
    jsQR เป้น pure javascript หรือที่เรียกว่าโค้ด javascript ล้วนๆ ไม่จำเป้นต้องใช้งานหรือ
เรียกใช้ library อื่นๆ เข้ามาช่วย ลักษณะการทำงานแบบ standalone  แต่เราก็ยังสามารถนำไป
ประยุกต์ใช้เพิ่มเติมกับ library อื่นๆ ได้ เช่น 
    ใช้งานร่วมกับ jquery ในการใช้ ajax ส่งค่าไปดึงข้อมูล API อีกที 
    หรือจะใช้ Fetch API ของ javascript รับส่งข้อมูล api แทนก็ได้เหมือนกัน   
 
 

การทำงานของ jsQR Library

    jsQR จะทำการอ่านข้อมูลรูปภาพหรือที่เรียกว่า imageData แล้วทำการแปลงข้อมูลนั้น โดยตรวจจับ qrcode ในข้อมูลรูป
ถ้ามี  ก็จะส่งค่านั้นกลับออกมา มี property คร่าวๆ ที่ jsQR ส่งกลับออกมา ได้แก่
    ค่า binaryData หรือข้อมูล binary 
    ค่า data คือข้อมูล qrcode หรือก็คือค่าข้อมูล ข้อความ ตัวเลข ที่เราสร้างเป็น qrcode นั่นเอง
    ค่า location คือตำแหน่งพิกัดของ qrcode ในรูป หรือก็คือจุดของเส้นขอบของ qrcode ในรูป
 
    หลักๆ แล้วค่า data เป็นข้อมูล qrcode ที่เราต้องการนำไปใช้งานต่อ สำหรับค่า location ถ้าเราสังเกตการทำงานในตัวอย่าง
demo จะเห็นว่าถูกนำมาใช้สร้างเป็นกรอบ เวลาที่เราทำการ scan เพื่อบ่งบอกว่าตรวจจับ qrcode ในรูปภาพที่ตำแหน่งใด 
 
    มาดูการทำงานของ demo ต้นฉบับกัน 
    สามารถ view source ดูคำสั่งการทำงานได้
 
    เริ่มต้นตัวโปรแกรมทำการสร้าง video element ขึ้นมาก่อน
 
var video = document.createElement("video");
    จากนั้นก็ทำการขอสิทธิ์ใช้งาน video ผ่าน mediaDevices ด้วยคำสั่ง getUserMedia()
 
// Use facingMode: environment to attemt to get the front camera on phones
navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) {
  video.srcObject = stream;
  video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
  video.play();
  requestAnimationFrame(tick);
});
    การขอสิทธิ์ใช้งานกล้อง หากใช้ระบบออนไลน์ผ่านเว็บไซต์ จะต้องทำผ่าน https 
    สังเกตรูปแบบการทำงาน จะเห็นว่าเป็นแบบ async (asynchronous) เมื่อทำการขอสิทธิ์การใช้งานไปแล้ว ก็ต้องรอ
โดยการใช้งาน Promise  
    มีการกำหนด option การใช้งาน video เช่น ตัวอย่าง ใช้งานแค่ภาพวิดีโออย่างเดียว ไม่ได้มีการกำหนดการใช้งาน
เสียงจากวิดีโอด้วย รวมถึงกำหนดว่าให้ใช้โหมดกล้องหน้า ถ้ามี หรือกล้องหลังก็ได้ ตามรูปแบบด้านบน ดูการกำหนด
การใช้งานเพิ่มเติมจากลิ้งค์นี้ getUserMedia
 
    เมื่อผู้ใช้อนุญาต ก็จะทำการกำหนดข้อมูลภาพวิดีโอผ่านข้อมูล stream และให้วิดีโอทำการเล่น นั่นก็คือการแสดงภาพวิดีโอจาก
กล้องที่เรากำลังเปิดใช้งานอยู่นั่นเอง
    หลังจากทำการจับภาพวิดีโอ ก็เริ่มทำการเรียกใช้ฟังก์ชั่น tick ผ่านการใช้งาน window.requestAnimationFrame()
 
requestAnimationFrame(tick);
// https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
    window.requestAnimationFrame() หรือเรียกใช้งานสั้นๆว่า requestAnimationFrame() เป็นการบอก บราวเซอร์ว่า เราจะทำ
การใช้งานการสร้าง animation หรือการเคลื่อนไหวสักอย่างหนึ่งขึ้น และให้บราวเซอร์ทำการเรียกใช้ฟังก์ชั่นที่ได้กำหนดไว้ ก่อนที่การ
เคลื่อนไหว หรือ animation ลำดับถัดไปจะเกิดขึ้น โดยใช้แฟรมเรทที่ 60 ครั้งใน 1 วินาทีเป้นค่าเริ่มต้น ทั้งนี้ก็จะขึ้นอยู่กับ refresh 
rate ของหน้าจอนั้นๆ ด้วย ถ้าเราเทียบกับรูปแบบการใช้งาน setInterval() ใน javascript ก็จะมีค่าจำนวนครั้งที่ทำงานใน 1 วินาที
เป็น 1000/60 หรือก็คือ setInterval(function(){},1000/60)  
    ข้อแตกต่างที่ดีกว่าระหว่างฟังก์ชั่น requestAnimationFrame() กับ setInterval() ก็คือเป็นเรทที่เหมาะกับการทำ animation 
เพราะจะได้ภาพหรือการแสดงที่ลื่นใหลหรือ smooth มากกว่าในช่วงเรทนี้ นอกจากนั้น requestAnimationFrame() ยังสามารถหยุด
ทำงานขั่วคราว ในหน้าต่างหรือแท็บที่ไม่ได้เปิดอยู่ หรือ iframe ที่ซ่อนอยู่ เพื่อประสิทธิภาพของการแสดงผล รวมถึงประหยัดการใช้
พลังงานของ battery ด้วย  ต่างจาก setInterval() ที่จะทำงานตลอดเวลาตามกำหนดเวลาที่ตั้งไว้แม้หน้าต่างนั้นจะไม่แสดงอยู่ก็ตาม
    รูปแบบการเรียกใช้ เป้นดังนี้
 
  <script>
  requestAnimationFrame(tick);  // เริ่มตั้นทำงาน
  function tick(){
	  // กำหนดคำสั่งตามต้องการ
	  // คำสั่งส่วนนี้จะทำซ้ำ 60 ครั้งใน 1 วินาที
    requestAnimationFrame(tick);   // วนลูปเข้าไปทำคำสั่งซ้ำเรื่อยๆ 
  }
  </script>
    ใน demo ใช้คำสั่งนี้เรียกใช้งานฟังก์ชั่น tick()
 

    การทำงานของฟังก์ชั่น tick()

 
<script>
    function tick() {
      loadingMessage.innerText = "⏳ Loading video..." // กำลังโหลดวิดีโอ
      if (video.readyState === video.HAVE_ENOUGH_DATA) { // ถ้าวิดีโอพร้อม
	  // ซ่อนแสดง element ต่างๆ
        loadingMessage.hidden = true;
        canvasElement.hidden = false;
        outputContainer.hidden = false;

		  // สร้าง canvas สำหรับวาดภาพหรือสร้างรูปภาพ กำหนดความกว้างความสูงเท่ากับ video
        canvasElement.height = video.videoHeight;
        canvasElement.width = video.videoWidth;
		// เอาภาพใน video เขียนลองใน canvas นั่นคือ รูปภาพจากวิดีโอถุูกวาดลง canvs ทุกๆ 60 ครั้งใน 1 วินาที
		// เป็นเหมือนการเกิด animation ขึ้นใน frame rate เท่ากับ 60
        canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
		// ดึงข้อมูลรูปภาพของ video ที่เขียนลง canvas มาใช้
        var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
		// แล้วเอาเข้าไปตรวจสอบหาค่า จาก qrcode ในรูปถ้ามี นั่นคือ ในขณะที่วิดีโอกำลังอยู่ภาพจะถูกส่งไปตรวจสอบ qrcode ตลอด
		// แล้วคืนค่ามาในตัวแปร code
        var code = jsQR(imageData.data, imageData.width, imageData.height, {
          inversionAttempts: "dontInvert",
        });
        if (code) {// ถ้ามีข้อมุล qrcode
		// ทำการวดรูปกรอบสี่เหลียม ตามตำแหน่ง qrcode ที่พบในรูปลงไปในรูปใน canvas ใช้ฟังก์ชั่น drawLine()
          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");
		  // ซ่อน แสดงส่วน element ข้อมูล qrcode
          outputMessage.hidden = true;
          outputData.parentElement.hidden = false;
          outputData.innerText = code.data;
        } else {
		// ถ้าไม่พบ qrcode หรือเอา qrcode ออกจาหน้ากล้อง กำหนดซ่อนแสดง ข้อความแจ้ง
          outputMessage.hidden = false;
          outputData.parentElement.hidden = true;
        }
      }
      requestAnimationFrame(tick); // วนลูปทำซ้ำ
    }
</script>
 
    ส่วนของฟังก์ชั่นวาดกรอบสี่เหลี่ยมสีแดงครอบ qrcode ในรุป
 
    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();
    }
    ข้างต้นเป็นบางส่วนของการใช้งานใน demo เราจะเห็นว่า qrcode ถุก detect จากรูปภาพตลอดเวลา ถึงแม้จะตรวจพบข้อมูล
และแสดงข้อมูลแล้วก็ตาม หากจะนำเอา demo ไปใช้งานโดยตรงก็ไม่น่าจะได้ เพราะสิ่งที่เราต้องการคือ ต้องมีช่วงหยุดของการ 
scan ต้องให้ข้อมูลที่ถูก detect แล้วแสดงแค่ครั้งเดียว ใน demo ข้อมูลส่งอออกมา 60 ครั้งใน 1 วินาทีโดยไม่ได้หยุด เราจะเอา
ข้อมูลไปใช้ ก็จะไม่มีจังหวะที่เหมาะสม เพราะข้อมุลไม่นิ่ง เราจะประยุกต์ใช้งานเพิ่มเติม ตามหัวถัดไปด้านล่าง

 
 

การประยุกต์ใช้งาน jsQR สแกน qrcode ผ่าน webcam

    การประยุกต์ใช้งานนี้ นอกจากจะใช้เป็นตัวสแกน qrcode ผ่าน webcam หรือกล้องของโน้ดบุ็ค หรือ pc แล้วแต่อุปกรณ์แล้ว
เรายังสามารถใช้ในมือถือ เป็นเสมือนเครื่องสแกน qrcode ได้อีกด้วย ในที่นี้เราจะให้แนวทางเกี่ยวกับการใช้งานกับ webcam
เช่น สมมติเราตั้งเครื่องโน้ตบุ็คหรือ pc ไว้ และเปิด webcam หรือกล้องให้ detect qrcode จากผู้ใช้ ที่อาจจะเป็นบัตร หรือรูป
จากมือถือ เพื่อทำการลงทะเบียน เข้าร่วมกิจกรรม / เช็คชื่อ / ลงเวลาทำงาน หรืออื่นๆ แล้วแต่จะใช้งาน
    ข้อมูล qrcode อาจจะเป็นข้อมูลเข้ารหัส เพื่อใช้อ้างอิงถึงผู้ใช้นั้นๆ โดยทำการสร้างเป็น qrcode สำหรับผู้ใช้แต่ละคนไว้แล้ว
เมื่อทำการ อ่านค่า qrcode เราก็ทำการส่งค่าที่ได้ไปดึงข้อมูลของผู้ใช้นั้นไปใช้งานต่อ แนวทางการประยุกต์จะประมาณนี้
    * jsQR จะรองรับเฉพาะ QR code มาตรฐานเท่านั้น ไม่รองรับ QR code แบบ 2D barcode หรือที่เรียกว่า Data Matrix อย่าง
ที่แสดงในล็อตเตอรรี่ เป็นต้น
    สิ่งที่เราจะประยุกต์จาก demo คือ
    - เมื่อสแกนแล้ว จะหยุดการทำงานของวิดีโอชั่วคราว 5 วินาที หรือแล้วแต่เรากำหนด เพื่อให้ทำการส่งค่า หรือใช้งานค่าจาก
qrcode นั้นหากต้องการ เช่น ไปดึงข้อมูลผ่าน ajax ไปจัดการต่อ เป็นต้น
    - จะให้มีเสียงสแกนทุกๆ ครั้งที่อ่านค่าข้อมูลได้
    - ในขณะที่หยุดภาพสแกน qrcode ล่าสุด ให้แสดงกรอบในส่วนของ qrcode ในรูปนั้นๆ ด้วย
    - หลังจากหยุดชั่วคราว 5 วินาที ก็จะเริ่ม detect qrcode ใหม่สำหรับผู้ใช้คนต่อไป 
 
    สามารถดาวน์โหลดไฟล์ตัวอย่างได้ที่ qrcode-scanner 
 

    ไฟล์ qrscan.php

 
<!DOCTYPE html>
<html lang='en'>
  <head>
    <meta charset='utf-8' />
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css">
    <title>Document</title>
	<script src="lib/jsqr/jsQR.js"></script>
	  <style>
		h1 {
		  margin: 10px 0;
		  font-size: 40px;
		}
		.wrap-qrcode-scanner{
			  max-width: 640px;
			  margin: 0 auto;
			  position: relative;		
		}
		#loadingMessage {
		  text-align: center;
		  padding: 40px;
		  background-color: #eee;
		}
		#canvas {
		  width: 100%;
		}
		#output {
		  margin-top: 20px;
		  background: #eee;
		  padding: 10px;
		  padding-bottom: 0;
		}
		#output div {
		  padding-bottom: 10px;
		  word-wrap: break-word;
		}
		#beepsound{width: 0px;height: 1px;}
	  </style>	
  </head>
<body>


<div class="wrap-qrcode-scanner">
	<h1>QRCode Scanner</h1>
	<div id="loadingMessage">🎥 Unable to access video stream (please make sure you have a webcam enabled)</div>
	<canvas id="canvas" hidden></canvas>
	<div id="output" hidden>
	<div id="outputMessage">No QR code detected.</div>
	<div hidden><b>Data:</b> <span id="outputData"></span></div>
	</div>
	<audio id="beepsound" controls>
	<source src="sound/scanner-beeps-barcode.mp3" type="audio/mpeg">
	Your browser does not support the audio tag.
	</audio>  
	<img id="outputqrcode">
	<canvas id="canvas2" ></canvas>
</div>
  <script>
    var video = document.createElement("video");
    var canvasElement = document.getElementById("canvas");
    var canvas = canvasElement.getContext("2d");
    var loadingMessage = document.getElementById("loadingMessage");
    var outputContainer = document.getElementById("output");
    var outputMessage = document.getElementById("outputMessage");
    var outputData = document.getElementById("outputData");
	var beepsound = document.getElementById("beepsound");
	var outputQrcode = document.getElementById('outputqrcode');
	var TLR,TRR,BRL,BLL;
	var code;
	var waiting;

    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();
	  return true;
    }

    // Use facingMode: environment to attemt to get the front camera on phones
    navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) {
      video.srcObject = stream;
      video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
      video.play();
      requestAnimationFrame(tick);
    });

    function tick() {
      loadingMessage.innerText = "⏳ Loading video..."
      if (video.readyState === video.HAVE_ENOUGH_DATA) {
        loadingMessage.hidden = true;
        canvasElement.hidden = false;
        outputContainer.hidden = false;

        canvasElement.height = video.videoHeight;
        canvasElement.width = video.videoWidth;
		canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
		if(!video.paused){				
			var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
			 code = jsQR(imageData.data, imageData.width, imageData.height, {
			  inversionAttempts: "dontInvert",
			});					
		}
        if (code) {
          TLR = drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58");
          TRR = drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58");
          BRL = drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58");
          BLL = drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58");
          outputMessage.hidden = true;
          outputData.parentElement.hidden = false;
          outputData.innerText = code.data;
		  if(code.data!="" && !waiting && TLR==true && TRR==true && BRL==true && BLL==true ){
		  	console.log(code.data);
			// สามารถส่งค่า code.data ไปทำงานอย่างอื่นๆ ผ่าน ajax ได้
		  	video.pause();
			beepsound.play();
			beepsound.onended = function() {
				beepsound.muted = true;
			};	
			// ให้เริ่มเล่นวิดีโอก่อนล็กน้อย เพื่อล้างค่ารูป qrcod ล่าสุด เป็นการใช้รูปจากกล้องแทน
			setTimeout(function(){
				video.play();
			},4500);
			// ให้รอ 5 วินาทีสำหรับการ สแกนในครั้งจ่อไป
			 waiting = setTimeout(function(){
			 	TLR,TRR,BRL,BLL = null;
				beepsound.muted = false;
				if(waiting){
					clearTimeout(waiting);
					waiting = null;					
				}
			  },5000);					
		  }
        } else {
          outputMessage.hidden = false;
          outputData.parentElement.hidden = true;
        }
      }
      requestAnimationFrame(tick);
    }
  </script>
</body>
</html>
 
    ทดสอบการทำงาน ได้ที่ demo ด้านล่าง สามารถสร้าง qrcode ผ่านเว็บไซต์ qrcode-monkey 
    แล้วนำมาทดสอบสแกนค่าได้ หรือใช้งานร่วมกับ qrcode ที่สร้างจากบทความด้านล่างนี้ก็ได้
        สร้าง qrcode ด้วย php endroid qrcode อัพเดทปี 2020 http://niik.in/978




กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ







เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ