2011年11月22日 星期二

Java讀取圖片EXIF資訊


首先介紹一下什麼是EXIF,EXIF是Exchangeable Image File的縮寫,這是一種專門為數碼相機照片設定的格式。這種格式可以用來記錄數位照片的屬性資訊,例如相機的品牌及型號、相片的拍攝時間、拍攝時所設置 的光圈大小、快門速度、ISO等等資訊。除此之外它還能夠記錄拍攝資料,以及照片格式化方式,這樣就可以輸出到相容EXIF格式的外設上,例如照片印表機 等。
目前最常見的支援EXIF資訊的圖片格式是JPG,很多的圖像工具都可以直接顯示圖片的EXIF資訊,包括現在的一些著名的相冊網站也提供頁面用於 顯示照片的EXIF資訊。本文主要介紹Java語言如何讀取圖像的EXIF資訊,包括如何根據EXIF資訊對圖像進行調整以適合使用者流覽。
目前最簡單易用的EXIF資訊處理的Java包是Drew Noakes寫的metadata-extractor,該專案最新的版本是2.3.4,支持EXIF 2.2版本。你可以直接從http://www.drewnoakes.com/code/exif/ 下載該項目的最新版本包括其源碼。
需要注意的是,並不是每個JPG影像檔都包含有EXIF資訊,你可以在Windows資源管理器按一下選中圖片後,如果該圖片包含EXIF資訊,則會在屬性->摘要中顯示出來。

拍攝設備的型號便是EXIF資訊中的其中一個。下面我們給出一段代碼將這個圖片的所有的EXIF資訊全部列印出來。
Java代碼  收藏代码
1.  import java.io.File;  
2.  import java.util.Iterator;  
3.     
4.  import com.drew.imaging.jpeg.JpegMetadataReader;  
5.  import com.drew.metadata.Directory;  
6.  import com.drew.metadata.Metadata;  
7.  import com.drew.metadata.Tag;  
8.  import com.drew.metadata.exif.ExifDirectory;  
9.     
10. /** 
11.  * 測試用於讀取圖片的EXIF資訊 
12.  * @author Winter Lau 
13.  */  
14. public class ExifTester {  
15.      public static void main(String[] args) throws Exception {  
16.          File jpegFile = new File("C:/Documents and Settings/Administrator/workspace/ipv6/src/test/a.JPG");  
17.          Metadata metadata = JpegMetadataReader.readMetadata(jpegFile);  
18.          Directory exif = metadata.getDirectory(ExifDirectory.class);  
19.          Iterator tags = exif.getTagIterator();  
20.          while (tags.hasNext()) {  
21.              Tag tag = (Tag)tags.next();  
22.              System.out.println(tag);  
23.          }  
24.      }  
25. }  
 這個結果顯示如下:
