My friends, my life, my style - James S.F. Hsieh

11/28/2009

由之前的文章 RGB 的物理意義是什麼? 可以了解到, RGB 是利用三原色合成的比例來描述色彩, 但是這不是唯一的一種描述方式, 我們可以用另一種方式來描述 RGB 所描述的色彩: 給定一個顏色, 它有多純 (摻雜白色的程度), 它有多亮. 這種描述方式就是所謂的 色相 Hue, 飽和度 Saturation, 亮度 Lightness, HSL (HSI, HSB) 色彩模型. 以下是將 H 定義為 0~360 度 S, L 定義正規劃在 0~1 之間的示意圖, 色彩分部會是一個錐體.



Figure 1


要如何做呢? Digital Image Processing 這本書的 4.6.1 節有完整的證明如何將 RGB 與 HSL 相互轉換所以我就不在此贅述. 我對過程中的第一個轉換 trichromatic coefficient 挺有興趣, 它可以把 RGB 轉換成 rgb 的三色係數定義如下:
X = R + G + B  且 L 亮度的定義是 L = X / 3
r = R / X,     g = G / X,     b = B / X   (X 不等於 0)     
且 1 = r + g + b


所以我們可以將 (R,G,B) 轉換成 (r, g, b, L) 係數, 系數上的意義是什麼呢? (r, g, b) 其實是色彩成分的比例關係且總合必定為 1 (記得, 人眼對色彩的感知是相對的, 重點就是比例), 而 L 則為 RGB 的平均成分, 或稱平均強度. (R,G,B) 與 (r, g, b, L) 可以相互轉換. 你是否想過為什麼 RGB 是個三維空間卻需要四個係數才能表達呢, 其實該轉換並非線性轉換, 仔細觀察你會發現 (r, g, b)  只有兩個維度喔. 以下是 rgb 的圖形, 不過我把三個軸換掉了, 紅軸與 OW 向量平行, 綠軸與 Pr Pg 向量平行, 藍軸與 Pb Qb 向量平行, 原因是因為這樣可以很容易觀察 rgb 是落在這個正三角形的平面上.




Figure 2


以下是個非常簡單的數學證明:
任意一點 P = (r, g, b) 且 r, g, b 介於 (0, 1) 之間可知 P 點落在第一卦限中
取向量 Q (1, 1, 1) 為參考向量與 P 點做內積可以得其投影量
 P × Q =  r  x 1 + g x 1 + b x 1 = r + g + b = 1 
可知 任意一點 P 在 Q 的投影量 (或稱 紅軸的分量) 都相同
得證所有點都落在 Pr Pg Pb 三角形平面上
而 HSL 轉換的意義就是: H 為 P W 跟 W Pr 的夾角, S 為 W P 與 W P' 的比例 且 P' 為 W P 向量 延伸到三角形邊線的一點, L 則為量度. 我們可以知道 H S 其實就是在描述 (r, g, b) 這個三角形平面.


看過 HSL 與 RGB 相互轉換公式可以知道該轉換需要多次計算三角函數與根號, 我跟國軍 (我同事, 不是政府的國軍喔, 雖然我還是國防役 :P) 開始思考, 如果我們希望調整 Hue, Saturation 與 Lightness 是否可以不要轉換到 HSL 就可以輕易辦到呢? 根據我們的研究結果是可以的喔, 最簡單的就是處理 Lightness, 其實就是對 (R, G, B) 三個值做 Scale, 其次呢就是調整 P 點 (r, g, b) 的位置就可以做到另外兩個 H 與 S 的調整, 做法是先將 (r, g, b) 基底換成上圖的三軸, 轉換的矩陣為 C 反矩陣為 Ci 可以輕易算出, 我們就可以對轉換後的向量根據 OW軸做 θ  Rotate 來達到 Hue 的調整, Rotate 矩陣為 Ro 矩陣. 對 Saturation 則對另外兩軸做 Scale 就可以達成, Scale 矩陣為 Sc 矩陣.


所以調整 Hue, Saturation. Lightness 可以用
(R,G,B) x (C x Sc(l, s) x Ro(h) x Ci)

Figure 3


