8/5/2009 2:30:49 AM

JPEG XR (就是 Microsoft HD Photo 啦) 已經是 ISO 正式標準了...

Microsoft.NET | 543 | WPF | 技術隨筆

先寫在前面,這篇不是什麼技術的探討或是評論,純脆是我個人看到這消息的想法而已。很久沒貼些軟體相關的文章了,最近比較少在動手寫 Code, 自然就沒什麼新題材好寫 @@,不過這兩天倒是看到一個蠻令人興奮的新聞,就是:

 

JPEG XR 已經正式通過 ISO 標準了!!

 

http://jpeg.org/newsrel26.html
http://blogs.msdn.com/billcrow/archive/2009/07/29/jpeg-xr-is-now-an-international-standard.aspx

 

JPEG 應該已經無人不知,無人不曉了吧? 不過當年還是有朋友鬧過笑話... 曾有人正經八百的來問我

"什麼是 [結合照片專業群組] 啊???" 就是 JPEG 啦 (無聊的話看一下底下的題外話)

我還丈二金剛摸不著頭腦,把他在看的整篇文章拿過來看,才晃然大悟他到底在問啥 =_= ... 原來是 "JPEG: Joint Photographic Experts Group”的縮寫... 當然類似的 MPEG (Moving Picture Experts Group) 也碰過類似的笑話... 無聊 GOOGLE 一下,竟然還查的到一篇範例...

http://support.microsoft.com/default.aspx/kb/235928/zh-tw

My God… 這翻譯真是比之前碰到了 "註冊傑克" 還絕 XD...

之前其實沒特別注意這些標準,曾經有印像的就是用 wavelet 壓縮方式的 JPEG2000... 嘗試取代 JPEG,也取得 ISO 的標準化,不過一直沒達成它的目的,只在特定領域還有應用空間。兩年前 Microsoft 隨著 Vista / WPF 推出 Windows Media Photo 的格式,後來為了讓它成為標準,換了個叫沒有 MS 色彩的名字: HD Photo, 最後變成現在的 JPEG XR ..

我是在兩年前,隨著 .NET 3.0 推出 WPF,剛好自己用的 CANON 相機的 RAW FILE 又被 WPF 支援,所以開始研究相關的 API 及 support .. 在關於 HD Photo 眾多報導中,有個觀點是我相當認同的。找不到較具代表性的消息來源,我就憑記憶寫一下,大意是:

 

隨著技術進步,未來影像設備 (如印表機,掃描器,顯示器等等) 的色彩表現能力及色域會遠超過 JPEG 格式的範圍 (現在就是了),因此儲存格式支援的動態範圍 (dynamic range) 越高,對於影像的長期保存越重要。

 

這就是處女座的龜毛個性啊... 衝著這個看法,我從 Canon PowerShot G2 時代開始,我就試著盡量用 .CRW 格式 (CANON RAW) 來保存相片,而不是用 JPEG。後來換了 Canon PowerShot G9,正好 WPF 出來,我就開始改用保存 .CR2 檔,而另外轉一份 JPEG 檔來作一般用途 (畢竟 JPEG 還是方便的多)。不過一張照片花掉 15 ~ 20mb, 保存起來壓力還真不小 =_=

 

現在看到 JPEG XR 的標準化,正好是我要的東西啊 :D   我需要的正是個能妥善保存這些影像資料細節的方式,同時能讓我輕鬆愉快的使用,不用耽心工具支不支援,或是其它五四三等問題困擾...。這些問題對阿宅來說,一點都不困難,有一缸子的工具辦的到,不過... 如果隨變看個照片,或是要 COPY 給家人朋友看,還要動用一堆雞絲,那也太辛苦了一點... 能有個通用的標準格式及大廠背書,那是再好也不過了 :D

 

所以,接下來要做什麼? 我突然慶興我一直都有留著這幾年拍下來的 RAW file (.CRW / .CR2) 檔案... 該是替我的歸檔程式翻新的時後了,下一步是開始嘗試用 .WDP 來取代現在放兩份 RAW + JPEG 的方式...



8/28/2008 2:57:00 AM

Canon G9 害我沒睡好... 相片自動轉正的問題

Microsoft.NET | 543 | WPF | 技術隨筆 | 小技巧

抱怨一下,因為在看照片時發現,有些直的拍的照片看起來是正確的 (會自己轉 90 度),有些卻不是... 得歪著頭看,所以就很好奇到底是怎麼回事...。

 

image

這幾張是正確的 (右上 & 左下,會自動轉 90 度)

 

image

這幾張是錯的 (應該自動轉 90 度才對)

 

image

第二張是錯的 (應該要轉 180 度才對)

 

越想越不對,我記得除了我那台古董 CANON G2 之外,後來 CANON 的相機都有加上偵測轉向的機制啊? 不外乎是水銀開關或是類似的東西,總之相機能夠得知目前是不是轉直的拍,同時能把這資訊寫到 EXIF 內的 ORIENTATION 欄位去...

但是為什麼拍出來的相片有的可以自動轉,有的不行? 花了點時間歸納一下,發現 G9 拍出來的 "有機會" 是正常的,而家裡大人的 IXUS55 則都不會自動轉,真是怪了... 在相機上看都會自動轉正啊...

因為我的照片都是自己用 WPF 寫程式縮圖處理的,我開始懷疑是不是我的歸檔程式的問題。G9 拍的 .CR2 檔,透過 RAW CODEC 轉成 JPEG 會自動轉正,G9 / IXUS55 拍的 JPG 檔則不會...

 

 

image

嗯,開始無聊了,拿起相機拍了四種角度,然後用 DEBUGGER 去看 EXIF 的 ORIENTATION 值為啥... .CR2 要用 "/ifd/{ushort=274}" 來查,會得到一個 UInt16 的值,如果是 .JPG 則要改成 "/app1/{ushort=0}/{ushort=274}" ...

得到的值還真怪。.CR2 / .JPG 都一樣。依照上圖的順序,得到的值分別是 0x01, 0x08, 0x01, 0x06。查了查文件,除了轉 180 度那個的值不大對之外,另外三個都正常。不過 Canon Codec 在 decode 時會自動幫我做 RotateTransform,我得在處理 .JPG 時自己補上這個動作。除了轉 180" 之外其它都正常了。

 