Js代碼  收藏代码
1.  [Exif] Make - OLYMPUS OPTICAL CO.,LTD  
2.  [Exif] Model - u10D,S300D,u300D  
3.  [Exif] Orientation - Top, left side (Horizontal / normal)  
4.  [Exif] X Resolution - 72 dots per inch  
5.  [Exif] Y Resolution - 72 dots per inch  
6.  [Exif] Resolution Unit - Inch  
7.  [Exif] Software - 22-1012                          
8.  [Exif] Date/Time - 2005:04:14 13:47:10  
9.  [Exif] YCbCr Positioning - Datum point  
10. [Exif] Exposure Time - 0.01 sec  
11. [Exif] F-Number - F5.2  
12. [Exif] Exposure Program - Program creative (slow program)  
13. [Exif] ISO Speed Ratings - 80  
14. [Exif] Exif Version - 2.20  
15. [Exif] Date/Time Original - 2005:04:14 13:47:10  
16. [Exif] Date/Time Digitized - 2005:04:14 13:47:10  
17. [Exif] Components Configuration - YCbCr  
18. [Exif] Exposure Bias Value - 0 EV  
19. [Exif] Max Aperture Value - F3.1  
20. [Exif] Metering Mode - Multi-segment  
21. [Exif] Light Source - Unknown  
22. [Exif] Flash - Flash did not fire, auto  
23. [Exif] Focal Length - 17.4 mm  
24. [Exif] User Comment -   
25. [Exif] FlashPix Version - 1.00  
26. [Exif] Color Space - sRGB  
27. [Exif] Exif Image Width - 1024 pixels  
28. [Exif] Exif Image Height - 768 pixels  
29. [Exif] File Source - Digital Still Camera (DSC)  
30. [Exif] Windows XP Title - 風景  
31. [Exif] Windows XP Author - 一路風塵  
32. [Exif] Windows XP Keywords - 你是我的唯一  
33. [Exif] Windows XP Subject - 我的第一張  
34. [Exif] Custom Rendered - Normal process  
35. [Exif] Exposure Mode - Auto exposure  
36. [Exif] White Balance - Auto white balance  
37. [Exif] Digital Zoom Ratio - 1  
38. [Exif] Scene Capture Type - Landscape  
39. [Exif] Gain Control - None  
40. [Exif] Contrast - None  
41. [Exif] Saturation - None  
42. [Exif] Sharpness - None  
43. [Exif] Unknown tag (0xc4a5) - 80 114 105 110 116 73 77 0 480 2 -10...  
44. [Exif] Compression - JPEG (old-style)  
45. [Exif] Thumbnail Offset - 2022 bytes  
46. [Exif] Thumbnail Length - 5864 bytes  
47. [Exif] Thumbnail Data - [5864 bytes of thumbnail data]  
 起初的時候,我只想找到圖片的作者,標題,關鍵字這三欄資訊,那如何取得這三者的資訊呢。可以直接使用API如下:
metadata中定義了很多TAG資訊:比如:TAG_WIN_AUTHOR,
TAG_WIN_KEYWORDS
,TAG_WIN_TITLE
等定義在Windows中資訊。所以我們只要:
Java代碼  收藏代码
1.  package test;  
2.  import java.io.File;  
3.  import java.util.Iterator;  
4.     
5.  import com.drew.imaging.jpeg.JpegMetadataReader;  
6.  import com.drew.metadata.Directory;  
7.  import com.drew.metadata.Metadata;  
8.  import com.drew.metadata.Tag;  
9.  import com.drew.metadata.exif.ExifDirectory;  
10.    
11. /** 
12.  * 測試用於讀取圖片的EXIF資訊 
13.  * @author Winter Lau 
14.  */  
15. public class PicExif {  
16.      public static void main(String[] args) throws Exception {  
17.          File jpegFile = new File(  
18.                           "C:/Documents and Settings/Administrator/workspace/ipv6/src/test/a.JPG");  
19.          Metadata metadata = JpegMetadataReader.readMetadata(jpegFile);  
20.          Directory exif = metadata.getDirectory(ExifDirectory.class);  
21.          Iterator tags = exif.getTagIterator();  
22.   
23.           if(exif.containsTag(ExifDirectory.TAG_WIN_AUTHOR)){  
24.               System.out.println("Pic author is "+exif.getDescription(ExifDirectory.TAG_WIN_AUTHOR));        
25.          }  
26.           if(exif.containsTag(ExifDirectory.TAG_WIN_TITLE)){  
27.             System.out.println("Pic title is "+exif.getDescription(ExifDirectory.TAG_WIN_TITLE));      
28.           }  
29.           if(exif.containsTag(ExifDirectory.TAG_WIN_KEYWORDS)){  
30.         System.out.println("Pic keyword is "+exif.getDescription(ExifDirectory.TAG_WIN_KEYWORDS));  
31.          }  
32.      }  
33. }  
 列印的結果:
Java代碼  收藏代码
1.  Pic author is 一路風塵  
2.  Pic title is 風景  
3.  Pic keyword is 你是我的唯一  
 有了這些API,我們還可以做其他事情。這裡不一一列舉了,有興趣的直接看http://www.drewnoakes.com/code/exif/javadoc/ 。

