使用OpenCV Java創建Windows攝像頭掃碼程序

OpenCV提供了一些基本的Webcam控制接口。用OpenCV C/C++或者Python,可以在任意平臺快速創建一個攝像頭預覽應用。然而使用Java,情況就複雜的多,因爲OpenCV Java並沒有提供一個類似於imshow的窗口顯示接口。想要創建一個帶界面的應用,就需要通過OpenCV接口獲取數據,並轉換成對應的格式,然後通過Java的UI組件顯示出來。

OpenCV Java安裝

在OpenCV官網下載最新的Windows安裝包

安裝後找到目錄opencv-4.3\opencv\build\java

如果用Eclipse,可以直接導入工程。如果用Maven,需要先安裝到Maven的本地倉庫:

mvn install:install-file -Dfile=opencv-430.jar -DgroupId=org -DartifactId=opencv -Dversion=4.3.0 -Dpackaging=jar

然後在pom.xml文件裏添加依賴:

<dependency>
  <groupId>org</groupId>
  <artifactId>opencv</artifactId>
  <version>4.3.0</version>
</dependency>

還有一個問題就是dll文件怎麼加載。如果找不到,會看到錯誤信息:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no opencv_java430 in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
        at java.lang.Runtime.loadLibrary0(Runtime.java:870)
        at java.lang.System.loadLibrary(System.java:1122)
        at com.java.barcode.App.main(App.java:65)

解決方法有幾種:

  1. 查看系統中的Java庫加載路徑。把dll文件放到對應的路徑下即可:

    System.out.println(System.getProperty("java.library.path"));
    
  2. 使用全路徑加載:

    System.load("D:/opencv-4.3/opencv/build/java/x64/opencv_java430.dll");
    
  3. 啓動程序的時候指定dll路徑:

    java -Djava.library.path=<dll path> -cp target/opencv-dotcode-1.0-SNAPSHOT-jar-with-dependencies.jar com.java.barcode.App
    

攝像頭視頻窗口

在OpenCV Java的文檔中提供了一份基於JavaFX的示例代碼

參考邏輯之後,也可以把代碼移植到Java Swing中。我這裏用JLable來顯示視頻幀:

public void updateViewer(final BufferedImage image) {
        if (!SwingUtilities.isEventDispatchThread()) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    mImage.setIcon(new ImageIcon(image));
                }
            });
            return;
        }
    }
 
Runnable frameGrabber = new Runnable() {
 
                    @Override
                    public void run() {
                        Mat frame = grabFrame();
                        byte[] data = Utils.matToByteArray(frame);
 
                        if (!status.get()) {
                            status.set(true);
                            barcodeTimer.schedule(new BarcodeRunnable(frame, mBarcodeReader, callback, status), 0, TimeUnit.MILLISECONDS);
                        }
                     
                        BufferedImage bufferedImage = Utils.byteToBufferedImage(data, frame.width(), frame.height(), frame.channels());
                        if (isRunning) updateViewer(bufferedImage);
                    }
                };
this.timer = Executors.newSingleThreadScheduledExecutor();
this.timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);

OpenCV接口運行在獨立的線程中,需要通過SwingUtilities來更新UI。

示例:DotCode解碼

pom.xml中添加Dynamsoft Barcode Reader SDK:

<repositories>
  <repository>
    <id>dbr</id>
    <url>https://download.dynamsoft.com/maven/dbr/jar</url>
  </repository>
</repositories>
<dependencies>
  <dependency>
    <groupId>com.dynamsoft</groupId>
    <artifactId>dbr</artifactId>
    <version>7.4.0</version>
  </dependency>
</dependencies>

然後像OpenCV一樣創建一條線程來做條形碼解碼。

barcodeTimer = Executors.newSingleThreadScheduledExecutor();

Runnable中的代碼:

public void run() {
        // TODO Auto-generated method stub
        try {
            TextResult[] results = reader.decodeBuffer(Utils.matToByteArray(frame), frame.width(), frame.height(), (int)frame.step1(), EnumImagePixelFormat.IPF_BGR_888, "");
            if (results != null && results.length > 0) {
                if (callback != null) {
                    callback.onResult(results, Utils.matToBufferedImage(frame));
                }
            }
            else {
                status.set(false);
            }
            
        } catch (BarcodeReaderException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        }
    }

OpenCV獲取的數據類型是Mat

條形碼解碼的時候要把Mat轉換成byte[]

public static byte[] matToByteArray(Mat original)
{
    int width = original.width(), height = original.height(), channels = original.channels();
    byte[] sourcePixels = new byte[width * height * channels];
    original.get(0, 0, sourcePixels);
    return sourcePixels;
}

而在顯示畫面的時候,要把數據再轉成BufferedImage

public static BufferedImage byteToBufferedImage(byte[] sourcePixels, int width, int height, int channels)
{
    BufferedImage image = null;
     
    if (channels > 1)
    {
        image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
    }
    else
    {
        image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
    }
    final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
    System.arraycopy(sourcePixels, 0, targetPixels, 0, sourcePixels.length);
     
    return image;
}

最後通過自定義的JLable來繪製條形碼的區域:

private ArrayList<Point[]> data = new ArrayList<>();
 
@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    if (data.size() > 0) {
        g2d.setColor(Color.RED);
        for (Point[] points : data) {
            for (int i = 0; i < points.length; ++i) {
                if (i == 3) {
                    g2d.drawLine(points[i].x, points[i].y, points[0].x, points[0].y);
                } else {
                    g2d.drawLine(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
                }
            }
        }
 
    }
    g2d.dispose();
}
 
public void appendPoints(Point[] points) {
    data.add(points);
}
 
public void clearPoints() {
    data.clear();
}

在這裏插入圖片描述

源碼

https://github.com/yushulx/java-dotcode-reader

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章