概述
今天进行一个很有意思的实践,利用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,有兴趣朋友可以体验一下。