2011年11月15日 星期二

mmseg4j 中文斷詞java 實作


照著這篇文章的步驟,你可以使用java 將一串正體(繁體)中文的字串依照詞彙切開,以方便進行中文文字探勘(Text Mining)等計算詞頻的工作。
image
▲mmseg4j中文斷詞結果,可以看到它把「處理」、「文章」等詞分割出來
首先呢,為了簡化開發,程式都在Eclipse 上開發,以下用簡單兩句話說明Eclipse 如何安裝:
  • 到Java網站,下載並安裝JRE (請選擇合適的作業系統)
  • 到Eclipse官網,下載Eclipse Classic 版本,將之解壓縮之後,執行資料夾中的Eclipse.exe ,就可以用它來寫Java 程式了
簡單介紹MMSeg 與mmseg4j
mmseg4j 是採用蔡志浩先生在2000 年發表的一個中文分詞演算法 ─ MMSeg,它使用兩套演算法與四個模糊解析規則,據稱能達到98.41% 中文斷詞準確率(理論請參考MMSeg官網)。這樣的準確率已經近似於大陸中科院的ICTCLAS,973計畫專家組評測第一名的98.5%,此外mmseg4j在斷詞速度表現良好,高達1900kb/s(Simple版本),根據5樓網友實測表示比庖丁(Paoding)還快。
此外可喜可賀的是,mmseg4j詞庫採用utf-8編碼(不像ICTCLAS使用GB)而且可以自訂,因此只要替換詞庫就可以用來將優美的正體中文進行斷詞,以下來稍微介紹一下詞庫。mmseg4j 預設使用sogou 詞庫(去除無意義詞),並合併rmmseg (Ruby中文斷詞套件)的詞庫,共14多萬詞。詞庫放在jar裡面,若使用WinRAR等軟體,將下載解開的mmseg4j裡面的mmseg4j-all-X.X.X-with-dic.jar 再解開,裡面的data 資料夾就是詞庫檔預設的位置。
  • chars.dic:單一字和對應的頻率,提供complex模式使用。
  • units.dic:單位詞,如:分、秒、年。主要是在數位後面的單位資訊切分好,不與words.dic中的詞有混淆。
  • words.dic:核心的詞庫檔,一行一詞,不需要其它任何資料,採utf-8編碼。
若要自訂辭典,檔案名必需是 “words” 為首碼和“.dic”為副檔名,例如wordsXXX.dic。然後我們可以把這個些自訂的詞庫集中在某個資料夾,並在程式中設定讀取這些詞庫檔(稍後會示範),也可以把這樣的純文字文件用WinRAR壓縮回mmseg4j-all-X.X.X-with-dic.jar 的data 資料夾中。
官網(簡體)下載mmseg4j:mmseg4j (請選擇最新的zip檔案,名稱會如「mmseg4j-1.8.3.zip」)
下載mmseg4j 正體(繁體)中文詞庫 以下詞庫是筆者透過程式工具將原本的詞庫轉為正體字,以方便台港使用者研究學習,歡迎下載分享,但請註明來源:)
來吧!我們來斷詞 當然不免俗的啟動Eclipse之後,我們必須先建立一個專案。檔案 > 新增 > Java專案
image
▲名稱取作SegChinese
image
▲下一步到Java 設定的地方,選擇新增外部JAR,然後瀏覽到剛剛下載來的mmseg4j-all-X.X.X-with-dic.jar,新增後按下完成。接著展開左邊專案目錄,在src上按右鍵,選擇新增類別,取個類別名稱,在這裡我還是取為「SegChinese」。
55行java 程式「碼上會」
我們要import這些東西,如下:
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;

import com.chenlb.mmseg4j.ComplexSeg;
import com.chenlb.mmseg4j.Dictionary;
import com.chenlb.mmseg4j.MMSeg;
import com.chenlb.mmseg4j.Seg;
import com.chenlb.mmseg4j.Word;


