概述
今天進行一個很有意思的實踐,利用Java程式設計實作照片拼圖,通過程式操作圖檔檔案,結合photoshop軟體,實作下面的效果:

該圖檔由很多小圖檔拼接而成,放大以後是這樣的效果:
下文中将介紹實作步驟,主要用到了Java程式和photoshop,利用Java程式設計完成圖檔的拼接和原圖像的采樣,然後利用photoshop完成采樣圖檔和拼圖圖檔的疊加,達到更好的效果。
原理
構造這樣一幅拼圖的原理很簡單,主要分為3個步驟:
① 将作為子元素的圖檔縮小,拼接成一幅圖檔,這裡稱為:拼圖圖檔。
② 對原圖檔中的顔色進行采樣,并實作圖檔的像素化,生成半透明的采樣圖檔。
③ 将采樣圖檔覆寫在拼圖圖檔之上,操作完成。
合成步驟可以用下圖來描述:
利用Java中的BufferedImage類可以很輕松地對圖檔檔案進行處理,在這裡,我們主要的操作是:讀取和寫入圖檔中某個位置的像素值。主要用到的方法如下:
BufferedImage read(File file):BufferedImage(int width, int height, int imageType):ImageIO類中的靜态方法。根據指定的File對象建構一個BufferedImage對象,支援png、jpg格式的圖檔檔案,如果file對象指向的不是一個圖檔檔案,則會出現異常。int getRgb(int x, int y):建構一個新的BufferedImage對象,參數指定了圖檔的尺寸以及圖檔的類型。imageType參數可以使用BufferedImage類中提供的靜态常量,例如BufferedImage.TYPE_3BYTE_BGR,表明圖檔為RGB三色模式,每個像素的資訊用3個位元組來表示。初始情況下,圖檔中所有位置的像素值均為0,即為黑色。void setRgb(int x, int y, int rgb):擷取圖檔中指定位置的像素值,傳回值為一個int結果,從低位到高位的3個位元組分别表示藍、綠、紅三種基本顔色的飽和度。boolean write(BufferImage buffer, String formatName, File output):設定圖檔中指定位置的像素值。ImageIO類中的靜态方法,将圖檔資訊寫入到檔案中,其中formatName為一個表示圖檔檔案格式的字元串,合法的值有"jpg"、"png"等,output參數則表示目标輸出檔案。 |
這裡可以通過簡單的計算來找到這些參
利用以上的API變可以完成圖檔的拼接操作。當然,這些API隻是程式中的核心步驟,要程式設計生成拼圖檔案,還有很多問題需要解決,詳見下文。
實作
接下來介紹具體的程式實作方式。
·參數設定
首先介紹程式中用到的幾個參數:
|
這裡可以通過簡單的計算來找到這些參數之間的關系,如果設定了模闆圖檔的尺寸(orgWidth、orgHeight)和元素圖檔(photoWidth、photoHeight)的尺寸以及放大倍率(L),此時,構成結果圖檔中的元素圖檔的行數和列數分别是: row = floor[orgWidth / (photoWidth / L)] column = floor[orgHeight / (photoHeight / L)]
這裡的floor表示向下取整,即如果計算得到的行數和列數不是整數,此時,模闆圖檔中的邊緣部分可能無法呈現在結果圖檔中。同時,我們還可以計算出結果圖檔的大小尺寸:
resultWidth = orgWidth * L = row * photoWidth
resultHeight = orgHeight * L = column * photoHeight
該資料同時還是采樣圖檔和拼圖圖檔的大小尺寸資料。
以上為程式中用到的參數。
·調整長寬比例
調整長寬比例的目的是使得所有的元素圖檔具有相同的長寬比例,便于之後的程式調整圖檔的大小。在程式的輸入中我們設定了元素圖檔的尺寸photoWidth、photoHeight,是以,元素圖檔的長寬比例為:photoWidth / photoHeight,将這個結果攜程比值的形式為:
寬 :高 = w : h (其中:w = photoWidth / gcd, h = photoHeight / gcd)
其中:gcd為photoWidth和photoHeight的最大公約數。
在保證圖檔長寬比例的同時,我們還要使得修改後的圖檔的面積盡可能的大,這樣就使得原有圖檔的内容能盡可能多地保留。
這裡需要計算兩個參數:
qw = width / w;
qh = height / h;
其中:width和height為圖檔調整前的原始尺寸。 選擇qw和qh的較小者q,然後就可以确定調整比例後的圖檔的尺寸(用width'和height'來表示):
width' = q * w
height' = q * h
此時,可以得到下面的結論:
qw > qh時,q = qh,此時:width' = qh * w, height' = height。
qw < qh時,q = qw,此時:width' = width, height' = qw * h。
·調整大小
調整比例後,所有的元素圖檔都有相同的長寬比例,接下來調整圖檔的大小。 調整圖檔大小的邏輯很簡單,取出x坐标是width'/photoWidth的倍數,同時y坐标是height'/photoHeight的倍數位置的像素點即可。
·生成拼圖圖像
調整至所有元素圖檔大小相同後,我們就可以利用這些元素圖檔生成拼圖圖檔了,拼圖圖檔的大小為:resultWidth × resultHeight,元素圖檔的大小為photoWidth × photoHeight,拼圖圖檔中具有row行,column列的元素圖檔。在這裡,我們需要進行坐标映射,對于位于拼圖圖檔的第r行第c列的元素圖檔,其中的任意一個像素點,如果該點在元素圖檔中的坐标是px,py,那麼該點在拼圖圖檔中的坐标dx,dy可以表示為:
dx = photoWidth * r + px
dy = photoHeight * c + py
其中:0 ≤ px < photoWidth, 0 ≤ py < photoHeight。
确定了元素圖檔和拼圖圖檔的坐标映射關系後,我們還需要決定具體每個位置要選擇哪個圖檔進行覆寫,我們需要找到色調和模闆圖檔對應位置最相似的圖檔進行覆寫,例如,如果模闆圖像為一張人像,那麼圖檔中人面部顔色較淺,我們需要選擇顔色較淺的元素圖檔進行拼接;圖檔中頭發的部分顔色較深,我們需要選擇色調較暗的元素圖檔進行覆寫。
這裡為找到每個位置的最合适的圖檔,我們定義一個判定的标準,在選擇拼圖圖檔中的第r行,第c列的位置的圖檔時,對于某一個候選圖檔中的每一個像素點(px,py),首先将其轉化為拼圖圖檔中的坐标(dx, dy),然後,在将其顔色和模闆圖檔中對應為位置的像素點的顔色進行比較,計算其相似度s,對于相似度s,我們定義為兩像素點紅、綠、藍三個分量的絕對內插補點之和,即:
r1 = rgb1 & 0x000000ff; g1 = (rgb1 & 0x0000ff00) >> 8; b1 = (rgb1 & 0x00ff0000) >> 16; r2 = rgb2 & 0x000000ff; g2 = (rgb2 & 0x0000ff00) >> 8; b2 = (rgb2 & 0x00ff0000) >> 16;
s = |r2 - r1| + |b2 - b1| + |g2 - g1|
然後将s值和約定的門檻值s0進行比較,統計候選圖檔中s值小于門檻值s0的像素點的個數count,找到候選圖檔中count值最大的圖檔,就是目前位置需要選擇的圖檔。
這裡還有一個問題:由于模闆圖像和拼圖圖像的大小是不相同的,是以,在比較像素點的相似度時,還需要對拼圖圖檔的坐标和模闆圖檔的像素點坐标進行坐标映射。通過計算,我們可以得到:對于拼圖圖像中的位置(dx,dy)原始圖像中的對應位置(rx, ry)可以表示為:
rx = dx / L
ry = dy / L
進而,根據上文中的計算結果,我們可以得到元素圖檔中的像素點坐标(px,py)和模闆圖檔中的坐标(rx, ry)之間的直接映射關系:
rx = orgWidth / row * r + px / L
ry = orgHeight / column * c + py / L
這樣,在選擇圖檔時,将位于元素圖檔中(px, py)位置的像素點和模闆圖檔中位于(rx, ry)位置的像素點進行比較,計算相似度s,選擇最優的圖檔拼接即可。
下圖是筆者采用的模闆圖檔和生成的拼圖圖檔,可以看到,設定的放大倍率越大,包含的元素圖檔越多,生成的拼圖圖像和模闆圖像的相似度也就越高。
| |
| |
·生成采樣圖檔
生成拼圖圖檔過程中,雖然程式會通過計算找到合适元素圖檔對模闆圖檔的每個位置進行拼接,但是,如果選擇的元素圖檔在色彩上和模闆圖檔差别很大,生成的拼圖圖像效果依舊不好,解決的方案是在拼圖圖像上覆寫一層半透明的基于模闆圖像的采樣圖像使得拼圖圖像的色調跟符合原圖。本步驟的目的就是生成這個采樣圖檔,顯然采樣圖檔的尺寸和拼圖圖檔是完全一緻的。
對于由row行column列元素圖檔構成的拼圖圖檔,其對應的采樣圖檔也由row行column列構成,不同的是,構成采樣圖檔的每個元素圖檔是隻有一種顔色的純色色塊,這裡由于筆者對于計算機圖形學的相關知識了解甚少,是以采用一種最簡單的政策,選擇模闆圖檔對應位置的中心位置的像素的顔色作為采樣色。如下圖所示:
同樣,這裡需要對模闆圖檔和采樣圖檔之間進行坐标映射,這個映射關系和上文中選擇元素圖檔時用到的映射關系很相似,對于采樣圖檔中第r行第c列的元素圖檔,其顔色應該采樣于模闆圖檔中的sx,xy位置,則:
sx = orgWidth / row * (r + 1/2)
sy = orgHeight / column * (c + 1/2) 下圖為筆者生成的采樣圖檔,同樣可以看出,放大倍率越大,包含的元素圖檔越多,采樣圖檔就越清晰,就能保留模闆圖檔中更多的資訊。
| |
| |
·代碼整合
以上就是Java程式完成的全部工作,為了操作友善,筆者利用Swing繪制了GUI界面,将以上各個步驟的代碼整合起來:
PS疊加
最後的步驟是疊加,利用photoShop将采樣圖檔疊加在拼圖圖檔之上,然後将采樣圖檔設定為半透明即可:
以上便是生成照片拼圖方法。
程式代碼
程式代碼見:https://github.com/octopusfly/photo_puzzle,有興趣朋友可以體驗一下。