這些矩陣運算來達成, X 係數的轉換是純量乘除所以可以提出抵銷, 所以整個計算幾乎等於是在 對 Figure 3 的 RGB 空間對 SW 這個軸做 Hue 的旋轉與 Lightness 的縮放, Saturation 的調整等於是在對另正交另兩軸做等量的縮放. 且 Sc 與 Ro 當中只有 θ 跟 scale 兩個變數很容易計算, 而且整結果可以合成一個矩陣就夠了, 在 GPU 中使用 HLSL 做矩陣向量計算的速度非常的快, 會比轉換 HSL 再轉回來快的多, 整體大概只需要 27個 instructions 就可以做到整個 HSL color adjust 的計算.  以 ATI Radeon HD 4890 為例大約有 11333MPixels/Sec, image 為 FullHD 下FPS 為 5465.

後來, 我的同事們發現這個算法有個重大的問題是, Hue 旋轉的過程中會讓 RGB 超出第一卦限, 並且旋轉後 Saturation 會跟著改變, 導致顏色出現問題, 我實驗的結果的確也是如此, 看來效果是不等價....... 數學不好啊有學到!! 我們可以用一下這張圖 Figure 4 來驗證, 該圖包含了 RGB 全部的色彩 (4096 x 4096), Figure 5 是調整 Hue 60 度下的結果, 我刻意把顏色標為黑色是超出第一卦限的部分. 如果將 Saturation 值調整為 0.5, 也就是降低一半的飽和度, 則不會再出現黑色, 因為所有的顏色都會落在正三角形中間的圓形中.


Figure 4



Figure 5

最有趣的是, 如果你拿 Figure 1 做 Hue 的調整, 那效果就向中間彩色圓盤在旋轉喔! 有趣吧. 感謝國軍跟他女友的指導才能找到更快的算法 :P 謝謝~~

11/27/2009

RGB 的物理意義是什麼?


最近因為工作的需求在學習影像處理, 因為我微積分, 線代與工數都不好 (不如說數學都不好), 所以挑了一個自認為比較容易理解 (其實不然) 也比較基本的色彩學來學習, 所以就挑了RGB 與 HSI (HSL) 轉換跟 Color adjust 這個問題.

首先我遇到的第一個問題是 "RGB 的物理意義到底是什麼" 為什麼可以用這樣表示光的色彩!
我從偉大的 Wiki 顏色 與 三原色光模式 找到了答案, 基本上 "顏色" 這種東西不是物理的特性而是生理特性, 光只有強度波長等基本的性質, 而顏色是生物眼睛中的光感細胞接受到光之後在由腦袋詮釋出來的性質. (當生物真好可以看到彩色的世界) 現在我們可以思考為什麼 色彩之間可以組合來產生不同的顏色呢?

首先我們知道 RGB 三色強度的組合在怎樣都不會混合出一道黃色波長的光線, 產生出來的永遠是三道光而每道光有各自的波長, 那為什麼 RGB 可以表現所有的顏色呢? 其實, 生物對顏色的詮釋是個有趣的東西, 不同顏色的感光細胞接受到光線波長的比例不同, 會有不同的反應, 雖然螢幕只有打出紅(255, 0, 0) 跟藍 (0, 0, 255), 但是眼睛卻會認為是粉紅色 (255, 0, 255) 因為眼睛中這兩種光感細胞會同時受到刺激, 而大腦會混合這刺激的結果認為是粉紅色, 而非真的接收到 粉紅色的波長. 而 RGB 就是這複合光的參數. 這也是為什麼 (x, x, x) 三種等比例的光會是灰階的, 因為所有光感細胞都接受到相同強度的刺激, 但是波長或是光的強度是不等的喔. 因為人類對不同顏色的敏感度是不同的. 我們可以想像鳥類或是其他動物 (包含外星人) 看到 RGB 混合產生出來的色彩應該非常畸形吧, 畢竟光的敏感強度是不同的. 其實很多種顏色都不是純色, 粉紅色 白色 灰色 等等都只能混合多種波長來產生, 純色只有彩虹的那幾種單一波長的光線而已.



由於人眼的特性, 所以我們就以 RGB 這樣的模型 擷取 紀錄 重現 顏色, 想想大部分的色彩處理方式諸如 底片, 數位相機 CCD, 投影機, CRT 顯示器, LCD 顯示器, 彩色印刷, 染布, 等都是用這種方式來處理呢, 下一篇來分享我理解的 HSI (HSL) Color space 吧!

11/25/2009

Interop between Host, embedded browser, JavaScript and Silverlight

