所需模块
本项目使用 Pillow 读取图片,访问它们的底层数据,创建和修改图像。
代码
为了从输入的深度图生成三维立体画,首先重复一幅给定的平铺图像,生成一幅中间图像。接下来,生成一幅充满随机点的平铺图像。然后进入生成三维立体画的核心代码,即利用所提供的深度图中的信息,移动输入的图像。
重复给定的平铺图像
我们从利用 createTiledImage()方法开始,通过平铺一个图形文件,创建一幅新的图像。图像尺寸由 dims 元组指定,该元组形式为(width, height)。
def createTiledImage(tile, dims):
# 利用提供的尺寸(dims)创建新的 Python 图像库(PIL)Image 对象
img = Image.new('RGB', dims)
W, H = dims
w, h = tile.size
# 确定列数
cols = int(W/w) + 1
rows = int(H/h) + 1 # 确定中间图像所需的行数
# paste the tiles into the image
for i in range(rows):
for j in range(cols):
img.paste(tile, (j*w, i*h)) # 循环遍历行和列,并用平铺图像填充它们
# output the image
return img
通过乘积(jw, ih),确定平铺图像左上角的位置,这样它能对准行和列。完成后,该方法返回指定尺寸的 Image 对象,用输入图像 tile 平铺。
从随机圆创建平铺图像
如果用户不提供平铺图像,就利用 createRandomTile()方法,用随机圆圈创建一张平铺图像。
def createRandomTile(dims):
# 用 dim 给出的尺寸创建新的 Image 对象
img = Image.new('RGB', dims)
draw = ImageDraw.Draw(img)
# 用 ImageDraw.Draw() 在该图像中画圆圈,用宽或高中较小值的 1/100 作为半径,画圆圈
r = int(min(*dims)/100)
# 设置要画的圆圈数为 1000
n = 1000
# draw random circles
for i in range(n):
# 算出每个圆圈的 x 和 y 坐标
x, y = random.randint(0, dims[0]-r), random.randint(0, dims[1]-r)
fill = (random.randint(0, 255), random.randint(0, 255),
random.randint(0, 255)) # 在[0,255]的范围内随机选取 RGB 值,用选择颜色填充
draw.ellipse((x-r, y-r, x+r, y+r), fill)
return img
创建三维立体画
现在,让我们创建一些三维立体画。createAutostereogram()方法完成了大部分工作,如下所示:
def createAutostereogram(dmap, tile):
# 进行完整性检查,确保深度图和图像具有相同的尺寸
if dmap.mode is not 'L':
dmap = dmap.convert('L')
# 如果用户没有提供平铺图像,就创建随机圆圈平铺图像
if not tile:
tile = createRandomTile((100, 100))
# 创建一张平铺好的图像,符合提供的深度图的大小
img = createTiledImage(tile, dmap.size)
# 生成这张平铺好的图像的副本
sImg = img.copy()
# 调用 Image.load()方法,将图像数据加载到内存中。该方法允许用形如[i, j]的二维数组来访问图像像素
pixD = dmap.load()
pixS = sImg.load()
# 将图像的尺寸保存为行数和列数,将图像看成单个像素构成的网格
cols, rows = sImg.size
for j in range(rows):
for i in range(cols):
xshift = pixD[i, j]/10
xpos = i - tile.size[0] + xshift
if xpos > 0 and xpos < cols:
pixS[i, j] = pixS[xpos, j]
# display the shifted image
return sImg
三维立体画创建算法的核心在于,根据从深度图中收集的信息,移动平铺图像中像素的方式。要做到这一点,遍历平铺图像,处理每一个像素。
在:
xshift = pixD[i, j]/10
根据深度图 pixD 中的相关像素,查找偏移的值。然后将这个深度值除以 10,因为这里用的是 8 位深度图,这意味着深度的范围是 0 到 255。如果除以 10,得到的深度值范围是 0 到 25。由于深度图输入图像的尺寸通常是几百像素,所以这些偏移值很合适。
在:
xpos = i - tile.size[0] + xshift
计算像素的新 x 位置,用平铺图像填充三维立体画。每隔 w 个像素,像素的值不断重复,由公式 ai = ai + w 表示,其中的 ai是在 x 轴下标 i 处的给定像素的颜色(因为考虑的是像素行,而不是列,所以忽略 y 方向)。
要创建深度感,就要让间隔(或重复的间距)与该像素的深度图值成正比。这样在最终的三维立体画图像中,每个像素和它前一次(周期地)出现相比,偏移了delta_i。
命令行选项
现在,我们来看看该程序的 main()方法,其中提供了一些命令行选项。
# create a parser
parser = argparse.ArgumentParser(description="Autosterograms...")
# add expected arguments
parser.add_argument('--depth', dest='dmFile', required=True)
parser.add_argument('--tile', dest='tileFile', required=False)
parser.add_argument('--out', dest='outFile', required=False)
# parse args
args = parser.parse_args()
# set the output file
outFile = 'as.png'
if args.outFile:
outFile = args.outFile
# set tile
tileFile = False
if args.tileFile:
tileFile = Image.open(args.tileFile)
像以前的项目一样,利用 argparse 为程序定义了一些命令行选项。一个必需的参数是深度图文件,两个可选的参数是平铺图像文件名和输出文件名。如果未指定平铺图像,程序会生成随机圆圈平铺图像。如果未指定输出文件名,则三维立体画会输出到 as.png 文件。