sarxos / webcam-capture

The goal of this project is to allow integrated or USB-connected webcams to be accessed directly from Java. Using provided libraries users are able to read camera images and detect motion. Main project consist of several sub projects - the root one, which contains required classes, build-in webcam driver compatible with Windows, Linux and Mac OS, which can stream images as fast as your camera can serve them (up to 50 FPS). Main project can be used standalone, but user is able to replace build-in driver with different one - such as OpenIMAJ, GStreamer, V4L4j, JMF, LTI-CIVIL, FMJ, etc.
http://webcam-capture.sarxos.pl
MIT License
2.27k stars 1.11k forks source link

Data Matrix Scan takes too long #932

Open smi42 opened 4 months ago

smi42 commented 4 months ago

I'm currently working on a Java application that needs to scan Data Matrix QR-Codes, specifically to extract and display XML data in a text box. The QR-Codes I'm dealing with contain BMP (German medication plans). For testing purposes, I'm using examples from this document: https://download.hl7.de/ukf/Testfaelle_ukf201.pdf im running this on a microsoft surface go 4 business webcam specs are : Rear Camera:

Resolution: 8.0 megapixels Video Resolution: 1080p HD video

Logging in german btw

Here's the code I have so far:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamPanel;
import com.github.sarxos.webcam.WebcamResolution;
import com.google.zxing.*;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;

import javax.swing.*;
import java.awt.*;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Logger;

public class QRCodeScanner extends JFrame implements Runnable, ThreadFactory {

    private static final Logger LOGGER = Logger.getLogger(QRCodeScanner.class.getName());

    private static final long serialVersionUID = 1L;

    private BlockingQueue<String> decodedResultsQueue = new LinkedBlockingQueue<>();

    private Executor executor = Executors.newSingleThreadExecutor(this);
    private Webcam webcam = null;
    private WebcamPanel panel = null;
    private JTextArea textarea = null;
    private JScrollPane scrollPane = null;

    public QRCodeScanner() {
        super();

        setLayout(new BorderLayout());
        setTitle("QR Code Scanner");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Dimension size = WebcamResolution.VGA.getSize();

        webcam = Webcam.getWebcams().get(1);
        webcam.setViewSize(size);

        panel = new WebcamPanel(webcam);
        panel.setPreferredSize(size);

        textarea = new JTextArea();
        textarea.setEditable(false);
        scrollPane = new JScrollPane(textarea);
        scrollPane.setPreferredSize(size);

        add(panel, BorderLayout.WEST);
        add(scrollPane, BorderLayout.CENTER);

        pack();
        setVisible(true);

        executor.execute(this);
    }

    @Override
    public void run() {
        do {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                LOGGER.severe("Thread interrupted: " + e.getMessage());
            }

            BufferedImage image = null;

            if (webcam.isOpen()) {
                LOGGER.info("Webcam is open.");
                if ((image = webcam.getImage()) == null) {
                    LOGGER.warning("Webcam image is null.");
                    continue;
                }
            } else {
                LOGGER.warning("Webcam is not open.");
            }

            Result result = processImageAndDecodeQRCode(image);

            if (result != null) {
                if (result.getBarcodeFormat() == BarcodeFormat.UPC_E) {
                    LOGGER.info("Detected UPC_E format, rescanning...");
                    continue;
                }
                result.getBarcodeFormat();
                LOGGER.info("QR code format: " + result.getBarcodeFormat());
                result.getNumBits();
                LOGGER.info("QR code num bits: " + result.getNumBits());
                result.getResultMetadata();
                LOGGER.info("QR code result metadata: " + result.getResultMetadata());
                result.getRawBytes();
                LOGGER.info("QR code raw bytes: " + result.getRawBytes());
                String text = result.getText();
                LOGGER.info("QR code text: " + text);
                decodedResultsQueue.add(text);
            }
        } while (true);
    }

    private Result processImageAndDecodeQRCode(BufferedImage image) {
        // Preprocess image: convert to grayscale and apply thresholding
        BufferedImage grayscaleImage = new BufferedImage(
            image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
        Graphics g = grayscaleImage.getGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();

        LuminanceSource source = new BufferedImageLuminanceSource(grayscaleImage);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

        Result result = null;
        try {
            result = new MultiFormatReader().decode(bitmap);
        } catch (NotFoundException e) {
            LOGGER.warning("No QR code found.");
            e.getCause();
        }

        return result;
    }

    public void startUIUpdateThread() {
        new Thread(() -> {
            while (true) {
                try {
                    // Take the next decoded result from the queue and update the UI
                    String text = decodedResultsQueue.take();
                    textarea.append(text + "\n");
                } catch (InterruptedException e) {
                    LOGGER.severe("UI update thread interrupted: " + e.getMessage());
                }
            }
        }).start();
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r, "example-runner");
        t.setDaemon(true);
        return t;
    }

    public static void main(String[] args) {
        QRCodeScanner scanner = new QRCodeScanner();
        scanner.startUIUpdateThread();  // Start the UI update thread
    }
}

Tried it on online generated Data Matrix QR Codes and it works really well https://barcode.tec-it.com/en/DataMatrix?data=This%20is%20a%20Data%20Matrix%20by%20TEC-IT99999 Sometimes I'm able to scan those example codes from the PDF, but it takes too long, if it even works. I want this app to perform as well as those QR code scanners from the Play Store.

Maybe someone with more knowledge in computer vision can help me optimize the scanning process or suggest a more efficient approach?

Any assistance or pointers would be greatly appreciated. Thanks in advance!