最近剛好有機會開發一些 RIA 相關的工作, 希望整合 Web 相關的服務與應用到 AP Side, 隱約記得之前參加過的 Microsoft 的 Workshop 中有介紹如何將 WCF, WinForm 2.0 跟 Web 整合在一起的方法. 如何將一個內含網頁服務的 Browser control 內嵌在 Host  (WinForm) 上, 並且讓 Host 有機會跟網頁中的 JavaScript 溝通, 進而使用 AJAX 的方式來更新 Browser control 顯示的內容, 如此就可以整合 AP 與網頁服務使兩者可以互動. 當然這都歸功於微軟在 Browser control 上提供了 window.externalHtmlDocument.InvokeScript 的方式讓 JavaScript 可以跟 Host 溝通 (這算後門嗎? :P)

然而只有和 HTML 上的 JavaScript 是不夠的, 我們的需求是需要跟 host 在 HTML 上的 Silverlight 溝通, 畢竟使用 Silverlight 或是 Flash 來做為 RIA 的應用更能得心應手. 其實Silverlight 本來就容易跟 JavaScript 溝通 (不然 Silverlight 怎麼跟 Flash 比), Silverlight 也提供了多種方式來達到這點, 詳細可以參考 MSDN HTML Bridge: Interaction Between HTML and Managed Code  或是一本簡體書 "Silverlight 2完美征程" 的 "第13章 浏览器交互".



有此方式我們就可以透由 JavaScript 做為 "膠水" 讓我們的 WinForm 或是 WPF App 與內嵌的 Silverlight App 溝通, MSDN 就有一篇 Article XBAP Hosting Silverlight Sample 用來實現這樣的用法, 而整體的概念大致如上圖.

11/24/2009

Vision of Taiwan's Mountains


我們的觀光局拍攝的喔 拍的真棒

11/16/2009

The structure of Task Parallel Library in .NET 4.0

Parallel programming 是當紅的東西, 當然 .NET 4.0 也把相對應的功能納進去, 不同於 OpenMP或是 Go language 的方式, .NET 4.0 是以函式庫的方式來包裝對應的功能. 上回我介紹了Thread , SynchronizationContext 與 Dispatcher 的關係, 我們可以了解 SynchronizationContext 封裝了一個重要的特性: 就是用來描述某個工作如果在當下的 context 下需要以非同步的方式來運作, 應該用什麼方式來工作比較恰當. 預設的方式當然就是使用 thread pool 的方式來工作嘍, 但是 WPF 是有 thread affiliate 的, 因為 WPF 大部分的物件都繼承至 DispatcherObject, 而 DispatcherObject 是會 binding 於特定的 thread, 原因是確保 thread-safety, 其實跟 COM 的 Apartment 沒什麼不同. 而 TPL 也根據這樣的設計做了相對應的調整!!



基本上, .NET 4.0 中的 TaskScheduler 只有兩種, 一種是 ThreadPoolTaskScheduler 另一種是 SynchronizationContextTaskScheduler, 如 class 的名稱所示, ThreadPoolTaskScheduler 會將接收到的工作直接轉給 global thread pool 去執行, 而 SynchronizationContextTaskScheduler 則會交給 TaskScheduler 建立時所對應的SynchronizationContex, 而 SC 目前則可以有多種:

  1. SynchronizationContext: 交由 global thread pool 去執行
  2. DispatcherSynchronizationContext: 交由 dispatcher 去執行
  3. WindowsFormsSynchronizationContext: 交由 binding control 去執行
  4. ComPlusSynchronizationContext: 交由 COM+ 的 IServiceActivity 去執行
  5. AspNetSynchronizationContext: 沒研究 Orz...
這兩種 class 都是 internal sealed 所以我們只能從幾個方式建立/取得 TaskScheduler :
  1. TaskScheduler.Default : Gets the default TaskScheduler instance.
  2. TaskScheduler.Current : Gets the TaskScheduler associated with the currently executing task.
  3. TaskScheduler.FromCurrentSynchronizationContext : Creates a TaskScheduler associated with the current SynchronizationContext.
