圖像鉛筆畫算法

關於鉛筆畫算法


圖像鉛筆畫算法,屬於一直是非真實感繪製領域(Non-Photorealistic Rendering,NPR)中很熱門的一個課題,但是計算機也很難模擬出像人一樣真實的畫質,這也顯得CG師們的重要性。本文是基於香港中文大學Cewu Lu等人所做的工作《Combining Sketch and Tone for Pencil Drawing Production》,描述計算機生成鉛筆畫的藝術。本人才疏學淺,描述如有錯誤,還望指點。

算法概述

作者基於對日常生活中的人手繪鉛筆畫的觀察,可以分爲兩個步驟,第一步勾勒出物體的大致輪廓;第二步是對物體進行色調渲染,即用鉛筆反覆輕輕的劃。

手繪過程

也就是說鉛筆畫是由結構(Structure)和色調(Tone)組成。
鉛筆畫組成

算法步驟如下

  1. 產生筆畫結構(Stroke Structure Generation )
  2. 色調渲染(Tone Rendering)
  3. 筆畫結構圖與色調渲染圖融合得到最終圖像

框架圖

框架圖

詳細步驟

筆畫結構的產生

通過對圖像求其梯度得到,得到輪廓。

G=(( x I) 2 +( y I) 2 ) 12  (1) 

