티스토리 뷰

개요

  • 파일을 스캔해서 그 안에 있는 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'

 

소스파일

ZxingExam.java
0.00MB

 

참고