搜尋
  • Ting Chun Liu

Processing 蒙地卡羅演算法做文字動畫

標題很聳動,但其實也就是用了蒙地卡羅方法來計算文字在空間中的佔比。寫一點技術文章,這篇要先感謝家豪冠廷家陞可以一起快樂寫程式,還有淳俐剛好在柏林表演找我湊一腳很隨便的live-coding視覺,才有辦法把這東西弄完。這份Code有點類似高中在玩BBS的時候有些windows的軟體可以做Ascii art,把圖片或影片轉換成文字效果,一直在想有什麼演算法可以去實踐它……,後來用蒙地卡羅演算法(Monte Carlo algorithm)硬幹出來惹。恥恥的,其實到現在還沒辦法真的理解使用上,蒙地卡羅演算法跟蒙地卡羅方法的差別,跪求解釋。這篇會分兩段去寫分別會是如何使文字流動起來,和如果轉換圖像文字。

首圖先自肥

更新一下近況,好不容易在杜塞道夫註冊完居住地,終於算是半個杜賽人了。有時間其實寫一些畢業之後小心得寫了一半,但有點太恥不敢發,再修一下過幾天再說。


之前寫不少Python的部落格,想來寫寫最近用Processing玩的小玩意。起因是那天在河堤邊被林彥翔嘴正在實驗的新作品視覺太醜,翻來覆去睡不著,隔天就想來玩玩看用文字來模擬流動的效果。然後有沒有辦法把這樣的動態map到影像上。後來剛到德國時淳俐在柏林有表演找了我去做視覺湊一腳,就用了這個效果來玩。


在玩Ascii art時,當時都是一格一格描圖在那邊對色號畫出來的,或是抽選不同的文字慢慢試出來,也有些軟體或網頁可以做到輸入圖片,置換出文字檔,但既然可以自己做當然要自己玩囉。最開始寫這份Code的時候,因為喝酒所以寫的很亂,所以code的部分就不贅述太多,單純講一些實行方式,頂多用一兩行來註解一下比較關鍵的部分。

在這邊附上一些ascii art的連結:https://lmgtfy.com/?q=ascii+art&s=g


第一部分:文字流動


最一開始的時候是先從想讓文字流動開始,首先先寫一個可以佈滿畫布(Canvas)存放char的二維陣列(這邊先命名為text),因為我很懶得算,所以陣列大小就是[width+1][height+1]每一個二維陣列先存取一個隨機的文字,然後平鋪在畫布上。然後按著畫面位置上點色。


另一個部分是記得做textAlign(CENTER);,讓文字對齊在指定的位置中間(雖然字體的關係會有點下偏)。