就是那張 180 度的不正確,害我氣的牙癢癢的... 決定跟它拼了... 改了改程式,把所有 EXIF 都印出來,用肉眼一個一個比... 本來想說是不是有別的 FLAG 可以判定出來正反? 結果看到眼睛脫窗了也沒找到,我連 EXIF 裡的 BLOB (Binary data) 都一個一個印出來看 @_@,GOOGLE 也找不到啥有用的資訊...

最後氣到,拿起相機重拍一次,這次直接在相機上看,按下 DISPLAY 看看有無其它資訊... COW,終於發現... 相機自己也認不出轉 180 度的狀況? 嘖嘖... 搞了半天 CANON 只有偵測左轉及右轉 90 度的情況,轉 180 度就不理它了.. 哈哈!

不過有誰會轉 180 度拍照? 當然有...

  1. 用右手拿相機自拍,按不到快門... 只好轉 180 度,用底下的姆指來按
  2. 小孩拿著自己亂拍
  3. 想不出來了...

總算水落石出,犧牲了幾個小時的睡覺時間,咳咳... 不過既然本版都是討論 .NET 程式設計的,最後就貼點 CODE 充個數... 也算沒偏離主題了 :D 不過我想應該沒人像我一樣無聊在搞這些吧?

 

取得 ORIENTATION 的值,並且判定要往那個方向旋轉[copy code]
                        BitmapMetadata metadata = null;                        Rotation rotate = Rotation.Rotate0;                        // ...	                        ushort value = (ushort)metadata.GetQuery("/app1/{ushort=0}/{ushort=274}");                        if (value == 6)                        {                            rotate = Rotation.Rotate90;                        }                        else if (value == 8)                        {                            rotate = Rotation.Rotate270;                        }                        else if (value == 3)                        {                            rotate = Rotation.Rotate180;                        }
   1:  BitmapMetadata metadata = null;
   2:  Rotation rotate = Rotation.Rotate0;
   3:  // ...  
   4:  ushort value = (ushort)metadata.GetQuery("/app1/{ushort=0}/{ushort=274}");
   5:  if (value == 6)
   6:  {
   7:      rotate = Rotation.Rotate90;
   8:  }
   9:  else if (value == 8)
  10:  {
  11:      rotate = Rotation.Rotate270;
  12:  }
  13:  else if (value == 3)
  14:  {
  15:      rotate = Rotation.Rotate180;
  16:  }

 

 

 

利用 TransformGroup, 一次套用 ScaleTransform (縮放) + RotateTransform (旋轉) 兩種轉換特效[copy code]
            JpegBitmapEncoder target = new JpegBitmapEncoder();            TransformGroup tfs = new TransformGroup();            tfs.Children.Add(new ScaleTransform(0.1, 0.1));            switch (rotate)            {                case Rotation.Rotate90:                    tfs.Children.Add(new RotateTransform(90));                    break;                case Rotation.Rotate180:                    tfs.Children.Add(new RotateTransform(180));                    break;                case Rotation.Rotate270:                    tfs.Children.Add(new RotateTransform(270));                    break;            }            target.Frames.Add(BitmapFrame.Create(                    new TransformedBitmap(sourceFrame, tfs),                    null,                    null,                    null));            target.QualityLevel = quality;            //            // save to temp file            //            string temp = Path.Combine(Path.GetDirectoryName(trgFile), string.Format("{0:N}.tmp", Guid.NewGuid()));            FileStream trgs = File.OpenWrite(temp);            target.Save(trgs);            trgs.Close();
   1:  JpegBitmapEncoder target = new JpegBitmapEncoder();
   2:  TransformGroup tfs = new TransformGroup();
   3:  tfs.Children.Add(new ScaleTransform(0.1, 0.1));
   4:  switch (rotate)
   5:  {
   6:      case Rotation.Rotate90:
   7:          tfs.Children.Add(new RotateTransform(90));
   8:          break;
   9:      case Rotation.Rotate180:
  10:          tfs.Children.Add(new RotateTransform(180));
  11:          break;
  12:      case Rotation.Rotate270:
  13:          tfs.Children.Add(new RotateTransform(270));
  14:          break;
  15:  }
  16:  target.Frames.Add(BitmapFrame.Create(
  17:          new TransformedBitmap(sourceFrame, tfs),
  18:          null,
  19:          null,
  20:          null));
  21:  target.QualityLevel = quality;
  22:  //
  23:  // save to temp file
  24:  //
  25:  string temp = Path.Combine(Path.GetDirectoryName(trgFile), string.Format("{0:N}.tmp", Guid.NewGuid()));
  26:  FileStream trgs = File.OpenWrite(temp);
  27:  target.Save(trgs);
  28:  trgs.Close();


6/23/2008 3:50:00 AM

