스타벅스나 교촌치킨처럼 바코드가 포함된 기프티콘 이미지를
카메라로 스캔하거나 이미지를 업로드하여 자동 인식하는 기능에 대한 개발 요청이 있었다.
기프티콘 이미지를 DB에 저장하지 않고
단순히 클라이언트에서 조회 처리하는 것이 목표였다.
이번 글은 기프티콘의 바코드를 웹에서 스캔하고 인식하는 기능을 제공하는 라이브러리 추천과
해당 라이브러리를 적용하여 클라이언트 코드를 작성하는 것까지 담아볼 예정이다.
1. 바코드 스캔/인식 라이브러리
- zxing
- 무료, JS에서 바코드 인식 가능, 라이브러리 사용 쉬움
- 바코드만 있는 이미지여야 가능
- aspose
- 유료
- 바코드를 포함한 쿠폰 형태의 이미지도 인식 가능
- html5-qrcode
- 무료, 라이브러리 사용 쉬움
- 바코드만 있는 이미지여야 가능
- quaggaJS
- 무료, JS에서 바코드 인식 가능, 라이브러리 사용 쉬움
- 바코드를 포함한 쿠폰 형태의 이미지도 인식 가능
- Tesseract.js
- 무료, JS에서 바코드 인식 가능, 라이브러리 사용 쉬움
- 바코드를 포함한 쿠폰 형태의 이미지도 인식 가능
나는 quaggaJS, tesseract.js 라이브러리를 적용하여 개발했다.
무료 라이브러리에 바코드를 포함한 쿠폰 형태의 이미지도 인식이 가능한 라이브러리이기 때문이다.
나머지는 유료거나 바코드 이미지만 있는 이미지여야 인식이 가능하기 때문에 사용할 이유가 없었다.
1-1. quaggaJS, Tesseract.js 설명
1) QuaggaJS
https://github.com/ericblade/quagga2#installing
GitHub - ericblade/quagga2: An advanced barcode-scanner written in Javascript and TypeScript - Continuation from https://github.
An advanced barcode-scanner written in Javascript and TypeScript - Continuation from https://github.com/serratus/quaggajs - ericblade/quagga2
github.com
- HTML DOM 이미지나 파일 input에서 바코드 인식 가능
- 그러나 인식률이 월등히 뛰어나지는 않음. (간혹 낮을 수 있음)
- 최신화가 되고 있진 않다. 오래된 기술임.
-> 사용해보니 이미지 업로드 후 방식에서는 인식을 못하는 이미지도 있고, 인식이 잘되는 이미지도 있다.
그래서 quagga가 바코드 인식을 잘 못할 경우를 대비해 백업용으로 tesseract.js를 적용했다.
2) Tesseract.js
- 이미지 내 바코드 숫자를 OCR로 읽어서 텍스트만 추출
-> quagga가 인식하지 못하는 이미지를 얘는 인식을 한다.
그러나 속도가 quagga보다 좀 느리다는 단점이 있다.
그래서 이미지 인식 중이라는 오버레이 효과를 띄워서 문구를 노출시켰다.
1-2. zxing, html5-qrcode, aspose
1) zxing
https://zxing-js.github.io/library/index.html
ZXing TypeScript | Demo & Examples
ZXing TypeScript Examples ZXing ("zebra crossing") TypeScript is an open-source, multi-format 1D/2D barcode image processing library ported to TypeScript from Java. The library can be used from browser or from node. Below are links to browser demo & exampl
zxing-js.github.io
https://github.com/zxing-js/library
GitHub - zxing-js/library: Multi-format 1D/2D barcode image processing library, usable in JavaScript ecosystem.
Multi-format 1D/2D barcode image processing library, usable in JavaScript ecosystem. - zxing-js/library
github.com
zxing이 구글링했을 때 제일 많이 나온다.
실제 적용해봤을 때 자바스크립트에서 쉽게 코드를 작성할 수 있고, 인식도 곧 잘 된다.
그러나 문제는 바코드만 있는 이미지여야 인식이 된다는 점이다.
예를 들어, 아래 바코드가 포함된 쿠폰 전체 이미지는 인식이 되지 않는다.
아래 이미지처럼 바코드만 있는 이미지에 제한하여 인식이 된다.
이는 html5-qrcode도 마찬가지이다.
2) html5-qrcode
https://blog.minhazav.dev/research/html5-qrcode.html
HTML5 QR Code Scanner Demo
Demo of a cross platform HTML5 QR Code scanner
blog.minhazav.dev
이 양반도 바코드 인식률이 좋고 계속 최신화되고 있다.
그러나 쿠폰을 포함한 이미지는 인식하지 못한다.
3) aspose
https://products.aspose.com/barcode/java/
Java Barcode API - Generate or Scan 1D 2D & Postal Barcodes
Java barcode library to generate read recognize barcodes. Barcode SDK supports Linear & 2D barcodes as well as exports to JPG GIF PNG BMP and more image formats
products.aspose.com
얜 API 끌어와서 쓰는 것 같은데, 유료다.
근데 바코드가 포함된 쿠폰을 잘 인식한다.
하지만 유료라서 탈락.
2. quaggaJS, tesseract.js 라이브러리 사용법
환경 | springboot, jsp, javascript es5
1) JSP
quagga, tesseract 라이브러리 추가
<script src="https://unpkg.com/quagga@0.12.1/dist/quagga.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@4.0.2/dist/tesseract.min.js"></script>
input type="file", video 태그 추가
<div class="searchBarcodeImg">
<div class="title">쿠폰 조회</div>
<div class="wrap">
<input class="inputBarcodeNum" type="number" placeholder="쿠폰번호를 입력해주세요"
id="inputBarcodeNumNo" autocomplete="new-password">
<button class="clickBtn" onclick="checkBarcodeNum()">쿠폰조회</button>
<button class="clickBtn" onclick="startBarcodeScan()">바코드 인식</button>
<button class="clickBtn" onclick="uploadCouponImage()">E쿠폰 이미지 등록</button>
<input type="file" id="BarcodeImgInput" accept="image/*" style="display: none;" /> <!-- E쿠폰이미지 업로드 -->
</div>
<!-- 바코드 스캔 모달 -->
<div id="barcodeModal" class="modal" style="display:none;">
<div class="modal-content">
<span id="closeModal">×</span>
<p id="barcodeModalText">바코드 인식</p>
<div class="video-wrapper">
<video id="barcode-scanner" autoplay playsinline muted></video>
<div class="overlay-top"></div>
<div class="overlay-bottom"></div>
<div class="scan-line"></div>
</div>
</div>
</div>
</div>
[바코드 스캔]
- 바코드 스캔하기 위해 카메라를 활성화하기 위해 video 태그 추가
- 카메라 활성화 시 해당 영역 모달창으로 띄우기
[바코드 리더]
- 쿠폰 이미지를 업로드해서 바코드를 인식하여 바코드 번호를 추출하게끔 해줘야 하기 때문에 파일 업로드 태그 추가
JavaScript
1) 바코드 스캔
/*
* 바코드 스캔
* (카메라 활성화 후 인식)
*/
let scanTimeout = null; // 바코드 스캔 모달창 접속 시간 계산
let barcodeDetected = false; // 바코드 스캔 중복 방지 플래그
function startBarcodeScan() {
barcodeDetected = false; // 매번 시작할 때 초기화
const modal = document.getElementById('barcodeModal');
const video = document.getElementById('barcode-scanner');
modal.style.display = 'flex';
// 카메라 접근
navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 640 },
height: { ideal: 480 },
facingMode: "environment"
}
})
.then(function (stream) {
video.srcObject = stream;
return video.play();
})
.then(() => {
Quagga.init({
inputStream: {
name: "Live",
type: "LiveStream",
target: video,
constraints: {
width: { ideal: 640 },
height: { ideal: 480 },
facingMode: "environment"
}
},
decoder: {
readers: ['code_128_reader', 'ean_reader', 'ean_8_reader']
},
locate: true, // 중앙 인식 가능
}, function (err) {
if (err) {
console.error(err);
alert("카메라 초기화 실패 : " + err.message);
modal.style.display = 'none';
return;
}
Quagga.start();
});
// 모달창 접속 30초 후 안내 문구
scanTimeout = setTimeout(() => {
alert("어둡거나 빛이 반사된 경우 바코드가 인식되지 않을 수 있습니다");
}, 30000);
// 바코드 인식
Quagga.onDetected(function (result) {
if (barcodeDetected) return; // 이미 인식된 경우 무시
if (result && result.codeResult && result.codeResult.code) {
barcodeDetected = true; // 바코드 인식 플래그 설정
const code = result.codeResult.code;
alert("바코드 인식 성공: " + code);
document.getElementById('inputBarcodeNumNo').value = code;
checkBarcodeNum();
// 스캔 종료
Quagga.stop();
video.srcObject.getTracks().forEach(track => track.stop());
modal.style.display = 'none';
clearTimeout(scanTimeout);
}
});
})
.catch(function (err) {
alert("카메라 접근 또는 비디오 재생이 불가합니다.");
modal.style.display = 'none';
});
// 닫기 버튼
document.getElementById('closeModal').onclick = function () {
const modal = document.getElementById('barcodeModal');
const modalContent = modal.querySelector('.modal-content');
const video = document.getElementById('barcode-scanner');
Quagga.stop();
if (video.srcObject) {
video.srcObject.getTracks().forEach(track => track.stop());
}
clearTimeout(scanTimeout);
// 모바일이면 slide-down 애니메이션 적용
if (window.innerWidth <= 720) {
modalContent.classList.add('slide-down');
modalContent.addEventListener('animationend', function handler() {
modal.style.display = 'none';
modalContent.classList.remove('slide-down');
modalContent.removeEventListener('animationend', handler);
});
} else {
modal.style.display = 'none';
}
};
}
1. 바코드 스캔 모달창 접속 시간 계산하여 30초 이후 alert 안내문구 노출
2. 바코드 스캔 중복 방지 기능 추가 (여러번 재스캔 되는 경우가 있음)
3. 카메라 접근 또는 비디오 재생 불가한 경우, 에러 메시지 노출
4. 모바일 화면의 경우, 모달창 종료 버튼 클릭 시 위에서 아래로 슬라이드 애니메이션 효과 추가
2) 바코드 리더
/*
* 바코드 리더
* (이미지 업로드 후 인식)
*/
function uploadCouponImage() {
document.getElementById('BarcodeImgInput').click();
}
document.getElementById('BarcodeImgInput').addEventListener('change', function (e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function () {
const img = new Image();
img.src = reader.result;
img.onload = function () {
// 오버레이 표시
document.getElementById('barcodeLoadingOverlay').style.display = 'block';
// 바코드 먼저 시도
Quagga.decodeSingle({
src: img.src,
numOfWorkers: 0,
decoder: {
readers: ['code_128_reader', 'ean_reader', 'ean_8_reader']
},
locate: true // 바코드 위치 자동 탐지
}, function (result) {
if (result && result.codeResult) {
const code = result.codeResult.code;
$('#inputBarcodeNumNo').val(code);
// 오버레이 제거 및 쿠폰 조회
document.getElementById('barcodeLoadingOverlay').style.display = 'none';
checkBarcodeNum();
} else {
// 실패하면 OCR 시도
Tesseract.recognize(img, 'eng').then(({ data: { text } }) => {
const match = text.match(/(\d{4})[- ]?(\d{4})[- ]?(\d{4})/); // 바코드 패턴
if (match) {
const code = match.slice(1).join('');
$('#inputBarcodeNumNo').val(code);
// 오버레이 제거 및 쿠폰 조회
document.getElementById('barcodeLoadingOverlay').style.display = 'none';
checkBarcodeNum();
} else {
alert("바코드를 인식할 수 없습니다. 다시 시도해주세요.");
document.getElementById('barcodeLoadingOverlay').style.display = 'none';
}
});
}
});
};
};
reader.readAsDataURL(file);
});
1. 이미지 업로드 후 오버레이 표시 (인식 시간이 지연되는 경우 대처)
2. quagga 바코드 인식 실패 시 tesseract 바코드 인식으로 적용
3. 바코드 인식 불가의 경우 alert 노출
-> 쿠폰번호를 받아서 컨트롤러에서 유효성을 조회하는 코드는 이미 작성되어 있기에,
이미지에서 쿠폰번호를 추출해서 쿠폰번호 유효성 조회하는 로직에다가 던져주는 로직만 작성했다.
zxing에 대한 정보만 너무 많아서 라이브러리 찾는데 개발하는 시간보다 꽤 소요되었다.
워크데이로 하루 정도는 걸렸는데, 개발하는 시간은 2-3시간도 안걸린 듯...
바코드만 있는 이미지 말고,
쿠폰 전체의 이미지 안에 바코드 있는 위치를 인식해서 번호를 추출하는 최신 라이브러리는 거의 다 유료다.
무료는 quagga, tesseract가 있는데 바코드 인식률이 뛰어나진 않다.
카메라 활성화해서 스캔하면 오추출 되는 경우가 가끔 있다.
바코드 인식하면 quagga가 인식하지 못하는 이미지는 tesseract로 던지는데, tesseract는 인식 시간이 좀 걸린다.
그래서 사람들이 다 유료쓰나 ...
인식 시간이 너무 지연된다 하면 유료 라이브러리를 결제하는 방법도 있다고 말해주려한다.
아마 유료는 안쓸듯 ? 그것도 다 비용이니
아무튼 나와 같은 고민을 가진 사람들에게 도움이 되었으면 한다.
'[Project] > 업무일지' 카테고리의 다른 글
회원탈퇴 시 쿠키 삭제하기(JavaScript) (1) | 2024.12.27 |
---|---|
전역변수 vs 지역변수 (0) | 2024.12.16 |
[ES5/SpringBoot/Ajax/JSP] CKEditor 4.16.2 : 한글 깨짐 현상 해결 (인코딩 설정) (0) | 2024.10.25 |
[Oracle] 부모 - 자식 간 관계일 때 컬럼값 수정 (0) | 2024.10.24 |
[Oracle/WEB] 특정 쿠폰 사용완료 고객, 주문, 쿠폰 정보 조회 쿼리 작성 (0) | 2024.10.23 |