for (int x=0; x<=width; x+=10) { for (int y=0; y<=height; y+=10) { text[x][y] = char(int('a'+random(26))); } } for (int x=0; x<=width; x+=10) { for (int y=0; y<=height; y+=10) { fill(255,100); //上色 text(text[x][y], x, y); } }

稍稍解釋下 Ascii 碼,附上一張精美的Ascii Table。我們可以看到小寫 a 的編號是97,之後的連續25個字母都是英文小寫,因此我們要亂數產生英文小寫的話,只需要先整數運算出 ‘a’ + random(26) 之後去強轉成整數,小撇步是加成乘除運算,系統會自己默認為數字運算。之後只要強轉回char()就得到字母了。


最後就形成這張圖~~

下一步想要做出類似駭客任務的流動效果,只要在最上面那排不斷的隨機生成文字。然後再隨機的條件下,會讓下面的陣列位置繼承上面的文字,就會有這種效果,而調整Random的參數,也可以影響線條的長短流速等等。

for (int x=0; x<=width; x+=10) { text[x][0] = (char('a'+int(random(25)))); } for (int y=10; y<=height; y+=10) { for (int x=0; x<=width; x+=10) { if (random(5)>3) text[x][y] = text[x][y-10]; } }

執得一提的部分是,比較正確的程式撰寫邏輯,在換取的程式中,一般來說會習慣還是從底部一路換上去,就不會發生一次替換一整排的效果,但假設有了隨機的部分,也可以做出比較長條的線條,可以自己實驗看看下兩種方法。

for (int y=height; y>=10; y-=10) for (int y=10; y<height; y+=10)

重複製作由左至右的流動之後,就會有這樣大小色塊的狀態出現。

也因此會發現,一些文字例如 i, o, c 等等,會有比較多黑色的佔比,而大的文字則可以填出比較滿得色彩。而這些佔比較小文字也形成類似河流中不同的效果。這就回到最初的疑問,要怎麼樣知道哪些文字佔比高,哪些文字佔比低,然後可以把它套用到圖片使用上。




第二部分:圖像轉文字


總之核心部分是高中時候用來練習算圓周率的蒙地卡羅演算法,如下圖。


數學沒很好但大致解釋一下最簡單的使用方法就是計算圓周率的近似值。我們可以知道半徑為r的四分之一圓的面積為π*r*r/4,而邊長為r的正方形面積為r*r。兩者比值為π:4。因此我們可以在xy軸最大為r的情況下,隨機取樣,只要其距離與(0,0)小於r,便可以視為在圓內,再把圓內的所有點的數量加總起來。假設我們隨機打了10萬個點其中可能有X個點在圓內,則可以得知 100000:X = π:4 ,套入國中學會的方法(是國中吧?)便可以得知π = 400000/X。這樣的方法可以得知圓周率的近似值,而想當然爾,取樣的數字越高10萬100萬1000萬,數值就會越精準。


好了,廢話太多,跳回原本在計算文字的部分。我用了同樣的方法來計算文字在框格中的佔比,不同的Font自然大小也不同,比例也不同,這邊先以Processing-Java內建的SansSerif做說明。


首先因為我們只要取的是相對的比例,因此只要套用同一種尺寸作為標準即可(這部分我做的非常懶散),我們先將所有要計算的符號套入一個一維陣列,包含26+26大小寫英文字幕以及14個符號。懶得加總的人,當然也可以在setup當初寫一個 int index_length = index.length; 來計算總數。然後也在全域變數的地方宣告一個用來記錄的陣列。

char[] index = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '+', ' ‘}; float[] index_density;

我們將內建的文字固定好位置開一個800x800的canvas之後,把字型大小設定成500pt。並初始化儲存用的陣列。

size(800,800); textAlign(CENTER,CENTER); textSize(500); index_density=new float[index_length];

設置成500的原因,是因為qp等等字幕有些會往下超出框格範圍,因為我們只要相對比例,因此我們將所有文字大小都縮小。


然後回到原先使用蒙地卡羅的方法,一個一個畫上新的文字,打上十萬個點,計算比值。Processing快樂的地方就出現了,計算新增取樣的點是否在文字上的方法,只要用get()去抓顏色,將底色設定為白色,文字設定成黑色,當get到的數字是黑色時,便可以知道是否取樣到的點在文字上。下圖先順帶上色示意,但實際在執行的時候,要記得畫面上切記不能有其他數字顯示,或是畫上新東西,會導致取樣失準喔。


100萬個點的示意圖。


取出來的數字最後去做百分比和去小數點的動作。先把要除的數字都先轉為小數,才不會出錯,然後用nfc去小數點後三位。

nfc((float(total)/float(1000000))*100, 3));

再把每一個文字都做過一次,就得到一串陣列了。

float[] index_density = {4.595, 5.98, 3.475, 5.964, 4.46, 3.819, 6.538, 5.446, 2.25, 3.421, 5.131, 2.788, 7.01, 4.547, 4.703, 5.808, 5.809, 2.502, 3.456, 3.181,4.626, 3.487, 6.27, 3.945, 4.296, 4.2, 6.145, 7.078, 5.328, 7.283, 5.367, 4.456, 6.068, 6.606, 2.782, 3.793, 5.865, 3.744, 9.116, 7.354, 7.154, 5.516,8.077, 6.755, 4.99, 4.363, 5.898, 4.961, 9.212, 5.624, 4.408, 5.674, 1.737,2.119, 7.047, 5.383, 5.19, 6.321, 2.727, 7.452, 1.506, 2.689, 2.71, 1.173, 2.911, 0.0};

之後只要再對其作比值排序,這邊我很懶,我就直接寫個很簡單的Bubble Sort,在做數字交換時,同時交換文字,最後得到的文字陣列,便是我所需要的排序好的文字佔比。每次實作都會有誤差,可以大家換字體或是增加取樣數自己試試看。

for (int i=0; i<index_length-1; i++) { for (int j=i+1; j<index_length; j++) { if (index_density[i]>index_density[j]) { float tempF = index_density[i]; index_density[i] = index_density[j]; index_density[j] = tempF; char tempC = index[i]; index[i] = index[j]; index[j] = tempC; } } }

最後就取得我們要的漂亮答案。算完之後便可以把這串文字整理起來直接應用。


這串文字應用方法很多,我們可以直接map到顏色,上不同彩度,或是分區間製造一點隨機性。

PImage img = loadImage("XXX.jpg"); for (int x=0; x<width; x+=10) { for (int y=0; y<height; y+=10) { //map對應的畫布位置 int gx = floor(map(x, 0, width, 0, img.width)); int gy = floor(map(y, 0, height, 0, img.height)); fill(255,0,0, 60); text(index[floor(map(brightness(img.get(gx, gy)),255, 0, 0, index_length-1))], x, y); } }

可以透過這樣的方式,取得原圖像對應畫布位置的色塊,再透過不管是飽和度(Saturation),明度(Brightness),甚至是RGB等等,map出不同的效果。選取的方式,也要試圖片而定,圖片假設所有pixel的飽和度都很高,那選擇Brightness效果就沒那麼好。

也可以同時疊加很多層次,然後取圖片上同樣位置的red, green,blue比例,對應的放上上了顏色的文字,也會有一種特殊的效果。

相當不錯的地方是,影片也跑得起來呢!


大致上的內容差不多就這樣,附上分析的github連結。最近作業好多,過幾天再把Readme.md打完,嘖嘖

https://github.com/aprilcoffee/FontDensityCalculation/blob/master/FontDensityCalculation.pde



353 次查看0 則留言