整個 Process 會有一個 default 的 ThreadPoolTaskScheduler, 這個 scheduler 會在 TaskScheduler 的 static constructor 中被建立出來, 而 TaskScheduler.Default 就是拿到這個 default 的 scheduler. 如果一個 Task 是巢狀的, 想得到當下執行 task 所對應的 scheduler 則可以使用 TaskScheduler.Current 如果當下沒有 Task 就會拿到 default 的 scheduler, 而 TaskScheduler.FromCurrentSynchronizationContext 怎會根據的當下的 SynchronizationContext 來建立一個 "新" 的 scheduler. 

很明顯的一個 Task 要給哪種 scheduler 來執行是跟 context 有關的, 如同上述所說, 如果一個 Task 是跟 WPF UI 更新有關 (或是 ASP, COM+ 相關的工作等等...), 那就必須丟到對應的 SynchronizationContextTaskScheduler 去執行, 否則只需要用 TaskScheduler.Current, 就足夠了. 下次我們來討論 Task 跟 TaskScheduler 的一些細節. :)


11/15/2009

2009 11 15 五專同學會









11/09/2009

WinDbg 相關資訊

Common WinDbg Commands (Thematically Grouped)
.Net Debugging? Don’t give me none of your VS

2009 11 08 野柳一日遊





11/06/2009

Thread, SynchronizationContext and Dispatcher for .NET




Dispatcher 的 Constructor 是 private 的, 所以我們只幾種辦法得到 Dispatcher, 那就是調用 Dispatcher.CurrentDispatcher 這個 static property 或是 Dispatcher.FromThread 這個 static function, MSDN 有描述這個 property 的語意 "Gets the Dispatcher for the thread currently executing and creates a new Dispatcher if one is not already associated with the thread." 我們來看看相關的實作, 該 property 會調用 Dispatcher.FromThread 去找尋當下這個 thread 對應的 dispatcher.

public static Dispatcher CurrentDispatcher
{
    get
    {
        Dispatcher dispatcher = FromThread(Thread.CurrentThread);
        if (dispatcher == null) // 找不到就建立一個新的
        {
            dispatcher = new Dispatcher(); 
            // Constructor 會把自己加到 Dispatcher._dispatchers 中
        }
        return dispatcher;
    }
}
而 Dispatcher.FromThread 則會去Dispatcher._dispatchers 這個 List 找尋相對應的 dispatcher, 所以, dispatcher 跟 Thread 是有對應關係的.
public static Dispatcher FromThread(Thread thread)
{
    lock (_globalLock)
    {
        Dispatcher target = null;
        if (thread != null)
        {
            target = _possibleDispatcher.Target as Dispatcher; 
            // 檢查前一次找到的是否符合
            if ((target == null) || (target.Thread != thread))
            {
                target = null;
                for (int i = 0; i < _dispatchers.Count; i++)
                {
                    Dispatcher dispatcher2 = 
                        _dispatchers[i].Target as Dispatcher;
                    if (dispatcher2 != null)
                    {
                        if (dispatcher2.Thread == thread)
                        {
                            target = dispatcher2;
                        }
                    }
                    else
                    {
                        _dispatchers.RemoveAt(i); // 已經被回收掉了就從 list 中移除
                        i--;
                    }
                }
                if (target != null)
                {
                    _possibleDispatcher.Target = target; // 快取前一次找到的
                }
            }
        }
        return target;
    }
} 


接下來我們來看看 Dispatcher 如何運作, 還有他跟SynchronizationContext 之間的關係, 我們可以從 Dispatcher 的 constructor  知道 Dispatcher  本身建立了一個 MessageOnlyHwndWrapper 物件, 而該 Class 繼承至 HwndWrapper, HwndWrapper 的 constructor 會負責使用 Win32 API 來建立一個看不見的 Window 並且註冊自有的 WndProc , 而 Dispatcher 會利用 hook 的方式來被 message pump 所推動. 






[SecurityCritical, SecurityTreatAsSafe]
private Dispatcher()
{
    this._instanceLock = new object();
    this._timers = new List();
    this._queue = new PriorityQueue();
    _tlsDispatcher = this;
    this._dispatcherThread = Thread.CurrentThread;
    lock (_globalLock)
    {
        _dispatchers.Add(new WeakReference(this));
    }
    this._unhandledExceptionEventArgs = 
          new DispatcherUnhandledExceptionEventArgs(this);
    this._exceptionFilterEventArgs = 
          new DispatcherUnhandledExceptionFilterEventArgs(this);
    MessageOnlyHwndWrapper wrapper = new MessageOnlyHwndWrapper();
    this._window = new SecurityCriticalData(wrapper);
    this._hook = new HwndWrapperHook(this.WndProcHook);
    this._window.Value.AddHook(this._hook);
}