值得注意的是import com.chenlb.mmseg4j.ComplexSeg代表使用Complex 版本的斷詞(包含四個模糊規則);若要使用簡單版本,請換成import com.chenlb.mmseg4j.SimpleSeg;。


接下來,我們在class裡面宣告一個字典型態的變數dic(儲存詞庫),並建立建構子與設定一些程式資訊,如下:
protected Dictionary dic;
 
public SegChinese() {
 System.setProperty("mmseg.dic.path", "./src/SegChinese/data"); //這裡可以指定自訂詞庫
 dic = Dictionary.getInstance();
}

protected Seg getSeg() {
 return new ComplexSeg(dic);
}


Seg方法可以指定要使用的分詞器,在這邊使用Complex(複雜版的),並將字典及放入分詞器中。這裡一定要提一下,自訂的字典可以藉由System.setProperty("mmseg.dic.path", "路徑"); 來指定,其中路徑若是相對路徑的話,是相對於專案資料夾的目錄,在上面的例子中,我故意將字典資料夾放在專案資料夾中的src/SegChinese裡的data資料夾中,我們可以在data資料夾中放入多個字典檔,就可以使用多個字典。


再來我們要建構程式的斷詞核心部分,如下:
public String segWords(String txt, String wordSpilt) throws IOException {
 Reader input = new StringReader(txt);
 StringBuilder sb = new StringBuilder();
 Seg seg = getSeg();
 MMSeg mmSeg = new MMSeg(input, seg);
 Word word = null;
 boolean first = true;
 while((word=mmSeg.next())!=null) {
  if(!first) {
   sb.append(wordSpilt);
  }
  String w = word.getString();
  sb.append(w);
  first = false;  
 }
 return sb.toString();
}


這裡我們傳入一個字串與一個希望用來作為分隔的符號,接著使用MMSeg進行分詞,每個詞可以用.next()取得,最後把它變為字串回傳。


最後我們建立一個run 方法,用來傳入測試段詞的文字。以及main 方法以測試程式執行,如下:
protected void run(String[] args) throws IOException {
 String txt = "這行文字是要被中文斷詞處理的文章,可以從執行結果看斷詞是否成功 莊圓大師";
 
 if(args.length > 0) {
  txt = args[0];
 }
 
 System.out.println(segWords(txt, " | "));
}

public static void main(String[] args) throws IOException {  
 new SegChinese().run(args);
}


在run中我傳入直線(|)作為分隔符號,以上短短55行就完成了中文斷詞,輸出結果頁首所示。我把這份java 檔附在文末,也歡迎下載測試。


附件下載:SegChinese.java


結語在這裡我要感謝前輩Deduce的提點,讓我能花相對少的摸索找到這套工具。當然這只是一篇讓讀者很快了解如何使用mmseg4j的小品文,所以只精簡的介紹了最基本以及常用的功能。我在java/text mining上的確是晚輩,所以若有撰寫不當也請各界多多包含與指教。

JNI運用在JAVA 與 c 該如何作DLL檔


雖然JAVA的程式都必須在JAVA的虛擬機器上執行,但是我們可以在這部機器中打一個洞,讓JAVA程式可以直接溝通實際的機器.
JNI是一種讓JAVA程式可以外接外部程式的一種機制,讓JAVA程式更具有擴充性,下面將示範一個簡單的範例,這個範例中說明JAVA程式如何與C語言接軌,透過dll和native的方式來達成這項任務.
工具介紹:visual c++ 6.0來製作dll,使用java sdk 1.4來編譯java
java部分的實作
程式碼如下(HelloWorld.java):
class HelloWorld { 
  public native void displayHelloWorld();//native型態是通知Java這個函數會使用到外部函數 
 
  static { 
    System.loadLibrary("hello"); //載入hello.dll的動態連結
  } 
 
