一、導讀
在日系插畫領域,賽璐璐風格不會過多地塑造出復雜的光影和小調子。以線條造型為主,清晰的光影表達,以及明快的配色是這個風格的畫師普遍追求的目標。基於這個前提,賽璐璐風格就是做自動線稿提取算法的很好切入點。能否詳盡地提取出原始圖像線稿中所有的線條,並且結果能讓人在視覺基礎上感到滿意,應該是這個算法應該追求的主要目標。與此同時,這樣的線稿提取算法也能成為未來自動草稿細化的基礎。為了更好地實現機器輔助設計,我們應該深刻地從圖像處理的立場上研究這種風格。
二、基本算法構架的解釋以及測試集的選擇
算法主要分為三個部分:
1.首先對圖像進行降噪,並且平滑整個圖像,抹去圖像中的噪音和一些小調子,以更好地進行邊緣提取.其中,我選擇的降噪算法是經典的ROF降噪模型:其優點在於經歷多次迭代后仍然可以保持圖像的邊緣,並且同時可以刪去噪音和小調子。相關知識請參考如下網站:
https://blog.csdn.net/cyh706510441/article/details/45194223
2.使用Canny邊緣提取算法,提取出圖像中的主要細節。
有關Canny邊緣提取算法的前置知識請參考:
https://blog.csdn.net/Jamence/article/details/84645554
3.結果的后續美觀性上的處理。
測試集的選擇為:1.畫師Anmi;2.畫師Rei;3.畫師Kirarin;4.動畫《JOJO的奇妙冒險~黃金之風》
步驟1對於一些有小調子並且存在噪音的圖,例如插畫師rei以及Anmi的插畫,就很有必要進行這一步,其可以防止Canny算法過渡地提取邊緣。另外值得注意的是:不論是JPG格式還是PNG格式的圖像,其在MATLAB中都是以紅綠藍三個通道(也就是一個三維數組),儲存了三個不同的灰度值,最后疊加形成的彩色圖。我們提取邊緣的時候,應該對三個通道都進行提取,最后疊加成圖。在圖像降噪的環節中,我們的ROF降噪模型的迭代步數都是100,下面是原圖以及實驗結果:
可以看見,整體的形體被提取了出來,而且也沒有損失細節。后續算法的改進會更加注重線條的優化,以及噪點的刪去。同時,我們可以看到對紅綠藍三層通道進行處理后的的線稿提取結果,還可以加強外輪廓(這在線稿細化中是非常重要的)。雖然仍然會有小調子和噪音的出現。但不論如何,Canny邊緣提取算法和ROF降噪模型的完美配合,顯示出了廣闊的應用前景。
對於步驟二,為何使用Canny邊緣提取算法,是因為例如傳統的Sobel梯度邊緣提取算法,提取出的是圖像梯度值較高的“強邊緣”,但會忽視圖像中的弱邊緣。下圖中咦Kirarin老師的圖為例,左起至右依次是原圖—Sobel邊緣提取算法—Canny算子的結果圖:
對於線稿提取,我們自然是不想遺漏任何的線條信息的。但Canny算子此時也會顯出它的弊端:我們也不是毫無保留地提取所有線條。如何更好地改進Canny算法,使得其可以提取出賽璐璐圖片的“主要信息”而忽略其“次要信息”呢?首先我們需要定義何種線條信息在圖片中是“主要的”,何種是“次要的”。緊接着我們可以考慮設置一個閾值——即當某一像素點的梯度大於一定取值的時候,這個邊緣信息才被保留下來,否則便舍棄。或許在未來,這一領域會和神經網絡緊密聯系起來,而且值得一提的是,日本的很多學者也已經開始用神經網絡解決此類問題了。
對於步驟三,對提取結果的進一步優化,使其符合我們的視覺審美,這一步我目前仍然在優化和進行中:目前我的思路如下:一個好的線稿無非要滿足一下兩個特整:1.外輪廓粗而內輪廓細(例如上圖的向日葵的花瓣外輪廓很粗很實在,而內輪廓很細或者很淡。)2.對一些閉塞進行加深(通俗一點講就是兩條線相交的位置,臨近交點的線條都要加深) 而除此之外,便是要靠藝術家自身的設計功力去塑造漂亮的線稿了。請記住我們的目的永遠不是用機器取代人來設計,而是使用機器解決藝術創作中的重復性工作,解放創造力。
三、代碼的公布以及使用范例
首先是ROF降噪算法的代碼(注意是函數,本體要調用這個代碼)
function J = tv(I,iter,dt,ep,lam,I0,C) %% Total Variation denoising. %% Example: J=tv(I,iter,dt,ep,lam,I0) %% Input: I - 樣本圖像 (二進制浮點數 array 灰度圖 level 1-256), % iter - 迭代次數 % dt - 時間步長 [0.2], % ep - epsilon (正規化因子) [1], % lam - 松弛因子 [0], % I0 - 無噪聲圖像 [I0=I] % ([]內為默認值) % Output: 像素矩陣 %% 判斷是否輸入相關參數 if ~exist('ep') ep=1; end if ~exist('dt') dt=ep/5; % dt需滿足CFL條件 end if ~exist('lam') lam=0; end if ~exist('I0') I0=I; end if ~exist('C') C=0; end [ny,nx]=size(I); ep2=ep^2; %% 循環迭代 for i=1:iter, % 構建數值微分 I_x = (I(:,[2:nx nx])-I(:,[1 1:nx-1]))/2; I_y = (I([2:ny ny],:)-I([1 1:ny-1],:))/2; I_xx = I(:,[2:nx nx])+I(:,[1 1:nx-1])-2*I; I_yy = I([2:ny ny],:)+I([1 1:ny-1],:)-2*I; Dp = I([2:ny ny],[2:nx nx])+I([1 1:ny-1],[1 1:nx-1]); Dm = I([1 1:ny-1],[2:nx nx])+I([2:ny ny],[1 1:nx-1]); I_xy = (Dp-Dm)/4; % 計算遞推關系式 Num = I_xx.*(ep2+I_y.^2)-2*I_x.*I_y.*I_xy+I_yy.*(ep2+I_x.^2); Den = (ep2+I_x.^2+I_y.^2).^(3/2); I_t = Num./Den + lam.*(I0-I+C); I=I+dt*I_t; %% 更新圖像 J= I; end
然后是Canny邊緣提取算法:
function m = canny1step( src, lowTh) %canny函數第一步,求去x,y方向的偏導,模板如下: % Gx % 1 -1 % 1 -1 % Gy % -1 -1 % 1 1 %------------------------------------ % 輸入: % src:圖像,如果不是灰度圖轉成灰度圖 % lowTh:低閾值 % 輸出: % m: 兩個偏導的平方差,反映了邊緣的強度 % theta:反映了邊緣的方向 % sector:將方向分為3個區域,具體如下 % 2 1 0 % 3 X 3 % 0 1 2 % canny1:非極大值 % canny2:雙閾值抑制 % bin : 二值化 %--------------------------------------- [Ay Ax dim ] = size(src); %轉換為灰度圖 if dim>1 src = rgb2gray(src); end src = double(src); m = zeros(Ay, Ax); theta = zeros(Ay, Ax); sector = zeros(Ay, Ax); canny1 = zeros(Ay, Ax);%非極大值抑制 canny2 = zeros(Ay, Ax);%雙閾值檢測和連接 bin = zeros(Ay, Ax); for y = 1:(Ay-1) for x = 1:(Ax-1) gx = src(y, x) + src(y+1, x) - src(y, x+1) - src(y+1, x+1); gy = -src(y, x) + src(y+1, x) - src(y, x+1) + src(y+1, x+1); m(y,x) = (gx^2+gy^2)^0.5 ; %-------------------------------- theta(y,x) = atand(gx/gy) ; tem = theta(y,x); %-------------------------------- if (tem<67.5)&&(tem>22.5) sector(y,x) = 0; elseif (tem<22.5)&&(tem>-22.5) sector(y,x) = 3; elseif (tem<-22.5)&&(tem>-67.5) sector(y,x) = 2; else sector(y,x) = 1; end %-------------------------------- end end %------------------------- %非極大值抑制 %------> x % 2 1 0 % 3 X 3 %y 0 1 2 for y = 2:(Ay-1) for x = 2:(Ax-1) if 0 == sector(y,x) %右上 - 左下 if ( m(y,x)>m(y-1,x+1) )&&( m(y,x)>m(y+1,x-1) ) canny1(y,x) = m(y,x); else canny1(y,x) = 0; end elseif 1 == sector(y,x) %豎直方向 if ( m(y,x)>m(y-1,x) )&&( m(y,x)>m(y+1,x) ) canny1(y,x) = m(y,x); else canny1(y,x) = 0; end elseif 2 == sector(y,x) %左上 - 右下 if ( m(y,x)>m(y-1,x-1) )&&( m(y,x)>m(y+1,x+1) ) canny1(y,x) = m(y,x); else canny1(y,x) = 0; end elseif 3 == sector(y,x) %橫方向 if ( m(y,x)>m(y,x+1) )&&( m(y,x)>m(y,x-1) ) canny1(y,x) = m(y,x); else canny1(y,x) = 0; end end end%end for x end%end for y %--------------------------------- %雙閾值檢測 ratio = 2; for y = 2:(Ay-1) for x = 2:(Ax-1) if canny1(y,x)<lowTh %低閾值處理 canny2(y,x) = 0; bin(y,x) = 0; continue; elseif canny1(y,x)>ratio*lowTh %高閾值處理 canny2(y,x) = canny1(y,x); bin(y,x) = 1; continue; else %介於之間的看其8領域有沒有高於高閾值的,有則可以為邊緣 tem =[canny1(y-1,x-1), canny1(y-1,x), canny1(y-1,x+1); canny1(y,x-1), canny1(y,x), canny1(y,x+1); canny1(y+1,x-1), canny1(y+1,x), canny1(y+1,x+1)]; temMax = max(tem); if temMax(1) > ratio*lowTh canny2(y,x) = temMax(1); bin(y,x) = 1; continue; else canny2(y,x) = 0; bin(y,x) = 0; continue; end end end%end for x end%end for y end%end of function
最后是我們的主體代碼
close all; clear all; clc; mI = double( imread( 'C:/Matlab/IMG_3228.png' ) );%此處輸入你的圖像 sigma1 = 15; %高斯正態分布標准差 gausFilter = fspecial('gaussian',[3 3],sigma1); %高斯濾波器 %設置高斯濾波是提取邊緣后的進一步去除噪音點 mI1 = mI(:,:,1); mI2 = mI(:,:,1); mI3 = mI(:,:,1);%三個圖層分別對應紅綠藍三個通道的灰度圖 % mI1 = TVSample(mI( :, :, 1 ),100); % mI2 = TVSample(mI( :, :, 2 ),100); % mI3 = TVSample(mI( :, :, 3 ),100); [ny,nx]=size(mI1); A = canny(mI1,graythresh(mI1)); %將圖像進行TV迭代, TV函數的格式為:TV(圖像,迭代次數,(后續參數)) % A = edge(mI1);%為了讓你方便和其他邊緣提取算法比較,這里直接用MAT內置的edge來做,使用的時候去除 % 就行 A = abs(A);%取像素絕對值 A = imcomplement(A);%二值化 A = mat2gray(A);%轉為標准灰度(0-255) A = imfilter(A,gausFilter,'replicate'); %高斯濾波除一下結果圖的噪音 % A = imbinarize(A,'global');%像素取反,更清楚地看見邊緣 B = canny(mI2,graythresh(mI1)); %將圖像進行TV迭代, TV函數的格式為:TV(圖像,迭代次數,(后續參數)) % B = edge(mI2); B = abs(B); B = imcomplement(B); B = mat2gray(B); B = imfilter(B,gausFilter,'replicate'); % B = imbinarize(B,'global'); C = canny(mI3,graythresh(mI1)); %將圖像進行TV迭代, TV函數的格式為:TV(圖像,迭代次數,(后續參數)) % C = edge(mI3); C = abs(C); C = imcomplement(C); C = mat2gray(C); C = imfilter(C,gausFilter,'replicate'); % C = imbinarize(C,'global'); D = A+B+C; % D = 1 * D.^5 % tresh = min(min(D)) % D(find(tresh+1<D<tresh+3))=D(find(tresh+1<D<tresh+3))-1; % [ny,nx]=size(A); % E = A % for i = 3:ny-2 % for j = 3:nx-2 % if A(i,j) == 0 % E(i+1,j) = 0; E(i-1,j) = 0;E(i,j+1) = 0;E(i,j-1) = 0; % E(i+1,j+1) = 0;E(i+1,j-1) = 0;E(i-1,j+1) = 0;E(i-1,j-1) = 0; % % end % end % end %這里的一段代碼是為后續優化設計的,還沒完善。 figure(1); imagesc( A ); colormap( gray ); axis off;%輸出提取后1通道圖像 figure(2); imagesc( B ); colormap( gray ); axis off;%輸出提取后的2通道圖像 figure(3); imagesc( C ); colormap( gray ); axis off;%輸出提取后的3通道圖像 figure(4); imagesc( D ); colormap( gray ); axis off; %輸出提取后的復合圖像
四、寫在最后
我曾經一直在迷茫:其實我數學並不好,學什么都要比別人吃力很多,我喜歡畫畫,我為什么不能選自己喜歡的東西。但不管如何抱怨,命運還是把我帶到了國內數學系很牛的一個大學。我從自暴自棄到現在,思考了很多東西:大概這一切的確冥冥之中有天意,我的很多畫家朋友用他們自己的努力,去推動着商業美術的發展,那我也能用我自己的方式去實現一些東西,不管會繞多遠的路,遇到多少挫折也好,就像老謝的lesson3教的:最快的捷徑就是繞遠路。
我很喜歡布加拉提說的:“我不后悔,雖然是身處於這樣的世界,可我還是想走在“自己所堅信的道路”上,雖然現在我只能逃走,但只要找到弱點,就一定能打敗老板,我一定會找出他的弱點!”即使滾石早就預言着他不論如何都會死,但他用他的覺悟詮釋了什么叫做“知其不可而為之”。
ARRIVEDERC!