关注「实验楼」,每天分享一个项目教程
如何使用 Python 创建照片马赛克呢?我们将目标图像划分若干个网格,再用相近的颜色或图像去替换即可。跟着下面的操作,你一定能学会用Python自动实现这个效果。
你可以指定网格的尺寸,并选择输入图像是否可以在马赛克中重复使用。用 Python 图像库(PIL)创建图像;
计算图像的平均 RGB 值;
剪切图像;
通过粘贴另一张图像来替代原图像的一部分;
利用平均距离测量来比较 RGB 值。
Python
Xface 终端
读入一些小块图像,他们将取代原始图像中的小块;
读入目标图像,将他们分割成 M*N 的小块网格;
对于目标图像中的每个小块,从输入的小块图像中找到最佳匹配;
将选择的输入图像安排在 M*N 的网格中,创建最终的照片马赛克。
$ sudo apt-get update
$ sudo pip install --upgrade pip # 更新 pip
$ sudo pip install Pillow numpy # 使用 pip 安装 Pillow 和 numpy
$ wget http://labfile.oss.aliyuncs.com/courses/1041/test-data.zip
$ unzip test-data.zip
def getImages(imageDir):
"""
给定一个目录,加载该目录下的图像,并以列表的形式返回
"""
files = os.listdir(imageDir)
images = []
for file in files:
filePath = os.path.abspath(os.path.join(imageDir, file))
try:
# 显式加载以避免资源危机
fp = open(filePath, "rb")
im = Image.open(fp)
images.append(im)
# 强制从文件中加载图像数据
im.load()
# 关闭文件
fp.close()
except:
# skip
print("Invalid image: %s" % (filePath,))
return images
def getAverageRGB(image):
"""
计算并返回给定 Image 对象 (r,g,b) 形式的颜色平均值
"""
# 将图像转换成 numpy 中的数组 (三维数组)
im = np.array(image)
# 获取宽度, 高度, 深度
w,h,d = im.shape
# 计算平均值
return tuple(np.average(im.reshape(w*h, d), axis=0))
def splitImage(image, size):
"""
根据给定图像的维度来分割图像,返回一个大小为 m*n 的图像列表
"""
W, H = image.size[0], image.size[1]
m, n = size
w, h = int(W/n), int(H/m)
# 图像列表
imgs = []
# 生成列表
for j in range(m):
for i in range(n):
# 向 imgs 中追加裁减后的小块图像
imgs.append(image.crop((i*w, j*h, (i+1)*w, (j+1)*h)))
return imgs
def getBestMatchIndex(input_avg, avgs):
"""
返回按照 RGB 值的距离挑选出的最佳匹配。
"""
# 图像的均值
avg = input_avg
# 根据 x/y/z 的距离从 avgs 中挑选出距 input_avg 最近的值
index = 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 = dist
min_index = index
index += 1
return min_index
def createImageGrid(images, dims):
"""
给定网格大小和小块图像列表, 创建由图像组成的网格
"""
m, n = dims
# 检查参数
assert m*n == len(images)
# 获取图像宽度, 高度的最大值
# 注意: 并不是每个小块图像的大小都是相同的
width = max([img.size[0] for img in images])
height = max([img.size[1] for img in images])
# 创建输出图像
grid_img = Image.new('RGB', (n*width, m*height))
# 粘贴图像
for index in range(len(images)):
row = int(index/n)
col = index - n*row
grid_img.paste(images[index], (col*width, row*height))
return grid_img
def createPhotomosaic(target_image, input_images, grid_size,
reuse_images=True):
"""
从给定的目标图像和小块图像创建照片马赛克
"""
print('splitting input image...')
# 将目标图像分割分割成子图像
target_images = splitImage(target_image, grid_size)
print('finding image matches...')
# 对于每一个目标图像,从输入选择一个
output_images = []
# 用于向用户反馈
count = 0
batch_size = int(len(target_images)/10)
# 计算输入的小块图像的平均值
avgs = []
for img in input_images:
avgs.append(getAverageRGB(img))
for img in target_images:
# 子图像的均值
avg = getAverageRGB(img)
# 寻找最匹配的索引
match_index = getBestMatchIndex(avg, avgs)
output_images.append(input_images[match_index])
# 向用户反馈进度
if count > 0 and batch_size > 10 and count % batch_size is 0:
print('processed %d of %d...' %(count, len(target_images)))
count += 1
# 如果不允许重用图像, 就移除被选中的图像
if not reuse_images:
input_images.remove(match_index)
print('creating mosaic...')
# 将照片马赛克保存在图像中
mosaic_image = createImageGrid(output_images, grid_size)
# 返回照片马赛克
return mosaic_image
# 解析命令行参数
parser = argparse.ArgumentParser(description='Creates a photomosaic from input images')
# 添加参数
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()
# 载入小块图像
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)
完整代码您可以点击阅读原文,或登录实验楼找到:https://www.shiyanlou.com/courses/1041
$ python photomosaic.py --target-image test-data/a.jpg --input-folder test-data/set1/ --grid-size 128 128
reading input folder...
starting photomosaic creation...
resizing images...
max tile dims: (14, 10)
splitting input image...
finding image matches...
processed 1638 of 16384...
processed 3276 of 16384...
processed 4914 of 16384...
processed 6552 of 16384...
processed 8190 of 16384...
processed 9828 of 16384...
processed 11466 of 16384...
processed 13104 of 16384...
processed 14742 of 16384...
processed 16380 of 16384...
creating mosaic...
saved output to mosaic.png
done.
$ firefox test-data/a.jpg # 查看原图
$ firefox mosaic.png # 查看结果图片
编写一个程序,创建图像的块状版本,类似实验中出现的第一张图片。
利用本章的代码,通过粘贴匹配的图像创建照片马赛克,小块图像之间没有间隙。更艺术的表现形式,是在每个小块图像之间流出几个像素的均匀间隙。如何创建这样的间隙(提示:在计算最终的图像尺寸以及在 createImageGrid() 中粘贴时,考虑间隙的因素)?
程序的大部分时间,用于从输入文件夹中寻找小块图像的最佳匹配。为了加快程序, getBestMatchIndex() 需要运行得更快。这儿方法是对平均值(看成三维的点)列表进行简单的线性搜索。这个任务的一般问题就是最近邻居搜索。找到最近点有一种特别有效的方法,即 K-D 树搜索。 SciPy 库有一个方便的类 scipy.spatial.KDTree,可以创建 K-D 并向它查询最近点的匹配。请尝试用 SciPy 的 K-D 树替代线性搜索(参见http://docs.scipy.org/doc/scipy/reference/generated/ scipy.spatial.KDTree.html)
更多项目细节和完整代码,请点击阅读原文进入实验楼课程观看 :https://www.shiyanlou.com/courses/1041。学编程,敲一边胜过看10遍,欢迎大家来实验楼敲出这个项目~
备受好评的 楼+「 Python实战 」、「 Linux运维与Devops实战 」正在优惠报名中——
实验楼CEO、CTO、高级工程师亲自上阵,通过直播、录播、全程助教、作业挑战等方式,带你12周内打通Python、Linux的任督二脉,成为拥有真正工作能力的IT工程师!
点击下面的链接了解详情:
楼+ Python实战·第6期 开战在即,超多福利等着你!
三个月打造全能的Linux运维工程师——「Linux运维与DevOps实战」