  public static void main(String[] args) { 
    new HelloWorld().displayHelloWorld(); 
  } 
}
將這個程式存檔為'HelloWorld.java'先用
'javac HelloWorld.java'
這個指令將它組譯成HelloWorld.class,然後使用
'javah HelloWorld'
產生HelloWorld.h,在HelloWorld.h中我們要實作一個函數
Java_HelloWorld_displayHelloWorld(JNIEnv *, jobject);
首先啟動visual c++ 6.0,在檔案中選擇新增,然後在project中選擇Win32 Dynamic-Link Library這個選項,填寫專案的名稱'hello',按下'Ok',請選擇An empty Dll project這一選項,然後按'Finish'再按下'Ok'。
經過上述動作之後我們就會得到一個空的專案資料,然後我們需要增加一些程式到專案中,在這之前先處理好.h檔,首先先到安裝java JDK的目錄下找到include的資料夾,為了以後撰寫程式方便,將這個目錄下的所有檔案複製一份到你安裝visual c++ 6.0目錄下VC98\Include裡面,預設路徑為
C:\Program Files\Microsoft Visual Studio\VC98\Include
將資料複製到此資料夾中,並將原本放在win32中的jawt_md.h和jni_md.h複製一份至
C:\ Program Files\Microsoft Visual Studio\VC98\Include
完成這些前置動作之後,再來將我們剛剛產生的HelloWorld.h加入我們的Header Files中,並新增一個.c的檔案HelloWorldImp.c,到上方的功能表中Build選則'Build hello.dll',將會得到一個hello.dll在專案目錄下的Debug\hello.dll
HelloWorldImp.c程式碼如下:
#include 
#include "HelloWorld.h" 
#include 
 
JNIEXPORT void JNICALL 
Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj) 
{ 
printf("HelloWorld"); 
return; 
}
HelloWorld.h程式碼如下:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include 
/* Header for class HelloWorld */ #ifndef _Included_HelloWorld #define _Included_HelloWorld 
#ifdef __cplusplus extern "C" { #endif 
/* 
* Class: HelloWorld 
* Method: displayHelloWorld 
* Signature: ()V 
*/ 
JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld 
(JNIEnv *, jobject); 
#ifdef __cplusplus } #endif 
#endif
然後將hello.dll和HelloWorld.class放在同一個目錄下,執行java HelloWorld,出現列印HelloWorld在畫面上的結果
以上這是一個小小的範例,目的只是在做一個小小的實驗,因為沒有一個人會因為要列印一串自救去呼叫C語言來幫你做事,而會直接使用System.out.print("HelloWorld"),比較快一點.透過以上這個小實驗讓我們知道java和c之間是有辦法互通的,因此或許java可以透過這個方式與作業系統之間有更密切的互動

Java Annotation Example


使用步驟:
步驟1. 定義註文型態(Annotation type)
1. Annotation type 的宣告類似 interface 的宣告,使用 "@" 記號後接 interface 關鍵字和中括號 "{}",中括號內
的members 可以是 methods、enums、variables、或 inner classes/interfaces 等。

2. 註文中的 members
A. methods 宣告
(1) method declaration 用來定義 Annotation type 的 element。
(2) method declaration 不能有任何的參數或 throws 子句。
(3) return type 只能限定為 primitives、String、class、enums、Annotations、或這些型別的陣列。
(4) method 可以有預設值(default values),使用關鍵字 default 設定。
(5) method 存取權限不能為 protected、或 private。
B. enums(或稱 enumeration)
enums 的預設存取權限為 public。
enums 可用來作為 Annotation type 的預設值。

3. Annotation type 內沒有任何的 elements 稱為 marker Annotation type。
marker Annotation 在使用的時候,其後面的小括號(parenthes)可以省略。

