使用Java開發OpenCV3程序-2.讀取並顯示一幅圖片


  上一篇介紹了Java版本的OpenCV環境配置以及第一個示例程序,在第一個示例程序中,只是使用了控制台輸出的方式,打印圖像mat對象的一些信息,沒有使用GUI形式展示出來。而且程序的結構以及運行方式等也沒有做詳細的介紹。這篇文章就這些問題展開詳細的說明,為了有直觀的認識,先把這篇文章要實現的效果展現出來,如下圖,換了一張大圖來展示。

  具體調用的方法,就兩行,new一個對象然后調用imshow()方法:

1     ImageViewer imageViewer = new ImageViewer(mat, "第一幅圖片");
2     imageViewer.imshow();    

  以下是顯示該圖像的代碼,是不是很簡單。過程就是將OpenCV中的mat對象中的像素數據轉成Java中的Image對象,然后使用Java的界面方法新建一個GUI,將這個Image對象顯示出來。

 1 package com.superbool.util;
 2 
 3 import org.opencv.core.Mat;
 4 
 5 import javax.swing.*;
 6 import java.awt.*;
 7 import java.awt.image.BufferedImage;
 8 import java.awt.image.DataBufferByte;
 9 
10 /**
11  * Created by kofee on 2016/3/28.
12  */
13 public class ImageViewer {
14     private JLabel imageView;
15 
16     private Mat image;
17     private String windowName;
18 
19     /**
20      * 如果使用junit測試時調用該方法,圖像會一閃而過,可通過sleep()等方式暫時顯示
21      *
22      * @param
23      */
24 
25     public ImageViewer(Mat image) {
26         this.image = image;
27     }
28 
29 
30     /**
31      * @param image      要顯示的mat
32      * @param windowName 窗口標題
33      */
34     public ImageViewer(Mat image, String windowName) {
35         this.image = image;
36         this.windowName = windowName;
37     }
38 
39     /**
40      * 圖片顯示
41      */
42     public void imshow() {
43         setSystemLookAndFeel();
44         Image loadedImage = toBufferedImage(image);
45         JFrame frame = createJFrame(windowName, image.width(), image.height());
46         imageView.setIcon(new ImageIcon(loadedImage));
47         frame.pack();
48         frame.setLocationRelativeTo(null);
49         frame.setVisible(true);
50         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 用戶點擊窗口關閉
51     }
52 
53     private void setSystemLookAndFeel() {
54         try {
55             UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
56         } catch (ClassNotFoundException e) {
57             e.printStackTrace();
58         } catch (InstantiationException e) {
59             e.printStackTrace();
60         } catch (IllegalAccessException e) {
61             e.printStackTrace();
62         } catch (UnsupportedLookAndFeelException e) {
63             e.printStackTrace();
64         }
65     }
66 
67     private JFrame createJFrame(String windowName, int width, int height) {
68         JFrame frame = new JFrame(windowName);
69         imageView = new JLabel();
70         final JScrollPane imageScrollPane = new JScrollPane(imageView);
71         imageScrollPane.setPreferredSize(new Dimension(width, height));
72         frame.add(imageScrollPane, BorderLayout.CENTER);
73         frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
74         return frame;
75     }
76 
77 
78     private Image toBufferedImage(Mat matrix) {
79         int type = BufferedImage.TYPE_BYTE_GRAY;
80         if (matrix.channels() > 1) {
81             type = BufferedImage.TYPE_3BYTE_BGR;
82         }
83         int bufferSize = matrix.channels() * matrix.cols() * matrix.rows();
84         byte[] buffer = new byte[bufferSize];
85         matrix.get(0, 0, buffer); // 獲取所有的像素點
86         BufferedImage image = new BufferedImage(matrix.cols(), matrix.rows(), type);
87         final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
88         System.arraycopy(buffer, 0, targetPixels, 0, buffer.length);
89         return image;
90     }
91 }

  我們再回過頭看看整個項目,就目前來說還沒用到OpenCV的任何算法,只是用了mat對象作為圖像的緩存中轉顯示,用純Java方法調用也能達到一樣的效果。但是實現的過程卻是不同的。我們看讀取圖像的方法:

1 Mat mat = Imgcodecs.imread("C:/test.png");

  只有一行代碼,就將圖像讀取為Mat對象了,再來看imread()的內部實現代碼:

 1 public class Imgcodecs {
 2 ......
 3     public static Mat imread(String filename)
 4     {
 5         
 6         Mat retVal = new Mat(imread_1(filename));
 7         
 8         return retVal;
 9     }
10 
11 ......
12     private static native long imread_1(String filename);
13 ......
14 }
15 
16 ......
17 
18 public class Mat {
19 
20     public final long nativeObj;
21 
22     public Mat(long addr)
23     {
24         if (addr == 0)
25             throw new java.lang.UnsupportedOperationException("Native object address is NULL");
26         nativeObj = addr;
27     }
28 ......
29 }

  在Imgcodes類中調用了imread()方法,再看該方法內部,new了一個Mat對象,使用了imread_1()方法,卻返回的是long給Mat。繼續跟蹤mat()的構造方法,將long型的addr傳給了nativeObj。這么看來這個Mat對象貌似就是個long型的地址似的,實則不然,其實在這里有個最重要的方法,那就是private static native long imread_1(String filename); 乍看這個方法跟其他的方法並沒有什么不同,但是卻不能繼續往下跟蹤了,無法查看其具體的實現。但跟普通的java方法,多了native這個關鍵字,那這個關鍵字又是做什么用的呢?

  簡單來說這個方法就是連通Java代碼和C++代碼的橋梁,但卻是單向的,只能從Java端調用C++程序,當然是編譯后的可執行文件。以上面的代碼為例,OpenCV官方用C++代碼實現了一個imread_1()的方法,參數是文件名(包含路徑),返回值是這個mat對象在內存中的地址,這個方法編譯生成.dll文件,所以在程序運行之前需要指定.dll文件的位置(VM選項指定的-Djava.library.path=$PROJECT_DIR$\opencv\x64)和動態鏈接庫的文件名(System.loadLibrary(Core.NATIVE_LIBRARY_NAME);)。這就是JNI(Java Native Interface),更多關於JNI的知識可詳細參考其他資料,這里只是簡單介紹Java版本的OpenCV程序的調用過程。

  到這里大家已經應該明白了,所有的Java版本的OpenCV算法部分的代碼都是采用C++寫的,然后通過Java的JNI方式來調用的,所以理論上Java版本的程序並不會慢多少。

  最后介紹一下easy_opencv的項目結構:

   如上圖所示:

  • .idea/目錄以及easy_opencv.iml是idea項目的配置信息,用戶無需關注都是自動生成的,而且在git上我已經屏蔽了這兩個本地文件(目錄),所以你的電腦上可能跟我的不一致。
  • opencv/ 目錄下是opencv的動態鏈接庫程序以及jar包。
  • src/是我們保存代碼的主要目錄。java/路徑下就是具體的代碼。
  • resources/ 顧名思義就是資源文件的目錄,以后所有程序需要用的圖片等資源都將放到該目錄下。
  • test/是測試目錄
  • pom.xml就是maven工程的配置文件,一些包的依賴等需要在這里配置。

  如果對這些有疑問的話,可以上網搜索一下java maven工程,其采用了約定大於配置的方式,方便了應用程序的管理和開發。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM