티스토리 뷰
개요
- 파일을 스캔해서 그 안에 있는 QR코드가 파일명의 일부와 일치하는 지 체크하는 업무를 맡게 됨.
- 라이브러리 찾기부터 실제 구현까지 기록으로 남김
라이브러리 찾기
- 라이브러리는 'qrcode reader java'로 검색하면 나오는 결과 중 가장 위에 있고 많이 들어본 ZXing부터 동작실험을 하기로 함.
- 이게 첫번째인데 쓰는 방법도 어렵지 않고 무료니까 굳이 다른걸 조사하지 않고 이걸로 해야겠다고 생각했다.
동작실험
- 첫번째 조사인데 이미지에 대한 부분은 이 링크의 코드로 충분히 해결되었다.
https://www.geeksforgeeks.org/how-to-generate-and-read-qr-code-with-java-using-zxing-library/
How to generate and read QR code with Java using ZXing Library - GeeksforGeeks
A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.
www.geeksforgeeks.org
QR코드 읽기 동작 설명
public class QRCode {
// Function to read the QR file
public static String readQR(String path, String charset,
Map hashMap)
throws FileNotFoundException, IOException,
NotFoundException
{
BinaryBitmap binaryBitmap
= new BinaryBitmap(new HybridBinarizer(
new BufferedImageLuminanceSource(
ImageIO.read(
new FileInputStream(path)))));
Result result
= new MultiFormatReader().decode(binaryBitmap);
return result.getText();
}
// Driver code
public static void main(String[] args)
throws WriterException, IOException,
NotFoundException
{
// Path where the QR code is saved
String path = "F:\user\QRCodes";
// Encoding charset
String charset = "UTF-8";
Map<EncodeHintType, ErrorCorrectionLevel> hashMap
= new HashMap<EncodeHintType,
ErrorCorrectionLevel>();
hashMap.put(EncodeHintType.ERROR_CORRECTION,
ErrorCorrectionLevel.L);
System.out.println(
"QRCode output: "
+ readQR(filePath, charset, hashMap));
}
}
- 결과부터 거꾸로 돌아가도록하자.
- QR코드의 내용은 MultiFormatReader.decode가 처리해준다. 파라미터로 BinaryBitmap을 전달하면 된다.
- BinaryBitmap을 만들려면 Binarizer가 필요하고
( Binarizer 는 추상 클래스, HybridBinarizer가 클래스이다),
Binarizer를 만들려면 LuminanceSource가 필요하다.
(LuminanceSource도 추상 클래스이고 BufferedImageLuminanceSource가 클래스이다.) - LuminanceSource를 만들려면 BufferedImage가 필요한데,
BinaryBitmap부터 LuminanceSource까지는 Zxing(core와 javase)에 있고
BufferedImage는 java.awt.image패키지에 있다. - BufferedImage는 간단히 ImageIO.read로 만들 수 있다.
위의 코드에서는 FileInputStream을 썼지만 File이나 URL도 된다.
PDF의 QR코드도 읽어보기
- 위의 코드는 이미지에 대한 코드이고 PDF에서도 읽을 수 있게 개선해보자.
- 방법은 PDF를 읽고 PDF를 BinaryBitmap으로 만들어주면 된다.
- 라이브러리는 PDFBox를 이용하였다
- renderDpi는 이미지로 만들 때 해상도이다. 작을수록 작은 용량의 파일이 되지만 인식이 제대로 안되니 적절히 조절해주자.
- ImageType은 이미지를 어떤 타입으로 해주는가인데 BINARY는 흑백이다. 어차피 사람이 볼 것이 아니므로 QR코드에는 흑백이 유리할 것 같아 BINARY를 선택해주었다.
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
private static BufferedImage getBufferedImageForPdf(File pdfFile, int renderDpi) throws IOException {
var pdDocument = Loader.loadPDF(pdfFile);
var pdfRenderer = new PDFRenderer(pdDocument);
return pdfRenderer.renderImageWithDPI(0, renderDpi, ImageType.BINARY);
}
이미지를 나눠서 좀 더 빠르게 실행하기
- QR코드가 어차피 일정 장소에 1개만 있다면 전체를 해석하는건 시간낭비이다.
- 화면을 나눠서 처리하도록해보자.
- 코드는 다음과 같다.
private static BufferedImage getQuadrantBufferedImage(BufferedImage bufferedImage, Integer quadrant) {
var stopWatch = Stopwatch.createStarted();
int w = bufferedImage.getWidth();
int h = bufferedImage.getHeight();
int subW = w / 2;
int subH = h / 2;
int subX = 0;
int subY = 0;
switch (quadrant) {
case 1:
subX = w / 2;
break;
case 2:
break;
case 3:
subY = h / 2;
break;
case 4:
subX = w / 2;
subY = h / 2;
break;
}
var subBufferedImage = bufferedImage.getSubimage(subX, subY, subW, subH);
log.info("make subImage. quadrant:{}, Elapsed:{}", quadrant, stopWatch.stop());
return subBufferedImage;
}
- 원래사진:
- 나눠진 이미지
![]() |
![]() |
![]() |
![]() |
성능평가
사분면 | 사분면 파일 생성시간 | 처리시간 | QR코드 읽기 성공 |
원본 | - | 175.2ms | OK |
1 | 48.80μs | 29.87ms | OK |
2 | 27.40μs | 16.06ms | NG |
3 | 28.30μs | 24.35ms | NG |
4 | 927.8μs | 21.96ms | NG |
- 이미지파일을 처리
- 당연한 얘기지만 원본과 1사분면만 QR코드를 읽을 수 있었다.
- 사분면 파일 생성은 1ms가 안되는데 140ms정도 더 빨리 처리할 수 있었다.
DPI | 50 | 100 | 150 | 200 | 250 | 300 | 350 |
이미지로변환 | 40.00 | 44.00 | 60.00 | 60.00 | 100.00 | 110.00 | 130.00 |
PDF생성후 전체인식 | 18.53 | 33.28 | 53.50 | 63.07 | 69.91 | 93.51 | 84.52 |
1사분면 | 3.69 | 4.03 | 4.02 | 10.71 | 13.53 | 15.25 | 15.81 |
2사분면 | 1.07 | 1.36 | 5.39 | 3.80 | 6.26 | 12.54 | 6.87 |
3사분면 | 0.40 | 2.15 | 4.78 | 3.54 | 3.71 | 6.96 | 7.09 |
4사분면 | 0.50 | 0.93 | 3.87 | 4.32 | 4.37 | 4.75 | 6.77 |
- PDF파일을 처리
- 단위는 전체 ms이다.
- PDF처리를 위해서는 이미지로의 변환이 필요한데 스케일이나 해상도(DPI)를 지정하여야 한다.
해상도를 지정하지 않으면 스케일 1로 변환되는데, 이것은 72DPI에 해당한다. - DPI의 값이 높을수록 인식이 잘 될 것이라고 생각하였다. 50~150구간에서는 인식이 되지 않았다.
- 200,300에서는 인식이 되나 250,350은 인식이 되지 않는 문제가 발생하였다.
이유는 원본이 원래부터 조악한 화질이였고 96문자에 대한 QR코드였기 때문으로 예상된다. - 테스트한 자료는 포스팅을 위해 인터넷에서 가져온 자료이고
현업에서는 PDF도 아니고 화질도 좋고 7문자에 대한 QR코드이기 때문에 문제가 없을 것으로 예상된다.
결론
- 자바에서 Zxing라이브러리를 사용한다는 전제하에 어떤 형식이던 BufferedImage로 변환만 가능하다면 QR코드 인식이 가능하다.
- 변환할 때는 원본과 같은 결과를 보장할 수는 없다. 그러니 최대한 이미지 파일로 처리를 하자.
- QR코드의 위치가 정해져 있다면 그 부분의 이미지만 잘라서 처리하면 더 빠르게 처리할 수 있다.
팁
- PDF를 이미지로 변환하는 작업을 할 때 정수의 스케일(혹은 72의 배수의 DPI)를 쓰면 위치 계산시 소숫점이 나오지 않아 이미지 파일 결과물이 좀 더 잘 나올 수 도 있다.
하지만 위의 PDF는 인식률 개선 효과가 없었다. - 가능하다면 변환해서 처리해야하는 PDF를 쓰지말고 이미지 파일을 쓰자.
- 여러장의 페이지를 처리해야할 때는 PDF가 편할 수 있다.
- PNG나 TIFF포맷으로 스캔하도록 하자.
대부분의 OCR에서 TIFF를 지원하는데는 이유가 있다.
Gradle(Maven)정보
// https://mvnrepository.com/artifact/com.google.zxing/core
implementation group: 'com.google.zxing', name: 'core', version: '3.5.2'
// https://mvnrepository.com/artifact/com.google.zxing/javase
implementation group: 'com.google.zxing', name: 'javase', version: '3.5.2'
// https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox
implementation group: 'org.apache.pdfbox', name: 'pdfbox', version: '3.0.0'
소스파일
참고
- How to generate and read QR code with Java using ZXing Library
https://www.geeksforgeeks.org/how-to-generate-and-read-qr-code-with-java-using-zxing-library/ - NG vs. TIFF:
https://www.adobe.com/kr/creativecloud/file-types/image/comparison/tiff-vs-png.html - Convert a PDF file to image
https://stackoverflow.com/questions/18189314/convert-a-pdf-file-to-image
'today'work' 카테고리의 다른 글
Java 콘솔 로그 출력 테스트하기 (0) | 2023.11.16 |
---|---|
Maven에서 개행문자 LF로 고정하기. (0) | 2023.11.02 |
컴파일 시 Lombok Builder 클래스를 못 찾는 에러 (0) | 2023.09.28 |
postgreSQL에서 Input/output error 발생 (0) | 2023.07.24 |
PostgreSQL ROW LOCK걸기(FOR UPDATE, SKIP LOCKED) (0) | 2023.07.20 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- leetcode
- aws #aws region #aws credential #aws region provider #aws credential provier
- add two numbers
- multipleIntegrationFlow
- 로그파일인덱스
- mybatis @insert값 @update값
- excel to markdown
- logback #logstash #LoggingEventCompositeJsonEncoder #로그JSON
- excel table
- reverse integer
- cannotResolveSymbol
- json
- PostgreSQL #sequnceName
- AWS #X-Ray
- lombok #maven build #sym
- Two Sum
- QR코드읽기 #ReadQRCode
- yaml
- spring #redis #redis-cluster
- Maven LF #메이븐 개행문자
- 로그테스트 #콘솔로그테스트 #System.out
- spring-integration
- SnakeYAML
- PostgreSQL #FOR UPDATE #SKIP LOCKED
- springintegration #파일감시 #디렉토리감시 #파일완료검사
- Postgresql #MultiTruncate
- Python #Powertools
- palindrome number
- opencv로qr코드인식
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
글 보관함