4. 用來對 Annotation type 做註文的 Annotations 稱為 meta-Annotations。
meta-Annotation 的種類:
A. @Target --定義在 java.lang.Annotation.Target
此種 meta-Annotation 使用在 java.lang.Annotation.ElementType 列舉中定義的 constants,以 "@Target"
指明Annotation type 適用之處,如只用於 fields、或 constructors 等 。
B. @Retention --定義在 java.lang.Annotation.Retention
使用在 java.lang.Annotation.RetentionPolicy 列舉中定義的 constants,以 " "@Retention" 告知編譯器如何
處理Annotation,處理方式有:
(1) RetentionPolicy.RUNTIME --編譯過的 class 檔案內會保留 Annotations,並在 class 載入時讀取註文。
(2) RetentionPolicy.CLASS --編譯過的 class 檔案內會保留 Annotations,但在 runtime 時會忽略註文,為
預設行為。
(3) RetentionPolicy.SOURCE --編譯過後的 class 檔案會移除 Annotations。
C. @Documented  --定義在 java.lang.Annotation.Documented
屬於 marker Annotations,表示 Annotation 應出現在 Javadoc 中。預設情況下,Annotations 不會寫入
Javadoc檔案內。
須配合 @Retention 指明 RetentionPolicy.RUNTIME 兩者一起使用。
D. @Inherited --定義在 java.lang.Annotation.Inherited
設定基底類別內的 Annotation type 會被衍生類別自動繼承。預設值為不繼承。
當 annotated type 使用在類別上時,設定 @Inherited 才有作用;且 @Inherited 的作用只及於類別被繼
承,若為介面的實作則無作用。不過實際上試驗結果@Inherited好像沒有作用!?

範例: 定義 Annotation type
public @interface MyAT {
 int myid();
 String myname() default "[unassigned]"; 
 String mybirthday() default "[unimplemented]"; 
}
範例: meta-Annotations
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test { }
範例: meta-Annotations
import java.lang.Annotation.Documented;
import java.lang.Annotation.Retention;
import java.lang.Annotation.RetentionPolicy;

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation { }


步驟2. 使用註文(Annotation)
1. Annotation 是一種特別形式的 modifier,可用在一般 modifiers(如 public、static、或 final)能用的地方。
2. Annotations 應放在一般 modifiers 的前面。
3. Annotations 的組成:
由 "@" 符號,後面加上 Annotation type 名稱和小括號 "( )" 組成,括號內為成對的
"element=value",以 "=" 等號指派 element 的值為 value,多組 "element-value" 以 "," 逗號間隔,其中 values
必須是常數(compile-time constants)。要傳遞多組的 value 給 element,須使用陣列的方式。
4. 如果註文型態僅包含一個元素,且該元素的名字為 value 時,則在使用此種注文型態時候,元素的名字和等號可以省
略;若元素名稱不是 value,則不能省略。
範例:
@interface MyAT1 {
  String myAuthor() ; 
  String myDate() ; 
}
public class MyClass {
  @MyAT1( myAuthor = "Ken", myDate = "4/1/1964")
  public static void MyMethod( ) { ... }
  // ......
}
範例:
public @interface MyMarkerAT { }  // Marker AAnnotation Type
@MyMarkerAT public class MyClass {  // 省略 marker Annotations 後的小括號
  ... 
}
範例:
@interface Copyright { String value(); }

@Copyright("2002 Moon Macrosystems, Inc") 
public class MyClass { ... }

Annotations的種類


1. Normal Annotations(Full Annotations)
也就是一般形式的 Annotations
範例:
@MyNormalAnnotation(element1="val1", element2="val2")  
public void someMethod() { ... }
2. Single Member Annotations
Annotations 的成員 method 只有一個,這種情況下可以把該 method 命名為 value,以方便使用時直接指定其值。
範例:
@MySingleMemberAnnotation("a single value") public class SomeClass { ... }
3. Marker Annotations
這種 Annotations 只是一種標記作用,沒有任何成員。
範例:
@Deprecated public void doWork()  { ... }

4. Annotation type 內包含其他的 Annotation type
@interface MyATType1 {
     int valX() default 9;
     int valY() default 0;
}

@interface HelloAnnotation {
     enum myScope { LOCAL, WORLD }
     String name() default "Joe";
     myScope scope() default Scope.LOCAL;
     MyATType1 myAnotherAT();
}
....
@HelloAnnotation(scope=HelloAnnotation.myScope.WORLD, myAnotherAT=@MyATType1(valX=5, valY=2))
void myMethod1() {     }