完整代码
import sys, os, random, argparse
from PIL import Image
import imghdr
import numpy as np
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))
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
imgs.append(image.crop((i*w, j*h, (i+1)*w, (j+1)*h)))
return imgs
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
def getImageFilenames(imageDir):
files = os.listdir(imageDir)
filenames = []
for file in files:
filePath = os.path.abspath(os.path.join(imageDir, file))
try:
imgType = imghdr.what(filePath)
if imgType:
filenames.append(filePath)
except:
print("Invalid image: %s" % (filePath,))
return filenames
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
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
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
def 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)
args = parser.parse_args()
target_image = Image.open(args.target_image)
print('reading input folder...')
input_images = getImages(args.input_folder)
if input_images == []:
print('No input images found in %s. Exiting.' % (args.input_folder, ))
exit()
random.shuffle(input_images)
grid_size = (int(args.grid_size[0]), int(args.grid_size[1]))
output_filename = 'mosaic.png'
if args.outfile:
output_filename = args.outfile
reuse_images =True
resize_input = True
print('starting photomosaic creation...')
if not reuse_images:
if grid_size[0]*grid_size[1] > len(input_images):
print('grid size less than number of images')
exit()
if resize_input:
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)
mosaic_image = createPhotomosaic(target_image, input_images, grid_size, reuse_images)
mosaic_image.save(output_filename, 'PNG')
print("saved output to %s" % (output_filename,))
print('done.')
if __name__ == '__main__':
main()
运行程序
在本文件所在目录打开终端,输入:
python photomosaic.py --target-image cherai.jpg --input-folder test-data/set6/ --grid-size 128 128
其中 photomosaic.py是本文件名,cherai.jpg是输入图像的名称,**test-data/set6/**是输入图像所在的文件夹,128 128是网格尺寸,可根据需要自行更改。