利用 WPF 讀取 CANON (.CR2) 的 EXIF 及縮圖 (C# 範例程式說明)

Microsoft.NET | 543 | WPF | 技術隨筆

因為有人留話在詢問怎麼利用 WPF 處理這些動作,而這些又都是 M$ 文件及範例沒有寫的很清楚的部份... 有些範例有提到,但是 M$ 內建的 CODEC 正常,3RD PARTY 的 CODEC (像我碰到的 Canon Raw Codec) 就搞半天也搞不出來...。因為之前寫 MediaFiler 正好都碰過這些釘子,就順手寫起來以免以後又忘掉.. :P

 

1. 讀取 Metadata

講 METADATA 也許會有些人一頭霧水,講 EXIF 搞不好還比較多人知道... EXIF 的規格有點混亂,就像 RSS 一樣,有好幾個派係,M$ 乾脆在 WPF 裡跳出來直接稱它做 METADATA,也搭配了 METADATA QUERY LANGUAGE 來避開像 EXIF 一堆必需事先定義好 PROPERTY NAME 的麻煩事...

所以所有的動作都集中在兩件事上面,一個是怎麼操作 METADATA ? 另一個是我要的資料到底藏在那? (你得知道什麼樣的 QUERY 才抓的到你要的資料?)

先看簡單的,貼段 SAMPLE CODE 就搞定了..

SAMPLE 1. 如何讀取 METADATA ?[copy code]
                string srcFile = @"SampleRawFile.CR2";                //                //  sample 1. read metadata                //                FileStream fs = File.OpenRead(srcFile);                BitmapMetadata metadata = BitmapDecoder.Create(                    fs,                    BitmapCreateOptions.None,                    BitmapCacheOption.None).Frames[0].Metadata as BitmapMetadata;                foreach (string query in queries)                {                    object result = metadata.GetQuery(query);                    Console.WriteLine("query[{0}]: {1}", query, result);                }                fs.Close();
   1:  string srcFile = @"SampleRawFile.CR2";
   2:  //
   3:  //  sample 1. read metadata
   4:  //
   5:  FileStream fs = File.OpenRead(srcFile);
   6:  BitmapMetadata metadata = BitmapDecoder.Create(
   7:      fs,
   8:      BitmapCreateOptions.None,
   9:      BitmapCacheOption.None).Frames[0].Metadata as BitmapMetadata;
  10:  foreach (string query in queries)
  11:  {
  12:      object result = metadata.GetQuery(query);
  13:      Console.WriteLine("query[{0}]: {1}", query, result);
  14:  }
  15:  fs.Close();

 

扣掉 COMMENTS 只有12行... 哈哈,看起來好像也沒什麼好講的,關鍵就只有一個,STREAM別太早關掉...。以前我就吃過虧,試了好久才知道原來是我太雞婆,在第10行之前就把 fs 給關了,後面通通讀不出來...

不過關鍵的 queries (型別是 string[] ),內容到底是啥? 老實說我也不是很清楚 :P 先貼一下建立這字串陣列的 CODE:

建立 CR2 支援的 METADATA QUERY[copy code]
        private static string[] queries = new string[] {            "/ifd/{ushort=256}",            "/ifd/{ushort=257}",            "/ifd/{ushort=258}",            "/ifd/{ushort=259}",            "/ifd/{ushort=262}",            "/ifd/{ushort=270}",            "/ifd/{ushort=271}",            "/ifd/{ushort=272}",            "/ifd/{ushort=273}",            "/ifd/{ushort=274}",            "/ifd/{ushort=277}",            "/ifd/{ushort=278}",            "/ifd/{ushort=279}",            "/ifd/{ushort=282}",            "/ifd/{ushort=283}",            "/ifd/{ushort=284}",            "/ifd/{ushort=296}",            "/ifd/{ushort=306}",            "/ifd/{ushort=315}",            "/ifd/{ushort=34665}/{ushort=33434}",            "/ifd/{ushort=34665}/{ushort=33437}",            "/ifd/{ushort=34665}/{ushort=34855}",            "/ifd/{ushort=34665}/{ushort=36864}",            "/ifd/{ushort=34665}/{ushort=36868}",            "/ifd/{ushort=34665}/{ushort=37377}",            "/ifd/{ushort=34665}/{ushort=37378}",            "/ifd/{ushort=34665}/{ushort=37380}",            "/ifd/{ushort=34665}/{ushort=37386}",            "/ifd/{ushort=34665}/{ushort=37500}",            "/ifd/{ushort=34665}/{ushort=37510}",            "/ifd/{ushort=34665}/{ushort=40960}",            "/ifd/{ushort=34665}/{ushort=40961}",            "/ifd/{ushort=34665}/{ushort=41486}",            "/ifd/{ushort=34665}/{ushort=41487}",            "/ifd/{ushort=34665}/{ushort=41488}",            "/ifd/{ushort=34665}/{ushort=41728}",            "/ifd/{ushort=34665}/{ushort=41985}",            "/ifd/{ushort=34665}/{ushort=41986}",            "/ifd/{ushort=34665}/{ushort=41987}",            "/ifd/{ushort=34665}/{ushort=41988}",            "/ifd/{ushort=34665}/{ushort=41990}",            "/ifd/{ushort=34665}/OffsetSchema:offset"        };
   1:  private static string[] queries = new string[] {
   2:      "/ifd/{ushort=256}",
   3:      "/ifd/{ushort=257}",
   4:      "/ifd/{ushort=258}",
   5:      "/ifd/{ushort=259}",
   6:      "/ifd/{ushort=262}",
   7:      "/ifd/{ushort=270}",
   8:      "/ifd/{ushort=271}",
   9:      "/ifd/{ushort=272}",
  10:      "/ifd/{ushort=273}",
  11:      "/ifd/{ushort=274}",
  12:      "/ifd/{ushort=277}",
  13:      "/ifd/{ushort=278}",
  14:      "/ifd/{ushort=279}",
  15:      "/ifd/{ushort=282}",
  16:      "/ifd/{ushort=283}",
  17:      "/ifd/{ushort=284}",
  18:      "/ifd/{ushort=296}",
  19:      "/ifd/{ushort=306}",
  20:      "/ifd/{ushort=315}",
  21:      "/ifd/{ushort=34665}/{ushort=33434}",
  22:      "/ifd/{ushort=34665}/{ushort=33437}",
  23:      "/ifd/{ushort=34665}/{ushort=34855}",
  24:      "/ifd/{ushort=34665}/{ushort=36864}",
  25:      "/ifd/{ushort=34665}/{ushort=36868}",
  26:      "/ifd/{ushort=34665}/{ushort=37377}",
  27:      "/ifd/{ushort=34665}/{ushort=37378}",
  28:      "/ifd/{ushort=34665}/{ushort=37380}",
  29:      "/ifd/{ushort=34665}/{ushort=37386}",
  30:      "/ifd/{ushort=34665}/{ushort=37500}",
  31:      "/ifd/{ushort=34665}/{ushort=37510}",
  32:      "/ifd/{ushort=34665}/{ushort=40960}",
  33:      "/ifd/{ushort=34665}/{ushort=40961}",
  34:      "/ifd/{ushort=34665}/{ushort=41486}",
  35:      "/ifd/{ushort=34665}/{ushort=41487}",
  36:      "/ifd/{ushort=34665}/{ushort=41488}",
  37:      "/ifd/{ushort=34665}/{ushort=41728}",
  38:      "/ifd/{ushort=34665}/{ushort=41985}",
  39:      "/ifd/{ushort=34665}/{ushort=41986}",
  40:      "/ifd/{ushort=34665}/{ushort=41987}",
  41:      "/ifd/{ushort=34665}/{ushort=41988}",
  42:      "/ifd/{ushort=34665}/{ushort=41990}",
  43:      "/ifd/{ushort=34665}/OffsetSchema:offset"
  44:  };

 

因為我之前處理的目標,是在做轉檔的動作,同時要忠實的把 METADATA 也複製過去,因此得到這列表對我來說很重要,但是搞懂每個項目的意義是啥我就不管了,先列出來再說... 需要的人請自行判斷....

 

 

2. 建立縮圖

WPF 架構滿分,但是效能多少會打折扣。JPEG實在看不大出來,拿CANON的CODEC來看就很清楚了。如果是以 .CR2 -> .JPG,不作大小的縮放,CANON 自家的 DPP 大概要廿幾秒,CANON自家寫給 WPF 用的 CODEC 搭配 .NET 的 WPF 程式,則要 60 秒左右,測試的機器就我這台 Vista + 4GB RAM, CPU 是 Core2Duo E6300...

速度實在是不快,也不是用 .NET 寫效能太糟的原因,因為 VISTA 內建的秀圖程式也是靠同一個 CODEC,效能也差不多... 不過 CODEC 的架構設計的很好,如果我要的只是縮圖,那就不一樣了... 來看第二個範例:

SAMPLE 2. 建立原圖 (.CR2) 1/10 大小的 JPEG 縮圖[copy code]
                Stopwatch timer = new Stopwatch();                timer.Start();                FileStream fs = File.OpenRead(srcFile);                FileStream fs2 = File.OpenWrite(Path.ChangeExtension(srcFile, ".jpg"));                BitmapDecoder source = BitmapDecoder.Create(                    fs,                    BitmapCreateOptions.None,                    BitmapCacheOption.None);                JpegBitmapEncoder target = new JpegBitmapEncoder();                target.Frames.Add(BitmapFrame.Create(                    new TransformedBitmap(source.Frames[0], new ScaleTransform(0.1, 0.1)),                    null,                    null,                    null));                target.QualityLevel = 90;                target.Save(fs2);                fs.Close();                fs2.Close();                timer.Stop();                Console.WriteLine("Create 0.1x thumbnail: {0} ms.", timer.ElapsedMilliseconds);
   1:  Stopwatch timer = new Stopwatch();
   2:  timer.Start();
   3:  FileStream fs = File.OpenRead(srcFile);
   4:  FileStream fs2 = File.OpenWrite(Path.ChangeExtension(srcFile, ".jpg"));
   5:  BitmapDecoder source = BitmapDecoder.Create(
   6:      fs,
   7:      BitmapCreateOptions.None,
   8:      BitmapCacheOption.None);
   9:  JpegBitmapEncoder target = new JpegBitmapEncoder();
  10:  target.Frames.Add(BitmapFrame.Create(
  11:      new TransformedBitmap(source.Frames[0], new ScaleTransform(0.1, 0.1)),
  12:      null,
  13:      null,
  14:      null));
  15:  target.QualityLevel = 90;
  16:  target.Save(fs2);
  17:  fs.Close();
  18:  fs2.Close();
  19:  timer.Stop();
  20:  Console.WriteLine("Create 0.1x thumbnail: {0} ms.", timer.ElapsedMilliseconds);

 

範例程式也很簡單,扣掉計時的程式碼只有 16 行... 裡面兩個關鍵的參數,分別為第 11 行, 0.1 代表 Scale Transform 用的縮放比例,0.1 就是只要 1/10 的大小。如果你想要固定尺寸的縮圖,得先自行計算出這個 SCALE 的值。要單純的轉檔,就填 1.0 就好。

另一個參數是第15行的 90,它是指存成 JPEG 時要使用的 QUALITY,100 最好,越低失真越嚴重,但是相對的檔案大小也會大幅下降。一般用的 QUALITY 大約都在 75% ~ 90%,其實縮圖 75% 就夠了,反正看不大出來 (H)

 

我用 CANON G9 拍出來的 RAW (4000 X 3000),存成 400x300 JPEG,約要花費 1.5 秒,比原圖尺寸的 80 秒差太多了,可見 CODEC 一定針對這種需求作過最佳化,會有效避開縮圖時不必讀取的部份資料... 以加快速度。

同樣程式改成不縮圖看看,如果不縮圖的話,ScaleTransform也可以省了,直接把 Frame 加進去就好,原程式的 line 10 ~ line 14,換成這段:

不縮圖的替代程式碼[copy code]
                target.Frames.Add(source.Frames[0]);
   1:  target.Frames.Add(source.Frames[0]);

 

嘖,只有一行也在貼... 沒錯,就是這樣而以。跑出來的時間約為 65 秒...

這部份我就沒仔細的拿 DPP 比較過了。不過當你縮圖的尺寸降低時,的確是能有效的加快速度。不過如果對於 ASP.NET WEB 應用程式來看,1.5 秒還是太慢了 @_@,十個人連上來就會想哭了,怎麼辦? 只好善用 CACHE,不然就買本六月號 RUN PC 看看我投的那篇文章... 哈哈...

 

 

----

PS: 這篇是寫給對岸的那位愛賞鳥的網友看的,希望有解決到你的問題。完成後也記得給我欣賞一下你們的攝影作品 :D

PS2: 範例程式放在這裡,請自行下載。圖檔請放同目錄的 SampleRawFile.CR2

PS3: 家裡大人不准我買 PS3 ...



4/10/2008 2:03:00 AM

Canon CR2 --> .JPEG 速度加倍.. 該換 Core2 Quad 了嘛?

Microsoft.NET | 543 | Threading | WPF | 我的作品 | 技術隨筆

看來是換四核心CPU的時機到了 [H]

之前弄了半天的歸檔程式,效能都卡在 .CR2 -> .JPG 這段。雖然祭出了 ThreadPool,也想盡辦法把獨立的工作湊在一起,盡量提升 CPU 的利用率,不過得到的效果有限,因為最後都是剩下 .CR2 的檔案轉不完啊,其它拿來填空檔的工作早就做完了,實在不成比例... 整體效能還是卡在最慢的 Canon Codec 身上...

這次無意間想到,單一 process 內,Canon Codec 有過多不能重複進入的問題 (不能同時利用到兩顆CPU),那麼拆成兩個獨立的 process 是否就解決了?

想一想還蠻可行的,通常為了安全,都只需要做到 process 內的 LOCK 就夠了,不需要做到全域的 LOCK,除非要 LOCK 的資源是跨 process 會用的到的才需要這樣做... 在真正改程式下去之前,當然要先驗證一下...

用之前的 LIB 簡單寫了個執行檔,就單純的把 .CR2 轉 .JPG 而以。寫好後同時 RUN 兩份,讚! CPU 利用率飆到 80% (雖然離理想的 100% 還有段距離) ... 不過在我的 E6300 CPU,同時跑兩份,執行速度倒沒有下降,差不多..

確定這方式有效之後,花了點時間改我的歸檔程式,把轉檔部份抽成 .exe 然後由歸檔程式來啟動,一樣維持同時有兩個 .CR2 轉檔程序進行。耶! 情況不錯,轉一個檔案一樣要 70 秒,但是 70 秒過後可以轉完兩個檔案... 平均起來等於速度加倍了...

之前一直不想用這個方法,因為一來 IPC (inter-process communication) 是件麻煩事,不想去碰,二來啟動另一個 process,參數的傳遞也是個麻煩事,大概就只能靠檔案或是 arguments ... 或多或少都要處理到一些 parsing 的問題... 三來執行的回傳值也是一樣,總之都碰到 IPC 的問題就不想去碰他了

不過這次評估了一下,這些動作麻煩歸麻煩,至少不是花運算資源的動作,跟一張照片轉 70 秒,一次動則上百張的量比起來,怎麼算都划算...

真糟糕,這樣下去是不是代表該換 Q9450 了? 咳咳...



12/11/2007 9:03:00 PM

Canon Raw Codec + WPF #2, ThreadPool

Microsoft.NET | Threading | WPF | 我的作品

效能問題, 就跟我自己寫的小工具一起講好了. 話說之前 Microsoft 提供了一個很讚的小工具: Resize Pictures Power Toys, 功能超簡單, 大概就是檔案總管把圖檔選一選, 按右鍵選 "Resize pictures" 就好了:

image

選了之後就有簡單的對話視窗:

image

很簡單吧, 我個人非常愛用, 而且轉出來的效果也不差, 看起來 JPEG quality 大約有 80% ~ 90% 吧... 無耐 windows xp 裡有幾個跟 image 相關的 power toys, 到了 vista 通通不能用. 看來應該都是碰到 GDI+ 要轉換到 WPF 的陣痛期吧, 這幾個小工具還真是讓我繼續撐在 XP 的主要理由之一...

扯遠了, 所以我的目標就是寫個類似的小工具, 讓我簡單的做批次縮圖就好. 有了上一篇的基礎, 要寫這種 tools 實在是沒什麼挑戰, 大概會寫 winform 的拉兩下就可以收工了...

 

不過, 大話說太早. 先貼一下成品的畫面, 後面說明比較清楚:

image

要做的東西很簡單. 選好一堆圖按右鍵選 resize pictures 後就跳這畫面, 按 Resize 就開始跑. 用的是前一篇弄好的 library. 結果碰到的障礙還不少. 雖然可以跑, 但是看了就很礙眼...

  1. 效能有點糟.
    比較好的架構一定會有額外的效能折損, 我倒可接受. 內建的 JPEG codec 還好, 比不上像 Google Picasa 那樣快速. 但是 canon raw codec 就慘不忍睹... 如果把 raw 轉成同大小的 jpg (4000x3000 pixel), 足足要 60 ~ 80 sec ...
  2. 沒針對多處理器最佳化
    簡單的說, 以我的雙核 CPU (Core2Duo E6300), 跑起來 CPU 利用率只有 5x% 而以.
  3. ThreadPool 也無法解決問題
    因為 (2), 就很直覺的聯想到, 我一次轉兩張, 同樣時間內可以完成兩張的轉檔, 單位時間的運算量還是有提升, 雖然每一張還是要花那麼久... 不過我錯了, 看來是 canon codec 的限制, 開 thread pool 跑下去, 一樣是卡在 60% cpu 使用率左右...
  4. UI thread 問題
    thread pool 也不完全沒有作用. jpeg encode / decode 的部份是可以充份利用到 thread pool 的好處的, 只是 canon raw decode 的部份用不到. 當部份時間是 canon raw decode + jpeg encode / decode 時, 剩餘的 CPU 運算能力還是吃的到. 但是 thread pool 無法控制 priority, pool 裡的 thread 就嚴重的影響到 UI thread 的作業. 常看到的現像就是進度列一直在跑, 不過預覽圖片的控制項卻一直跑不出來

 

(1) 的問題其實沒這麼嚴重. Microsoft HD Photo 有一個 feature, 就是大檔放在網路上, 你也能夠很快的透過網路看到小圖. 有點類似早期漸進式的 jpeg 那樣. 不過看起來 codec 的設計更好一點. 實驗的結果是, Full Size .CR2 (4000x3000) 存成同大小的 JPEG 檔需要 60sec, 而存成 800x600 只要 5 sec. 但是拿對照組 .JPG (4000x3000) -> .JPG (800x600), 差距又沒這麼大. 因此推測起來, 應該在 decode 階段就已經針對這樣的需求設計過了.

剩下的問題我試了好幾種方法, 目標都擺在如何安排這堆 thread 在合適的時間做合適的工作. canon codec 就不適合同時丟好幾個 thread 下去跑, 因為完全沒用, 反而拉長每個 .CR2 從開始到輸出的時間. jpeg 的部份就很適合, 因為時間短, 多核的好處也可以藉著多 thread 用的到. 另外 canon codec 因為限制較多, 我需要它以較高的 priority, 並且要在第一時間就開始跑, 才不會拖慢整個轉檔的處理時間... 理想的 task 安排狀況應該要像這樣:

簡報1

 

最後找到一個我比較滿意的解, 就是另外寫一個合用的 ThreadPool... @_@

 

其實我是很不想做重新發明輪子這件事, 不過除此之外實在沒什麼好方法. 所幸 .net 下要自己弄出個 thread pool 也不難. 這一切都要感謝當年在 yam, 當時研發部的一位主管, 交大的學長, 現在跑到 Microsoft 去了, 我們都叫他 "旺旺" .. 他技術能力只能用 "神" 來形容... 當時他在公司內開了門課, 真是印像深刻. 就用 java 示範了如何寫 ThreadPool ... 寫起來還真沒幾行... 扣掉一堆 import (相當於 c# 的 using) 等宣告之類的 code, 整個功能 "完整" 的 thread pool 大概只有一百行左右的 code... 而且 thread 動態 create 跟回收等功能一樣不少... threads 之間同步問題也沒漏掉..

我歸納了一下我需要的 ThreadPool 到底要什麼功能, 而內建的到底缺什麼... 要怎麼做就很清楚了... 要解決上面的問題, 我大概需要這樣的 thread pool 來支援我的想法:

  1. thread 的數量不需要是動態的, 固定的就夠了. 一次開太多 thread 效果不見得好.
  2. thread 一定要能設定 priority. 因為轉圖檔是 cpu bound 的工作, priority 設低一點對整體的回應時間比較好.
  3. 需要多組 thread pool. 我的想法是用一組專用的 thread pool 來處理 canon raw codec, 只要 1 thread 就夠 (以後 canon 真的改善的話再加大數量). 另外其它 (大部份都是 jpeg codec) 的工作就丟到有 4 threads 的 thread pool 去跑. 至少到四核的 cpu 都還能夠充份的利用到.
  4. 需要簡單的作法, 能夠 wait thread pool 裡的工作全處理完. .net 內建的也可以, 不過必需透過比較麻煩的 WaitHandle 自己去 wait ...

這些剛好都是我需要, 但是 .NET 內建的 thread pool 做不到的需求. 因此自己寫了個簡單的 SimpleThreadPool .. 介面規格就儘量比照內建的 ThreadPool (因為 code 已經寫好不想改太多 [:P]). 用起來像這樣:

 

   1:          private static void SimpleThreadPoolTest()
   2:          {
   3:              SimpleThreadPool stp1 = new SimpleThreadPool(2, System.Threading.ThreadPriority.BelowNormal);
   4:              SimpleThreadPool stp2 = new SimpleThreadPool(1, System.Threading.ThreadPriority.Lowest);
   5:   
   6:              stp1.StartPool();
   7:              stp2.StartPool();
   8:   
   9:              for (int count = 0; count < 10; count++)
  10:              {
  11:                  stp1.QueueWorkItem(
  12:                      new WaitCallback(ShowMessage),
  13:                      string.Format("STP1[{0}]", count));
  14:                  stp2.QueueWorkItem(
  15:                      new WaitCallback(ShowMessage),
  16:                      string.Format("STP2[{0}]", count));
  17:   
  18:                  Thread.Sleep(13);
  19:              }
  20:   
  21:   
  22:              Console.WriteLine("wait stop");
  23:              stp1.EndPool();
  24:              stp2.EndPool();
  25:          }
  26:   
  27:   
  28:          private static void ShowMessage(object state)
  29:          {
  30:              Console.WriteLine("ThreadID: {0}, state: {1}", Thread.CurrentThread.ManagedThreadId, state);
  31:              Thread.Sleep((new Random()).Next(1000));
  32:          }

 

嗯, 功力跟旺旺比差了一點, 不過也是一百出頭行就搞定這個 ThreadPool ... [:D], 接下來就是火力展示了... 因為介面跟內建的 ThreadPool 幾乎一樣. 就簡單測一下 125 JPEG + 20 G9 RAW + 2 G2 RAW files 一起做轉檔時的 CPU 使用率記錄...

圖一. 用內建的 ThreadPool, 110 sec ( UI 回應正常, 進度列也會跑. 不過礙於 CPU loading 關係, ImageBox 的圖都沒出來)

image

 

 

圖二. 改用我自己寫的 SimpleThreadPool, 90 sec. 因為調整過 priority, 每張圖轉完 ImageBox 都會立即顯示出來.

image

 

第一張圖, 所有的 job 都依序執行, 簡單的 jpeg 都擠在前段, 那段 cpu 100% 就是這樣來的. 後面就都是 canon decoder 在跑, cpu 大約都維持在 50% 左右, 直到跑完為止.

而第二張圖, jpeg / canon 都強迫同時一起執行. 而 canon 的 priority 略高於 jpeg. 因此排程的策略是優先執行較慢的 canon decoder, 而剩餘的 cpu 運算能力就拿來處理 jpeg 的部份. 因為 cpu 使用率的統計圖下的面積 (積分) 就是總共需要的運算量. 看的出來維持在 100% 的部份越短, 則總體完成的時間就會拉長... 後面自定的 thread pool 的作法, 不論在 UI 回應, 跟整體處理的時間都比較好. 看來適度的調整 thread 數量, 跟 thread priority 還是很有用的. 不過題外話, thread 再怎麼用, 效果還是不如 lib or compiler level 做的平行處理效果來的好. ZD Net 上有一系列 intel 提供的 video, 講的還不錯. Microsoft 也替 .NET 開發了一套 Library (download), 只要調整一點語法, 就可以把 loop 內的運算轉成平行運算, 這種效果遠比用 thread pool 來的聰明 & 有效. 不過還在 community preview 就暫時不考慮採用了.

總算, 搞定了 thread pool, 也搞定了 metadata, 幾個主要的障礙都排除了. 兩個要開發的工具也完成了一個 ( image resizer ), 剩下的歸檔程式就剩下納入 video encoder 的部份也就大功告成了. 有力氣的話會再寫一篇吧, 敬請期待 [:D]



12/11/2007 7:14:00 PM

Canon Raw Codec + WPF #1, WPF Image Codec, Metadata

Microsoft.NET | Threading | WPF | 我的作品 | 安德魯的當年勇 | 技術隨筆

託 Canon G9 的福, 這一個月來的空閒時間都在研究 Windows Presentation Foundation 裡的 Image Codec 相關事項. 幹嘛買個相機還這麼辛苦? 因為原本算計好的計劃, 就差這一環啊... 雖然老早就有換機的計劃, 為什麼龜了那麼久 G9 一出來就跑去買? 除了之前講了一堆相機本身的考量之外, 剩下的關鍵因素是 RAW support. 因為:

  1. Canon G9 "又" 開始支援 RAW file
  2. Canon 正好搭配 WPF (windows presentation foundation), 發表了它專屬的 RAW file codec.
  3. WPF 裡提供了許多 JPEG 無法帶來的好處, 我打的如易算盤是: 不管未來是什麼東西取代了 JPEG, 留著 RAW 一定沒錯, 因此支援 RAW 就大大加了不少分.

RAW support 在我看來是必要的. 照片可不能等十年後再搭時光機回來照, 而現有的 JPEG 又已經是老古董的規格了, 未來是一定會有取而代之的新規格. 會是什麼我不曉得, 不過留著 RAW 準沒錯. 只要 Canon 沒倒, 未來一定有辦法把 RAW 轉成新的通用格式, 而不用經過 JPEG 的折損...

未來看起來很美好, 不過當 G9 入手後, 事情沒有想像的順利, 有了 WPF + Codec, 我自己必需寫些小程式來簡化未來例行的相片歸檔動作. Codec 只要去 Canon 下載就有, WPF 則是新東西, 得自己先研究一番... 搭配 WPF, Microsoft 也推出了新的圖型檔格式: HD Photo. 它的一堆好處我就不多說了, M$ 網站多的是. 我在意的是, HD Photo 提供的新功能, 包括廣大的色域等, 也會對應的在 image codec 裡提供. 因此如果 RAW file 本身就包含這些資訊的話, 透過 codec 讀出來就不會有資訊的折損, 存成 JPEG 就沒有這些好處了.

實作第一步, 當然是先研究 WPF 關於 Image 物件的基本處理. 嗯, 果然是跟之前的 GDI / GDI+ 不一樣. 感覺起來 GDI 典型 application 就是像 "小畫家" 這類的 AP, 而 WPF 典型的 application 就是像 flash 這類的, 裡面的物件已經變成圖型來源, 套用各種 transform, 層層處理套上去後得到的結果才是你看到的東西, 大概就類似 photoshop 的 layer 那樣的東西.

講了那麼多, 其實我也只是要用到 codec 來讀取 canon raw file, 把圖檔縮成我要的像素, 存成指定的 jpeg 檔而以... 我就以這個例子貼一段 sample code

   1:  BitmapDecoder source = BitmapDecoder.Create(
   2:      new Uri(@"C:\IMG_0001.CR2"),
   3:      BitmapCreateOptions.DelayCreation,
   4:      BitmapCacheOption.None);
   5:  JpegBitmapEncoder target = new JpegBitmapEncoder(); 
   6:   
   7:  target.Frames.Add(BitmapFrame.Create(
   8:      new TransformedBitmap(source.Frames[0], new ScaleTransform(0.3, 0.3)),
   9:      source.Frames[0].Thumbnail,
  10:      metadata,
  11:      null)); 
  12:  target.QualityLevel = 80; 
  13:   
  14:  FileStream trgs = File.OpenWrite(@"C:\IMG_0001.JPG");
  15:  target.Save(trgs);
  16:  trgs.Close(); 

而過去也寫過 GDI+ 版本的, 那個我就不貼了. 老實說 code 也不會太多, 不過寫起來就覺的是兩種不同層次的思考邏輯. 也隨著這次機會花了點心思研究 System.Windows.Media.Imaging 裡的東西, 就開始想把舊的程式都翻一翻.. 包括了之前的歸檔程式, 及另一個批次縮圖的工具. 後面有機會再貼.

圖檔基本內容處理掉之後, 接下來就是 exif. 我很在意這些圖檔隱藏的資訊, 也不知道為什麼, 哈哈. 上面的 code 轉完是沒有半點 EXIF 的, 沒轉過來感覺就像少了什麼... 無奈在處理 metadata 時又碰到了一些小 trouble ...

 

花了一些時間搞定了 metadata, 不過又碰到不同檔案格式之間的 metadata 轉換問題. WPF 的 Image Codec 已經把 metadata 的讀寫方式給 "抽像化" 了, 所有圖檔的 metadata 都用一樣的方式讀寫. 它採用的是類似 xpath 的 metadata query 來指明目標是那個 metadata, 然後再用 GetQuery( ) 來讀值, 或是用 SetQuery( ) 把值寫進去. 現在碰到的問題是 Canon Raw 的 query 跟 JPEG exif 用的對應不起來. 我也不知道怎麼解, google 找幾個 sample 對照著比一比, 摸黑試了幾種對應方式, 竟然看起來還好像猜中了, 就不管先用下去. 我簡單的把整理的對照表貼一下...

/ifd/{ushort=256} --> /app1/ifd/exif/{ushort=256}
/ifd/{ushort=257} --> /app1/ifd/exif/{ushort=257}
...

請不要問我這是啥意思, 我真的也搞不懂, 看了 w3c 一些 spec, 真是天書... 大概只知道 metadata 有幾種規範, exif, xmp, ifd 等等. 而 ushort=256 大概就是指整個 block 裡, 第 256 bytes 位置的 ushort 的值就是這筆資料存放的地方等等. 我是拿幾張照片轉換後對照著看, 看起來對就將就著用了. 最後是用程式跑了一份看起來可用的對照表, 存成 xml 檔, 丟在自己寫的 library project 裡, 當作 embedded resource. 供未來轉檔的動作時拿出來用. library 包裝好之後用起來像這樣:

 

   1:  ImageUtil.SaveToJPEG(
   2:      @"c:\IMG_0001.CR2",
   3:      @"c:\IMG_0001.JPG",
   4:      800,
   5:      800,
   6:      75);

 

 

弄到現在, 總算把最基本的動作: 轉檔 (含 exif) 給搞定了. 總算有足夠的資訊把 library 給弄好. 不過馬上就碰到第二個大問題... "效能". 這部份就留著下一篇吧.



11/26/2007 3:08:00 AM

前言: Canon Raw Codec 1.2 + .NET Framework 3.0 (WPF)

Microsoft.NET | Threading | WPF | 我的作品 | 技術隨筆

搞了好幾天, 終於理出點頭緒了. 自從 Canon 推出 1.2 版的 codec 之後, G9 拍的 .CR2 支援問題總算是往前跨了一大步, 至少在 XP / Vista 下可以直接顯示 .CR2 了. 接下來就是如何讓它自動化等等問題.

WPF寫起來很簡單, 效率的問題就先擺一邊了 (很理想的預期 canon 會在未來版本會改善... 咳...), 不過 EXIF 的問題還真是令我傷透腦筋... 前前後後碰到幾個問題, 加上官方文件很多細節沒有提供, 讓我碰了不少釘子... Orz. 先整理一下碰過的釘子有那些 (現在當然拔乾淨了才有心情打這篇.. 哈哈 [H]), 先列一下問題最大的 metadata:

  1. Metadata抓不到, BitmapSource.Metadata 抓出來都是 null ..
    (後來發現文件漏掉一行... Orz, 目前版本不提供 BitmapSource.Metadata, 只提供每個 Frame 自己的 Metadata ...)
  2. 內建的 Metadata 只有十個不到的欄位 (ApplicationName, CameraModel, ...), 問題是 exif 有一堆啊..
  3. WPF 改用 "Metadata Query Language", 類似 xpath 之於 xml document 一樣... 看起來就像 "/ifd/{ushort1000}" 這樣. 所有底層動作都是 GetQuery( ) / SetQuery( ). 不過沒有地方讓我列舉出所有已存在的 metadata query 啊...
  4. 文件上說用 InPlaceMetadataWriter 可以修改 metadata, 不過到現在我還是試不出來 -_-
  5. EXIF 上百個欄位, 對應的 query, 官方文件一個字都沒提到... 冏rz...
  6. .CR2 解出來的 metadata 跟 .JPG 廣為接受的 EXIF, 對應的 query 完全不一樣...

另外其它跟 metadata 無關的問題也有幾個:

  1. Canon Codec 的效能不怎麼樣, G9 的檔案 (4000x3000, 15mb 左右) 全幅解開, 接上 JpegEncoder 存同尺寸 100% quality 檔案, 在我的 Core2Duo E6300 (2GB ram, XP MCE2005 SP2) 足足要 80 sec ...
  2. 多處理器佔不到便宜. 雙核CPU跑下去, 處理器只有約 50% ~ 60% 使用率. 改了改程式, 開兩個 thread 下去也一樣, 殘念... ( M$ 的就要誇一下, 內建的 codec 就運作的很好... 又快, thread pool 用下去也享受的到全速.. [Y])
  3. 怪的很, Microsoft 提供的 viewer 直接看 G2 的 .CRW 一切正常, 不過透過 WPF 就會得到 Exception .. 還沒解.

這些鳥問題都在 MSDN 找不到直接的答案, 只好埋頭苦TRY了. 好消息是主要問題都解的差不多了. 先簡單列一下 solution, 後續的等我 [歸檔程式] 改版完成後再來專欄報導..

  1. Metadata Query 列舉的問題原來隱藏在實作的 interface 裡.. [:@], 氣的是官方文件還沒有任何說明 & 範例. 原來 BitmapMetadata 實作了 IEnumerable<string>, 直接把 BitmapMetadata 丟到 foreach 裡就是了...
  2. 修改 metadata 的動作, 暫時由 metadata.clone() 之後修改, 再加到 encoder 裡可以閃開碰到的問題, 就不理它了
  3. EXIF 問題, 因為 (1) 有解了, 加上 google 找到其它 sample, 東拼西湊誤打誤撞也被我試出來... 哈哈
  4. 效能問題一樣無解, 只能改程式儘量把不相干的 job 排在一起, 想辦法把空閒的 CPU 運算能力吃掉... 就看是我改的快還是 canon 改的快了 [H]..

前言大概就先打到這裡, 沒啥內容, 都只是預告片而以, 被它折磨了兩個禮拜, 當然要先貼一篇發洩一下. 剩下的工作就單純多了, code 改到一個段落我就會繼續寫後續幾篇. 這次會包括兩個 project, 一個是 Image Resizer, 另一個就是之前好幾篇都在講的歸檔工具. 敬請期待!

替舊文章打一下廣告... [H]



4/3/2007 7:32:00 PM

Canon RAW Codec for Vista 出來了..

Microsoft.NET | 543 | WPF | 技術隨筆

資料來源: http://blogs.msdn.com/pix/archive/2007/03/30/canon-raw-codec-for-vista-release.aspx

前天才抱怨了一下, WPF 的 WIC 沒有內建 canon raw codec, 沒想到 2007/3/30 已經出來了, 真是失敬 [:p], 不過為什麼只支援 .CR2 啊啊啊啊啊.... 難道用老相機 G2 就註定是這種下場嘛? Orz

往好方面想, .CRW 的 codec 應該也會出吧? 嗯, 再等一等好了...






精選文章

RUN! PC 文章及範例下載
2010/07. 結合檔案及資料庫的交易處理
2010/05. TxF讓檔案系統也能達到交易控制
2010/04. 生產者 vs 消費者 - 執行緒的供需問題
2008/11. 生產線模式的多執行緒應用
2008/09. 用ThreadPool發揮CPU運算能力
2008/06. SEMAPHORE在ASP.NET的應用
2008/04. 以ASP.NET開發同步WEB應用程式

如何學好 "寫程式" 系列
#1. 該如何學好 "寫程式" ??
#2. 為什麼 programmer 該學資料結構 ??
#3. 進階應用 - 資料結構 + 問題分析
#4. 你的程式夠 "可靠" 嗎?

#5. 善用 TRACE / ASSERT

安德魯是誰?

Andrew Wu | Create Your Badge

我喜歡鑽研物件導向、軟體工程及作業系統等相關技術。我會在這裡發表我的研究心得,也當作我自己的學習筆記。


Recent comments

Comment RSS