项目内容

该项目用Python创建照片马赛克,将目标图像划分成较小图像的网络,并用适当的图像替换网络中的每一小块,创建原始图像的照片的马赛克

项目知识点

  • 用Python图像库(PIL)创建图像
  • 计算图像的平均RGB值
  • 剪切图像
  • 通过粘贴另一张图像来替代原图像的一部分
  • 利用平均距离测量来比较RGB值

工作原理

照片马赛克的实质:将一张图像分割成长方形的网络,每个长方形由另一张匹配“目标”的图像替代。
创建照片马赛克,从目标图像的块状低分配率版本开始(图像的分配率决定马赛克的维度MXN)。

  1. 读入一些小块图像,它们将取代原始图像中的小块
  2. 读入目标图像,将它们分隔成M*N的小块网络
  3. 对于每个小块,从输入的小块图像中找到最佳匹配
  4. 将选择的输入图像安排在M*N的网络中,创建最终的照片马赛克

1.分割目标图像

  • x 轴表示网格的列,y 轴表示网格的行。
  • 下标为 (i,j) 的小块,左上角坐标为 (iw,ij) ,右下角坐标为 ((i+1)∗w,(j+1)∗h)
  • w 和 h 分别是小块的宽度和高度

2.平均颜色值

图像中的每个像素颜色由红、绿、蓝值表示。若一幅图像共有N个像素,平均RGB(三元组):

计算平均RGB目的:匹配图像小块和目标图像

3.匹配图像

匹配图像:对于目标图像的每个小块,需要在用户指定的输入文件夹的一些图像中,找到一副匹配图像
确定两图像是否匹配:最接近的匹配就是最接近平均的RGB值的图像,故需计算一个像素中的RGB值之间的距离
(三维图像两点之间的距离:)

项目code

  • 读取小块图像
    #os.listdir()方法将imageDir目录中的文件放入一个列表
    #os.path()和os.path.join()获取图像的完整文件名
def getImages(imageDir) :"""given a directory of images, return a list of Images"""files = os.listdir(imageDir)      #os.listdir()方法将imageDir目录中的文件放入一个列表images = []for file in files :   #遍历列表中的每个文件filePath = os.path.abspath(os.path.join(imageDir, file))        #os.path()和os.path.join()获取图像的完整文件名try :# explicit load so we don't run into resource crunchfp = open(filePath, "rb")                       #打开每个小块图像im = Image.open(fp)                     # Image.open()方法将文件句柄fp传入PIL()images.append(im)# force loading image data from fileim.load()#加载# close the filefp.close()                                    #关闭文件句柄并释放系统资源except :# skipprint("Invalid image: %s" % (filePath,))return images
  • 计算输入图像的平均颜色值
def getAverageRGB(image) :"""Given PIL Image, return average value of color as (r, g, b)"""# get image as numpy arrayim = np.array(image)            #用nunmpy将每个Image对象转换为数据数组,返回的numpy数组形为(w,h,d)对应(R,G,B)# get shapew, h, d = im.shape                    #保存shape元组,然后计算平均值,reshape():将数组形式变为形状(w * h, d)# get averagereturn tuple(np.average(im.reshape(w * h, d), axis=0))                   #average():计算RGB平均值
  • 将目标图像分割成网络
