【Python 项目】照片马赛克 - 2

代码

首先读入那些小块图像,它们将用于创建照片马赛克。接下来,计算图像的平均 RGB 值,然后将目标图像分割成图像网格,为小块找到最佳匹配。最后,组装图像小块,创建最终的照片马赛克。

读入小块图像

首先,从给定的文件夹中读入输入图像。下面是具体做法:

def getImages(imageDir):
	"""
	用 os.listdir()将 imageDir 目录中的文件放入一个列表
	"""
	files = os.listdir(imageDir)
	images = []
	for file in files:
		# os.path.abspath()和 os.path.join()来获取图像的完整文件名
		filePath = os.path.abspath(os.path.join(imageDir, file))
		try:
			# 用 open()打开图像文件。在随后的几行中,将文件句柄传入 Image.open(),将得到的图像 im 存入一个数组
			fp = open(filePath, "rb")
			im = Image.open(fp)
			images.append(im)
			# 调用 Image.load(),强制加载 im 中的图像数据
			im.load()
			# 关闭文件句柄,释放系统资源
			fp.close()
		except:
			# skip
			print("Invalid image: %s" % (filePath,))
	return images

计算输入图像的平均颜色值

读入输入图像后,需要计算它们的平均颜色值,以及目标图像中的每个小块的值。创建一个方法 getAverageRGB()来计算这两个值。

def getAverageRGB(image):
	"""
	return the average color value as (r, g, b) for each input image
	"""
	# 用 numpy 将每个 Image 对象转换为数据数组。返回的 numpy 数组形为(w, h, d)
	im = np.array(image)
	# 保存 shape 元组,然后计算平均 RGB值,将这个数组变形为更方便的形状(w*h, d)
	w,h,d = im.shape
	# 用 numpy.average()计算出使用平均值
	return tuple(np.average(im.reshape(w*h, d), axis=0))

将目标图像分割成网格

现在,需要将目标图像分割成 M×N 网格,包含更小的图像。让我们创建一个方法来实现。

def splitImage(image, size):
	"""
	given the image and dimensions (rows, cols), return an m*n list of images
	"""
	W, H = image.size[0], image.size[1] # 得到目标图像的维度
	m, n = size # 得到尺寸
	w, h = int(W/n), int(H/m) # 用基本除法计算目标图像中每一小块的尺寸
	# image list
	imgs = []
	# generate a list of dimensions
	for j in range(m):
		for i in range(n):
			# append cropped image
		# image.crop()利用左上角图像坐标和裁剪图像的维度作为参数,剪裁出图像的一部分
		imgs.append(image.crop((i*w, j*h, (i+1)*w, (j+1)*h)))
	return imgs

寻找小块的最佳匹配

现在,让我们从输入图像的文件夹中,找到小块的最佳匹配。创建一个工具方法 getBestMatchIndex(),如下所示:

def getBestMatchIndex(input_avg, avgs):
	"""
	return index of the best image match based on average RGB value distance
	"""

	# input image average
	avg = input_avg

	# get the closest RGB value to input, based on RGB distance
	index = 0 
	# 将最接近的匹配下标初始化为 0,最小距离初始化为无穷大
	min_index = 0 
	min_dist = float("inf")
	for val in avgs: # 遍历平均值列表中的值
		# 用标准公式计算距离
		dist = ((val[0] - avg[0])*(val[0] - avg[0]) + 
				(val[1] - avg[1])*(val[1] - avg[1]) + 
				(val[2] - avg[2])*(val[2] - avg[2]))
		if dist < min_dist: #如果计算的距离小于保存的最小距离 min_dist,它就被替换为新的最小距离
			min_dist = dist
			min_index = index
		index += 1 

	return min_index

迭代结束时,就得到了平均 RGB 值列表 args 中,最接近 input_avg 的下标。现在可以利用这个下标,从小块图像的列表中选择匹配的小块图像了。

创建图像网格