所以, 我們知道 Dispatcher 所依附的 thread 是需要 message pump 的, 也就是該 thread 事先必須調用過 Dispatcher.Run 而 Run 回去調用 Dispatcher.PushFrameImpl 來轉動 message pump, 最簡單的例子 Application.Run 內部就是間接調用 Dispatcher.PushFrameImpl 來實作 message pump 給 UI 使用. 而執行 Dispatcher.PushFrameImpl 的過程會去 overwrite 該 thread 的 SynchronizationContext 將原本的 context 換成 DispatcherSynchronizationContext.

private void PushFrameImpl(DispatcherFrame frame)
{
    SynchronizationContext syncContext = null;
    SynchronizationContext context2 = null;
    MSG msg = new MSG();
    this._frameDepth++;
    try
    {
        syncContext = SynchronizationContext.Current;
        context2 = new DispatcherSynchronizationContext(this);
        SynchronizationContext.SetSynchronizationContext(context2);
        try
        {
            while (frame.Continue)
            {
                if (!this.GetMessage(ref msg, IntPtr.Zero, 0, 0))
                {
                    break;
                }
                this.TranslateAndDispatchMessage(ref msg);
            }
            if ((this._frameDepth == 1) && this._hasShutdownStarted)
            {
                this.ShutdownImpl();
            }
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(syncContext);
        }
    }
    finally
    {
        this._frameDepth--;
        if (this._frameDepth == 0)
        {
            this._exitAllFrames = false;
        }
    }
}
如果在一個沒有 message pump 的 thread 中取得 Dispatcher 會怎樣呢? Dispatcher.CurrentDispatcher 會因為找不到對應的 dspatcher 而 new 一個新的出來, 當然, 對這個 new 出來的 dspatcher 使用 DispatcherPriority.Send priority 來調用 Dispatcher.Invoke 是會有用的, 因為 Dispatcher.InvokeImpl 會直接去執行 invoke 指定的 delegate, 特別有趣的是, 執行過程中Dispatcher.InvokeImpl 會去維護 SynchronizationContext, 所以他會去設定當下的 context 並且在執行完後再設定回來. 而非同步的 case 中 DispatcherOperation.InvokeImpl 也會去維護 context. 




[SecurityTreatAsSafe, FriendAccessAllowed, SecurityCritical]
internal object InvokeImpl(DispatcherPriority priority, TimeSpan timeout, 
  Delegate method, object args, bool isSingleParameter)
{
    ValidatePriority(priority, "priority");
    if (priority == DispatcherPriority.Inactive)
    {
        throw new ArgumentException(SR.Get("InvalidPriority"), "priority");
    }
    if (method == null)
    {
        throw new ArgumentNullException("method");
    }
    if ((priority == DispatcherPriority.Send) && this.CheckAccess())
    {
        SynchronizationContext current = SynchronizationContext.Current;
        try
        {
            SynchronizationContext.SetSynchronizationContext(
                 new DispatcherSynchronizationContext(this));
            return this.WrappedInvoke(method, args, isSingleParameter);
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(current);
        }
    }
    DispatcherOperation operation = this.BeginInvokeImpl(priority, 
             method, args, isSingleParameter);
    if (operation != null)
    {
        operation.Wait(timeout);
        if (operation.Status == DispatcherOperationStatus.Completed)
        {
            return operation.Result;
        }
        if (operation.Status == DispatcherOperationStatus.Aborted)
        {
            return null;
        }
        operation.Abort();
    }
    return null;
}

但其他的 priority 就沒機會執行了, 因為其它的 priority 會使用 Dispatcher.BeginInvokeImpl 來達到非同步的執行, 而骨子裡還是使用 PostMessage 這個 Win32 API 去產生 message 給 message queue, 但是這個 queue 將不會有人去調用 GetMessage 所以所有工作都不會被直行.

11/04/2009

2009 11 01 磺山溫泉火鍋行

Coooool 吧~~ 火山泥護膚 感謝麻豆 書緯
 


11/02/2009

2009 04 05 磺山溫泉

補一下之前缺的錄影~~





寄件者 自然


寄件者 自然


檢視較大的地圖