def splitImage(image, size) :"""Given Image and dims (rows, cols) returns 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 listimgs = []# generate list of dimensionsfor j in range(m) : #迭代遍历网络的维度for i in range(n) :# append cropped imageimgs.append(image.crop((i * w, j * h, (i + 1) * w, (j + 1) * h)))#image.crop():分割并将每一小块保存为单独的图像return imgs
  • 寻找小块的最佳匹配

#img.paste(images[index], (col * width, row * height)):第一个参数要粘贴的对象,第二个参数左上角的坐标


def getBestMatchIndex(input_avg, avgs) :# input_avg:最匹配平均RGB值;avgs:小块图像平均值的RGB列表"""return index of best Image match based on RGB value distance"""# input image averageavg = input_avg# get the closest RGB value to input, based on x/y/z distanceindex = 0min_index = 0#最接近匹配下标初始化为0min_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 = distmin_index = indexindex += 1return min_index
  • 创建图像网络

#img.paste(images[index], (col * width, row * height)):第一个参数要粘贴的对象,第二个参数左上角的坐标

def createImageGrid(images, dims) :"""Given a list of images and a grid size (m, n), createa grid of images."""m, n = dims                 #取得图像尺寸大小,并用assert检车提供给的图像的数量是否符合网络的大小# sanity checkassert m * n == len(images)                   # assert():检查代码中的假定# get max height and width of images# ie, not assuming they are all equalwidth = max([img.size[0] for img in images])  #计算小块图像的最大宽度和高度,若果输出的图像能完全填充小块,显示背景色height = max([img.size[1] for img in images])#否则默认为黑色# create output imagegrid_img = Image.new('RGB', (n * width, m * height)) #创建一个空的Image,大小符合网络中的所有图像# paste imagesfor index in range(len(images)) :#遍历选定的图像(小块在图像网络 中的下表有N*rol+col给出)row = int(index / n)#行col = index - n * row#列grid_img.paste(images[index], (col * width, row * height))#img.paste():粘贴到相应的网络中return grid_img
  • 创建照片马赛克
    #createPhotomosaic(target_image, input_images, grid_size,
    reuse_images=True)
    参数:目标图像、输入图像列表、生成照片马赛克的大、表明图像是否可以复用的标志
def createPhotomosaic(target_image, input_images, grid_size,reuse_images=True) :"""Creates photomosaic given target and input images."""print('splitting input image...')# split target imagetarget_images = splitImage(target_image, grid_size)#将目标图像分隔成一个网络print('finding image matches...')# for each target image, pick one from inputoutput_images = []#对每个小块,寻找匹配的图像# for user feedbackcount = 0batch_size = int(len(target_images) / 10)# batch_size设置为小块总数的1/10# calculate input image averagesavgs = []for img in input_images :#为文件夹中的每个图像计算平均RGB值,并保存在 avgs 列表中avgs.append(getAverageRGB(img))for img in target_images :#迭代目标图像网络中的平均RGB值,并将值保存在avg# target sub-image averageavg = getAverageRGB(img)# find match index #在输入图像的平均值列表中寻找最佳匹配match_index = getBestMatchIndex(avg, avgs)#返回的是个下标output_images.append(input_images[match_index])#取Images对象并保存在列表# 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 selected image from input if flag set 判断图像是否可以复用if not reuse_images :input_images.remove(match)print('creating mosaic...')# draw mosaic to image 创建最终的照片马赛克mosaic_image = createImageGrid(output_images, grid_size)# return mosaicreturn mosaic_image
  • 添加命令行选项
    #此段代码包含3个必要的命令行参数:目标图像的名称、输入文件夹的名称、网络的尺寸//4:可选的文件名
 # parse argumentsparser = argparse.ArgumentParser(description='Creates a photomosaic from input images')# add argumentsparser.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)
  • 控制照片的马赛克大小
    #若基于目标图像中匹配的小块,盲目将图像粘在一起,得到巨大的照片马赛克,比目标图像大得多,故需要控制照片的马赛克大小
 print('resizing images...')# for given grid size, compute max dims w,h 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,))# resizefor img in input_images :img.thumbnail(dims)            #thumbnail()调整图像

完整代码

"""
photomosaic.py
Creates a photomosaic given a target image and a folder of input images
Author: Mahesh Venkitachalam
"""import sys, os, random, argparse
from PIL import Image
import imghdr
import numpy as npdef getAverageRGBOld(image):"""Given PIL Image, return average value of color as (r, g, b)"""# no. of pixels in imagenpixels = image.size[0]*image.size[1]# get colors as [(cnt1, (r1, g1, b1)), ...]cols = image.getcolors(npixels)# get [(c1*r1, c1*g1, c1*g2),...]sumRGB = [(x[0]*x[1][0], x[0]*x[1][1], x[0]*x[1][2]) for x in cols] # calculate (sum(ci*ri)/np, sum(ci*gi)/np, sum(ci*bi)/np)# the zip gives us [(c1*r1, c2*r2, ..), (c1*g1, c1*g2,...)...]avg = tuple([int(sum(x)/npixels) for x in zip(*sumRGB)])return avgdef getAverageRGB(image):"""Given PIL Image, return average value of color as (r, g, b)"""# get image as numpy arrayim = np.array(image)# get shapew,h,d = im.shape# get averagereturn tuple(np.average(im.reshape(w*h, d), axis=0))def splitImage(image, size):"""Given Image and dims (rows, cols) returns an m*n list of Images """W, H = image.size[0], image.size[1]m, n = sizew, h = int(W/n), int(H/m)# image listimgs = []# generate list of dimensionsfor j in range(m):for i in range(n):# append cropped imageimgs.append(image.crop((i*w, j*h, (i+1)*w, (j+1)*h)))return imgsdef getImages(imageDir):"""given a directory of images, return a list of Images"""files = os.listdir(imageDir)images = []for file in files:filePath = os.path.abspath(os.path.join(imageDir, file))try:# explicit load so we don't run into resource crunchfp = open(filePath, "rb")im = Image.open(fp)images.append(im)# force loading image data from fileim.load() # close the filefp.close() except:# skipprint("Invalid image: %s" % (filePath,))return imagesdef getImageFilenames(imageDir):"""given a directory of images, return a list of Image file names"""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:# skipprint("Invalid image: %s" % (filePath,))return filenamesdef getBestMatchIndex(input_avg, avgs):"""return index of best Image match based on RGB value distance"""# input image averageavg = input_avg# get the closest RGB value to input, based on x/y/z distanceindex = 0min_index = 0min_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 = distmin_index = indexindex += 1return min_indexdef createImageGrid(images, dims):"""Given a list of images and a grid size (m, n), create a grid of images. """m, n = dims# sanity checkassert m*n == len(images)# get max height and width of images# ie, not assuming they are all equalwidth = max([img.size[0] for img in images])height = max([img.size[1] for img in images])# create output imagegrid_img = Image.new('RGB', (n*width, m*height))# paste imagesfor index in range(len(images)):row = int(index/n)col = index - n*rowgrid_img.paste(images[index], (col*width, row*height))return grid_imgdef createPhotomosaic(target_image, input_images, grid_size,reuse_images=True):"""Creates photomosaic given target and input images."""print('splitting input image...')# split target image target_images = splitImage(target_image, grid_size)print('finding image matches...')# for each target image, pick one from inputoutput_images = []# for user feedbackcount = 0batch_size = int(len(target_images)/10)# calculate input image averagesavgs = []for img in input_images:avgs.append(getAverageRGB(img))for img in target_images:# target sub-image averageavg = getAverageRGB(img)# find match indexmatch_index = getBestMatchIndex(avg, avgs)output_images.append(input_images[match_index])# user feedbackif count > 0 and batch_size > 10 and count % batch_size is 0:print('processed %d of %d...' %(count, len(target_images)))count += 1# remove selected image from input if flag setif not reuse_images:input_images.remove(match)print('creating mosaic...')# draw mosaic to imagemosaic_image = createImageGrid(output_images, grid_size)# return mosaicreturn mosaic_image# Gather our code in a main() function
def main():# Command line args are in sys.argv[1], sys.argv[2] ..# sys.argv[0] is the script name itself and can be ignored# parse argumentsparser = argparse.ArgumentParser(description='Creates a photomosaic from input images')# add argumentsparser.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()###### INPUTS ####### target imagetarget_image = Image.open(args.target_image)# input imagesprint('reading input folder...')input_images = getImages(args.input_folder)# check if any valid input images found  if input_images == []:print('No input images found in %s. Exiting.' % (args.input_folder, ))exit()# shuffle list - to get a more varied output?random.shuffle(input_images)# size of gridgrid_size = (int(args.grid_size[0]), int(args.grid_size[1]))# outputoutput_filename = 'mosaic.png'if args.outfile:output_filename = args.outfile# re-use any image in inputreuse_images = True# resize the input to fit original image size?resize_input = True##### END INPUTS #####print('starting photomosaic creation...')# if images can't be reused, ensure m*n <= num_of_images if not reuse_images:if grid_size[0]*grid_size[1] > len(input_images):print('grid size less than number of images')exit()# resizing inputif resize_input:print('resizing images...')# for given grid size, compute max dims w,h of tilesdims = (int(target_image.size[0]/grid_size[1]), int(target_image.size[1]/grid_size[0])) print("max tile dims: %s" % (dims,))# resizefor img in input_images:img.thumbnail(dims)# create photomosaicmosaic_image = createPhotomosaic(target_image, input_images, grid_size,reuse_images)# write out mosaicmosaic_image.save(output_filename, 'PNG')print("saved output to %s" % (output_filename,))print('done.')# Standard boilerplate to call the main() function to begin
# the program.
if __name__ == '__main__':

实验素材

所需素材

  • 本实验包括一个 .py 文件和若干图片组成
  • 所有的图片都放置在 test-data 文件夹下
  • test-data/a.jpg是目标图像
  • test-data/set1/ 文件夹下存放的是小块图像

实验效果

运行照片马赛克生成程序

$ python photomosaic.py --target-image test-data/a.jpg --input-folder test-data/set1/ --grid-size 128 128

效果图

原图:

效果图

参考资料

《Python极客项目编程》
github项目代码
实验楼Python创建照片马赛克
NeroChang

附加A——python两种编程方式:

  • 交互式编程——写一行python语句马上运行一行并显示效果

  • 脚本式编程——现在相关的文本文件中,写好相关的python代码,一口气执行

附加B——课后练习

Python小项目—照片马赛克相关推荐

  1. python小项目实例流程-Python小项目:快速开发出一个简单的学生管理系统

    原标题:Python小项目:快速开发出一个简单的学生管理系统 本文根据实际项目中的一部分api 设计抽象出来,实例化成一个简单小例子,暂且叫作「学生管理系统」. 这个系统主要完成下面增删改查的功能: ...

  2. python小项目案例-Python小项目:快速开发出一个简单的学生管理系统

    本文根据实际项目中的一部分api 设计抽象出来,实例化成一个简单小例子,暂且叫作「学生管理系统」. 这个系统主要完成下面增删改查的功能: 包括: 学校信息的管理 教师信息的管理 学生信息的管理 根据A ...

  3. python小项目-python 小项目

    广告关闭 2017年12月,云+社区对外发布,从最开始的技术博客到现在拥有多个社区产品.未来,我们一起乘风破浪,创造无限可能. 事先录制好一段音频,客户接通电话后,自动播放https:blog.csd ...

  4. 五十一、结合百度API接口打造 Python小项目

    @Author: Runsen 本项目围绕图像识别,通过调用百度 API 接口,可以实现很多人性化的功能,比如手势识别.比对.人像分割以及颜值打分等功能. 本次Gitchat付费文章,但是因为订阅太少 ...

  5. python项目开发实例-Python小项目:快速开发出一个简单的学生管理系统

    本文根据实际项目中的一部分api 设计抽象出来,实例化成一个简单小例子,暂且叫作「学生管理系统」. 这个系统主要完成下面增删改查的功能: 包括: 学校信息的管理 教师信息的管理 学生信息的管理 根据A ...

  6. part1:推荐一些适合练手、课程设计、毕业设计的python小项目源码,无任何下载门槛

    人生苦短,我用python,随着python这些年的流行,很多人开始使用python来实现各种功能.下面推荐一些适合用来练手.大学生课程设计作业.大学生毕业设计的python小项目,尤其适合新手,源码 ...

  7. Python 小项目 猜数字小游戏

    欢迎来到<Python 小项目>专栏,这个专栏会不定时更新Python的小项目,大家可以订阅关注哦! 这次,我们要编写一个非常简单的猜数字小游戏! 先看看运行效果: 代码详细教学: 导入模 ...

  8. Python小项目——生成个性二维码

    Python小项目--生成个性二维码 现代社交离不开微信,QQ,那么今天就教你用 Python 生成自己的个性二维码

  9. python小项目,检查生日是否出现在圆周率里面

    python小项目,检查生日是否出现在圆周率里面 file_path_pi = '/home/yecj/works/python/file/0至1000000位圆周率.txt'with open(fi ...

最新文章

  1. 清华学霸花了三年时间对java理解: Java分布式架构
  2. 启明云端分享| sigmastar SSD201/ SSD202D _OTA升级使用参考
  3. WebSocket服务器和客户端的一对多通知实现
  4. linux网络编程之多路I/O转接服务器poll函数
  5. python中的文件处理_python学习——python中的文件处理
  6. 130_传析阅管理系统accdb64位版本
  7. Win2003远程桌面报错:RPC错误 解决办法
  8. windosw启动redis
  9. trunk配置功能详解
  10. [转载] python numpy np.exp()函数
  11. MySQL 8.0 的 5 个新特性,太实用了!
  12. 万用表二极管档和三极管档的使用
  13. excel自动调整列宽_Excel入门:如何设置excel的列宽和行高?
  14. 【2021全国高校计算机能力挑战赛C++题目】17.信息整理 某机房上线了一套系统,和每台计算机都相连,以便监控各计算机相关外设的运行状态。
  15. 前端踩坑(八)前端使用Moment 时间格式化错误
  16. python 身份证号码有效性验证
  17. java dispatcher详解_Java Web开发详解:RequestDispatcher接口
  18. Python 常用的标准库以及第三方库有哪些?
  19. 【总结】反欺诈(Fraud Detection)中所用到的机器学习模型
  20. Python实战技巧系列

热门文章

  1. html5 localstorage 生命周期,cookie、localStorage和sessionStorage 三者之间的区别以及存储、获取、删除等使用方式...
  2. File常用方法,不积硅步无以至千里
  3. 电脑插上扩展坞后会有显示的空盘符
  4. 长期表现决定了最终结果--leo看赢在中国第三季(8)大结局
  5. 百度飞桨PP-YOLOE ONNX 在LabVIEW中的部署推理(含源码)
  6. wps云文档 wps自动备份怎么设置和取消
  7. MVG学习笔记(3) --从多个视角重建
  8. 摆弄教研室的服务器,为教研室写服务器使用指南的时候记录下的一些linux知识
  9. Cas5.3.14手机号码登录(五)
  10. 什么是java的事物