在继续创建照片马赛克之前,还需要一个工具方法。createImageGrid()方法将创建大小为 M×N 的图像网格。这个图像网格是最终的照片马赛克图像,利用选择的小块图像列表来创建。

def createImageGrid(images, dims):
	"""
	given a list of images and a grid size (m, n), create a grid of images
	"""
	m, n = dims # 取得网格的尺寸,然后用 assert 检查,提供给 createImageGrid()的图像数量是否符合网格的大小
	
	# sanity check
	assert m*n == len(images)
	
	# 计算小块图像的最大宽度和高度
	# don't assume they're all equal
	width = max([img.size[0] for img in images])
	height = max([img.size[1] for img in images])

	# 创建一个空的 Image,大小符合网格中的所有图像
	grid_img = Image.new('RGB', (n*width, m*height))
	
	# paste the tile images into the image grid
	for index in range(len(images)):
		row = int(index/n)
		col = index - n*row
		grid_img.paste(images[index], (col*width, row*height)) # 循环遍历选定的图像,利用 Image.paste()方法,将它们粘贴到相应的网格中
	
	return grid_img

创建照片马赛克

现在,有了所有必需的工具方法,让我们编写一个 main 函数,创建照片马赛克。

def createPhotomosaic(target_image, input_images, grid_size, reuse_images=True):
	"""
	creates a photomosaic given target and input images
	"""
	
	print('splitting input image...')
	# split the target image into tiles
	target_images = splitImage(target_image, grid_size)
	
	print('finding image matches...')
	# for each tile, pick one matching input image
	output_images = []
	# for user feedback
	count = 0 
	batch_size = int(len(target_images)/10)
	# calculate the average of the input image
	avgs = []
	for img in input_images:
		avgs.append(getAverageRGB(img))
	
	for img in target_images:
		# compute the average RGB value of the image
		avg = getAverageRGB(img)
		# find the matching index of closest RGB value
		# from a list of average RGB values
		match_index = getBestMatchIndex(avg, avgs)
		output_images.append(input_images[match_index])
		# user feedback
		if count > 0 and batch_size > 10 and count % batch_size is 0:
			print('processed %d of %d...' %(count, len(target_images)))
		count += 1 
		# remove the selected image from input if flag set
		if not reuse_images:
			input_images.remove(match)

	print('creating mosaic...')
	# create photomosaic image from tiles
	mosaic_image = createImageGrid(output_images, grid_size)
	
	# display the mosaic
	return mosaic_image

添加命令行选项

该程序的 main()方法支持这些命令行选项:

# parse arguments
parser = argparse.ArgumentParser(description='Creates a photomosaic frominput images')

# add arguments
parser.add_argument('--target-image', dest='target_image', required=True)
parser.add_argument('--input-folder', dest='input_folder', required=True)
parser.add_argument('--grid-size', nargs=2, dest='grid_size', required=True)
parser.add_argument('--output-file', dest='outfile', required=False)

这段代码包含 3 个必需的命令行参数:目标图像的名称、输入图像文件夹的名称,以及网格尺寸。第 4 个参数是可选的文件名。如果省略该文件名,照片马赛克将写入文件 mosaic.png。

控制照片马赛克的大小

要解决的最后一个问题是照片马赛克的大小。如果基于目标图像中匹配的小块,盲目地将输入图像粘贴在一起,就会得到一个巨大的照片马赛克,比目标图像大得多。为了避免这种情况,调整输入图像的大小,以匹配网格中每个小块的大小(这样做还有一个好处,可以加快平均 RGB 的计算,因为用了较小的图像)。main()方法也进行这样的处理:

print('resizing images...')
# for given grid size, compute the maximum width and height of tiles
dims = (int(target_image.size[0]/grid_size[1]), # 根据指定的网格大小,计算目标图像的维度
		int(target_image.size[1]/grid_size[0]))
print("max tile dims: %s" % (dims,))
# resize
for img in input_images:
	img.thumbnail(dims) # 调整图像,以适应网格的大小
  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

QuantumStack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值