然後檢測輪廓中每一點的方向,公式(2),沿着該方向進行擴展(3)。這裏,作者是對得到的梯度圖G進行8個方向的卷積,響應最大的卷積的方向爲視爲該點的方向。
C i (p)={G(p)0 ifargmax i {ψ i G}(p)=iotherwise  (2) 

得到每個點的方向後,再對梯度圖進行8個方向的卷積,將8個方向的響應疊加在一起,可得到圖像的筆畫結構
S= i=1 8 ψ i C i (3) 

色調渲染

人手繪的鉛筆畫的直方圖往往如下


作者解釋道,這是因爲鉛筆畫有高光(bright layer),中間調(mid-tone),陰影(dark layer)三部分組成,如下圖所示。

分別用拉布拉斯分佈,均勻分佈,高斯分佈函數來模擬。其表達式如下
P 1 (v)=⎧ ⎩ ⎨ 1σ b  e 1vσ b   0 ifv1otherwise  4 

P 2 (v)=⎧ ⎩ ⎨ 1u b u a  0 ifu a vu b otherwise  5 

P 3 (v)=12πσ d  − − − −    e (vμ d ) 2 2σ 2 d   6 

然後再對這3個函數調節不同的權重,用最大似然估計權重的值。
P(v)=1Z [ω 1 P 1 (v)+ω 2 P 2 (v)+ω 3 P 3 (v)]7 

最後一步就是紋理渲染,即模擬人反覆用鉛筆描的過程。

這一步看起來比較抽象,但也簡單,就是一個gamma矯正的過程,gamma值越高圖像越黑,如圖所示。
gamma矯正的示例代碼:

I = imread('texture.jpg');
I = rgb2gray(I);
I02 = ((double(I)/255).^0.2);
I04 = ((double(I)/255).^0.4);
I06 = ((double(I)/255).^0.6);
I08 = ((double(I)/255).^0.8);
I12 = ((double(I)/255).^1.2);
I14 = ((double(I)/255).^1.4);
I16 = ((double(I)/255).^1.6);
I18 = ((double(I)/255).^1.8);
I20 = ((double(I)/255).^2.0);


這裏未知的就是區域渲染次數β  ,或者說深度。

這可以由一個最優化問題解決。
β  =argmin β {βlog(P)log(J) 2 2 +λβ 2 2 }8 

我們可令β 2 2 =D x β 2 2 +∥ ∥ D y β∥ ∥  2 2   ,其中D x  D y   代表水平和豎直方向上的梯度算子,那麼該最優化β  滿足下列條件
(βlog(P)log(J)) T log(P)+λ(β T D x  T D x +β T D y  T D y )=0 

進一步,兩邊取轉置
(βlog(P)log(P)log(J)log(P))+λ(D x  T D x +D y  T D y )β=0 

由此可解的
[λ(D x  T D x +D y  T D y )+log(P) T log(P)]β=log(P) T log(J) 

轉換形式
[diag(log(P)log(P))+λ(D x  T D x +D y  T D y )]β=log(J)log(P) 

由於[diag(log(P)log(P))+λ(D x  T D x +D y  T D y )]  對稱正定,該大規模方程組,可以使用共軛梯度法(CG)求解β 

  • 紋理圖 P
  • 色調圖 J

  • β  圖層

  • 紋理渲染圖層


    至此,該算法講解已算完結了。更多內容可以看原始論文。

代碼

  • 產生筆畫結構代碼
function S = GenStroke(im, ks, dirNum)
% ==============================================
%   Compute the Stroke Structure 'S'
%   S = GenStroke(im, ks, dirNum) caculates the direction angle of pixels in  
%  "im", with kernel size "ks", and number of directions "dirNum".
%  
%   Paras:
%   @im        : input image ranging value from 0 to 1.
%   @ks        : kernel size.
%   @dirNum    : number of directions.
%
%           
%   Example
%   ==========
%   im = im2double(imread('npar12_pencil2.bmp'));
%   Iruv = rgb2ycbcr(im);
%   S= GenStroke(Iruv(:,:,1),ks,dirNum);
%   figure, imshow(S)
%
%
%   ==========
%   The code is created based on the method described in
%   "Combining Sketch and Tone for Pencil Drawing Production" Cewu Lu, Li Xu, Jiaya Jia 
%   International Symposium on Non-Photorealistic Animation and Rendering (NPAR 2012), June, 2012

%  image gradients
    [H, W, sc] = size(im);
    if sc == 3
        im = rgb2gray(im);
    end
    imX = [abs(im(:,1:(end-1)) - im(:,2:end)),zeros(H,1)];
    imY = [abs(im(1:(end-1),:) - im(2:end,:));zeros(1,W)];  
    imEdge = imX + imY;

% convolution kernel with horizontal direction 
    kerRef = zeros(ks*2+1);
    kerRef(ks+1,:) = 1;

% classification 
    response = zeros(H,W,dirNum);
    for n = 1 : dirNum
        ker = imrotate(kerRef, (n-1)*180/dirNum, 'bilinear', 'crop');
        response(:,:,n) = conv2(imEdge, ker, 'same');
    end

    [~ , index] = max(response,[], 3); 

 % create the sketch
    C = zeros(H, W, dirNum);
    for n=1:dirNum
        C(:,:,n) = imEdge .* (index == n);
    end

    Spn = zeros(H, W, dirNum);
    for n=1:dirNum
        ker = imrotate(kerRef, (n-1)*180/dirNum, 'bilinear', 'crop');
        Spn(:,:,n) = conv2(C(:,:,n), ker, 'same');
    end

    Sp = sum(Spn, 3);
    Sp = (Sp - min(Sp(:))) / (max(Sp(:)) - min(Sp(:)));
    S = 1 - Sp;
  • 紋理傳輸代碼
 function T = texturetransfer(P,J)
 % ==============================================
%   Texture Rendering
%   T = texturetransfer(P,J) pencil texture transfer from P to 
J 
%  
%   Paras:
%   @P        : tonal texture value from 0 to 1.
%   @J        : pencil tone image value for 0 to 1. 
%
%           
%   Example
%   ==========
%   P = im2double(rgb2gray(imread('TonalTexture.jpg')));
%   J = im2double(rgb2gray(imread('PencilTone.jpg'))); 
%   T = texturetransfer(P,J);
%   figure,imshow(T); title('T');


lambda =0.2;

[r,c,~] = size(P);
k = r*c;

% Transform to log domain
logP = log(P + eps);
logP = logP(:);
logJ = log(J + eps);
logJ = logJ(:);

dx = ones(k,1);
dx = dx(:);

dy = ones(k,1);
dy = dy(:);

B(:,1) = dx;
B(:,2) = dy;
d = [-r,-1];
A = spdiags(B,d,k,k);

e = padarray(dx, r,'post'); e = e(r+1:end);
w = padarray(dx, r,'pre');  w = w(1:end-r);
s = padarray(dy, 1,'post'); s = s(2:end);
n = padarray(dy, 1,'pre');  n = n(1:end-1);

D = -(e+w+s+n);
A = lambda*(A + A' + spdiags(D,0,k,k)) + spdiags(logP.*logP,0,k,k);
b = logJ.*logP;

%Solve 
beta = pcg(A,b,1e-6,60);
beta = reshape(beta,r,c);
beta = (beta - min(beta(:))) / (max(beta(:)) - min(beta(:))) * 4;
figure,imshow(beta./max(beta(:)));
T = (P).^beta;

注: 這裏我給出的只是第一步產生筆畫結構的代碼,和紋理渲染部分代碼,只有色調映射部分未給。若對此算法感興趣,可以參考作者論文。另外作者主頁上有部分代碼,可以下載

效果


圖片來源互聯網,侵權則刪。

致謝

在此感謝採石工(QQ544617183)指正公式求解過程中幾處不正確。
附上採石工的推導證明

軟件下載地址(可執行程序exe)

感興趣的朋友可以點此下載,嘗試效果。

更多閱讀

http://www.cnblogs.com/Imageshop/p/4285566.html
http://www.cse.cuhk.edu.hk/~leojia/projects/pencilsketch/pencil_drawing.htm

轉載請保留以下信息

作者 日期 聯繫方式
風吹夏天 2015年5月2日 wincoder#qq.com
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章