2014-08-13

[OF]黑白吧!長頸鹿!(上)

這篇的由來, 是源自於工作上的需求
這需求是希望做到黑白漫畫的效果(歡迎點開我公司的官網)
在google大神以及高手同事的協助下, 產生了兩套PhotoShop流程來做到所謂的黑白漫畫
那是一個喊出「Photoshop行!我程式就行!」的純真時光
我試著一步一步的將PS步驟轉為程式
其實是個滿有趣的過程

================分隔線=============

其實以英文的keyword來說, 當時的需求比較像是sketch(素描)
以comic這個關鍵字下去, 比較接近美式漫畫的風格
比起comic來說, photoshop的sketch effect其實大多教學都滿簡單的
這篇先來介紹這個比較簡單, 最後也被採用的作法
先來看看結果...

沒看錯, 還是長頸鹿!!


環境簡介:
Windows 7 + Visual Studio 2010 + OF 0.7.4(基本上到0.8.1測試過也ok)
Addons:
ofxOpenCV(不過使用上主要還是用源生的OpenCV語法)

單純就這個功能來說, 其實純粹用OpenCV就夠用了
接下來介紹一下作法[1]

步驟:

1.將影像A轉為灰階(Gray Level)得到G
2.將G反轉(Inverse)得到I
3.對I影像做高斯模糊(Gaussian Blur)得到S
4.以S為底, 對G影像做顏色亮化混合(Color Dodge Blending)得到影像M
5.對影像M做閾值處理(Threshold)即得到結果

是不是很簡單啊, 就像麥太太的料理一樣呦[2]
整個演算法的目的其實就是把影像中的"邊緣"找出來
關鍵就在於"Color Dodge Blending""Gaussian Blur"
首先是Color Dodge Blending, 當初其實不太清楚這個要怎麼實作
在參拜google大神數次後, 看到了一個佛心來的程式碼[3]:
#define ChannelBlend_ColorDodge(A,B) ((uint8)((B == 255) ? B:min(255, ((A << 8 ) / (255 - B)))))
看到"<< 8"會怕的朋友別緊張, 把他看成*256, 所以就是
ChannelBlend_ColorDodge(A,B) = A * 256 / (255 - B)

簡單一句話就是, 在同樣亮度A下, B越亮混合完結果就會越亮.
眼尖的朋友一定會發現, 在這個演算法的過程中B就是A的inverse, 也就是(255 - A)
所以代入後的結果就變成
ChannelBlend_ColorDodge(A,B) = A * 256/A

到此, 第二個關鍵Gaussian Blur就要發揮作用了
不懂Gaussian Blur是啥沒關係, 只要知道他會將原來的數值跟周圍根據某個權重去平均掉就好
剛剛的公式正確版應該如下:
ChannelBlend_ColorDodge(A,B) = A * 256/Gaussina(A)

換句話說, 只要Gaussina(A) <= A, 就會得到白色(255)
又因為在暗→亮的地方, 做完Gaussian Blur後是會變亮的, 所以邊緣的部份就會被畫出來

讓我們用一張測試的圖看看結果, 應該就更好懂了吧
右邊是原圖, 左邊是做完Color Dodge Blending的結果
可以看到由暗到亮的邊緣都被畫出來了

以下是Source Code:
1:  void testApp::ComicFilterVerCater()  
2:  {  
3:       //RGB to Gray  
4:       cv::Mat oMatGrayImg_;  
5:       cv::cvtColor(this->m_oImage, oMatGrayImg_, CV_RGB2GRAY);  
6:       //Inverse  
7:       cv::Mat MatImageInverse_;  
8:       cv::bitwise_not(oMatGrayImg_, MatImageInverse_);  
9:       //Smooth  
10:       int iTmpSize_ = this->m_ucKernalSize * 2 + 1;  
11:       cv::Mat MatSmoothImageInverse_;  
12:       cv::GaussianBlur(MatImageInverse_, MatSmoothImageInverse_, cv::Size(iTmpSize_, iTmpSize_), this->m_fSigma);  
13:       //Color dodge  
14:       cv::Mat MatEdge_ = oMatGrayImg_.clone();  
15:       for(int _idxY = 0; _idxY < MatSmoothImageInverse_.rows; ++_idxY)  
16:       {  
17:            for(int _idxX = 0; _idxX < MatSmoothImageInverse_.cols; ++_idxX)  
18:            {  
19:                 MatEdge_.at<uint8>(_idxY, _idxX) = ChannelBlend_ColorDodge(MatEdge_.at<uint8>(_idxY, _idxX), MatSmoothImageInverse_.at<uint8>(_idxY, _idxX));  
20:            }  
21:       }  
22:       //Threshold  
23:       cv::Mat oMatThreshold_;  
24:       cv::threshold(MatEdge_, oMatThreshold_, this->m_fThreshold, 255, cv::THRESH_BINARY);  
25:       this->m_oResult = MatEdge_;       
26:  }  


下集請到這裡:

=好寶寶Reference=
[1]How to change a photo into a pencil line drawing in Photoshop
[2]麥兜的故事─麥太太的世界
[3]Stack overflow How does photoshop blend two images together?

================分隔線=============
目前對於Blogger的使用還處於菜到不行的階段
排版什麼的還沒辦法處理到讓大家看了舒服, 實在很抱歉啊Orz

沒有留言:

張貼留言