上一篇介紹了Java版本的OpenCV環境配置以及第一個示例程序,在第一個示例程序中,只是使用了控制檯輸出的方式,打印圖像mat對象的一些信息,沒有使用GUI形式展現出來。並且程序的結構以及運行方式等也沒有作詳細的介紹。這篇文章就這些問題展開詳細的說明,爲了有直觀的認識,先把這篇文章要實現的效果展示出來,以下圖,換了一張大圖來展現。java
具體調用的方法,就兩行,new一個對象而後調用imshow()方法:git
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()的內部實現代碼:maven
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這個關鍵字,那這個關鍵字又是作什麼用的呢?ide
簡單來講這個方法就是連通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版本的程序並不會慢多少。this
最後介紹一下easy_opencv的項目結構:idea
如上圖所示:spa
若是對這些有疑問的話,能夠上網搜索一下java maven工程,其採用了約定大於配置的方式,方便了應用程序的管理和開發。