先上效果图

这是原图:

思路:

拼图的原理其实很简单,就是把原图划分成很多个小块,然后根据灰度或者rgb搜索图库中最相似的图片进行替换。接下来的问题就是如何实现图片搜索。这里可以参考阮一峰的博客

上代码:

第一步:获取目标图片的尺寸,计算每个子图的大小。例如:目标图片的尺寸为1600x1280,计算出这个尺寸的最大公约数为320,即拼出的图片由每行每列都有320张小图组成,这样计算出的小图尺寸则为5x4。但这个尺寸太小,所以设置一个min_unit,用来确定最终的小图片的尺寸,若min_unit=5,则每张小图片的尺寸为25x20,相应的每行每列最终的图片数也会变化。(注:代码中的flag用来处理目标图片尺寸无法计算出合理的数值的情况,这时候需要自定义一个图片尺寸)

    def divide_sub_im(self, width, height):flag = Trueg = self.gcd(width, height)if g < 20:flag = Falsewidth = self.__default_wheight = self.__default_hg = 320self.__sub_width = self.__min_unit * (width // g)self.__sub_height = self.__min_unit * (height // g)return flag# 辗转相除法求最大公约数@staticmethoddef gcd(a, b):while a % b:a, b = b, a % breturn b

第二步:读取图库,参数分别是图库的路径和上一步确定的小图片的尺寸(长,宽)。根据这个尺寸对图库的所有图片进行resize,方便之后的图片填充

    def read_all_img(self, db_path, fin_w, fin_h):files_name = os.listdir(db_path)n = 1for file_name in files_name:full_path = db_path + "\\" + file_nameif os.path.isfile(full_path):print("开始读取第%d张图片" % n)# threading.Thread(target=self.read_img, args=(full_path, fin_w, fin_h)).start()cur = Image.open(full_path)# 计算key值(灰度值,平均RGB,hash值,三选一)key = self.cal_key(cur)# 将素材缩放到目标大小cur = cur.resize((fin_w, fin_h), Image.ANTIALIAS)self.__all_img.update({key: cur})n += 1

第三步:计算图库中每张图片的key值,这里实现了三种模式。1:基于图片灰度计算出来的key值。2:基于图片平均RGB计算出来的key值。(效果图使用这种方式)3:基于感知哈希算法(Perceptual hash algorithm)计算出来的key值。最后将每张图片计算出来的key和Image对象保存在dict中,这个key值用来找出最适合的子图。

    def cal_key(self, im):if self.__mode == "RGB":return self.cal_avg_rgb(im)elif self.__mode == "gray":return self.cal_gray(im)elif self.__mode == "hash":return self.cal_hash(im)else:return ""# 计算灰度值@staticmethoddef cal_gray(im):if im.mode != "L":im = im.convert("L")return reduce(lambda x, y: x + y, im.getdata()) // (im.size[0] * im.size[1])# 计算平均rgb值@staticmethoddef cal_avg_rgb(im):if im.mode != "RGB":im = im.convert("RGB")pix = im.load()avg_r, avg_g, avg_b = 0, 0, 0n = 1for i in range(im.size[0]):for j in range(im.size[1]):r, g, b = pix[i, j]avg_r += ravg_g += gavg_b += bn += 1avg_r /= navg_g /= navg_b /= nreturn str(avg_r) + "-" + str(avg_g) + "-" + str(avg_b)# 计算pHashdef cal_hash(self, im):im = im.resize((8, 8), Image.ANTIALIAS)im = im.convert("L")avg_gray = self.cal_gray(im)k = ""_0 = "0"_1 = "1"for i in im.getdata():if i < avg_gray:k += _0else:k += _1return k

第四步:开始拼图。遍历整个大图,利用之前计算的子图尺寸将大图分为若干个小图,计算每个小图的key值,然后在图库中搜索最相似的图片,然后将图库中搜索的结果填充到新图片中。

    def core(self, aim_im, width, height):new_im = Image.new("RGB", (width, height))# 每行每列的图片数w = width // self.__sub_widthprint("源文件尺寸为:(w:%d  h:%d)" % (width, height))print("子图的尺寸为:(w:%d  h:%d)" % (self.__sub_width, self.__sub_height))print("w:%d" % w)print("开始拼图,请稍等...")start = time.time()n = 1for i in range(w):for j in range(w):print("正在拼第%d张素材" % n)left = i * self.__sub_widthup = j * self.__sub_heightright = (i + 1) * self.__sub_widthdown = (j + 1) * self.__sub_heightbox = (left, up, right, down)cur_sub_im = aim_im.crop(box)# 计算key值(灰度值,平均RGB,hash值,三选一)cur_sub_key = self.cal_key(cur_sub_im)# 搜索最匹配图片(灰度值,平均RGB,hash值,三选一)fit_sub = self.find_key(cur_sub_key)new_im.paste(fit_sub, box)n += 1print("拼图完成,共耗时%f秒" % (time.time() - start))new_im.save(self.__out_path)

完整代码:

参数用途:

db_path:图库目录

aim_path:目标图片路径

out_path:生成的图片的输出路径

sub_width=64:子图的尺寸(默认64,可自己更改)

sub_height=64:

min_unit=2:可理解成粒度,值越小拼出的图片越精细,每个子图也越小

mode="RGB":拼图方式,默认RGB

default_w=1600:默认生成的图片尺寸,只在无法计算有效合理的最大公约数时有效
 default_h=1280

import os
import time
from functools import reduce
from threading import Thread
from PIL import Imageclass MosaicMaker(object):# 内部类,执行多线程拼图的任务类class __SubTask:def __init__(self, n, cur_sub_im, new_im, m, box):self.n = nself.cur_sub_im = cur_sub_imself.new_im = new_imself.m = mself.box = boxdef work(self):# print("正在拼第%d张素材" % self.n)# 计算key值(灰度值,平均RGB,hash值,三选一)cur_sub_key = self.m.cal_key(self.cur_sub_im)# 搜索最匹配图片(灰度值,平均RGB,hash值,三选一)fit_sub = self.m.find_key(cur_sub_key)self.new_im.paste(fit_sub, self.box)# 内部类,执行多线程读取图库的任务类class __ReadTask:def __init__(self, n, full_path, fin_w, fin_h, m):self.n = nself.full_path = full_pathself.fin_w = fin_wself.fin_h = fin_hself.m = mdef read(self):print("开始读取第%d张图片" % self.n)cur = Image.open(self.full_path)# 计算key值(灰度值,平均RGB,hash值,三选一)key = self.m.cal_key(cur)# 将素材缩放到目标大小cur = cur.resize((self.fin_w, self.fin_h), Image.ANTIALIAS)self.m.get_all_img().update({key: cur})# 图库目录 目标文件 输出路径 子图尺寸 最小像素单位 拼图模式 默认尺寸def __init__(self, db_path, aim_path, out_path, sub_width=64, sub_height=64, min_unit=5, mode="RGB", default_w=1600,default_h=1280):self.__db_path = db_pathself.__aim_path = aim_pathself.__out_path = out_pathself.__sub_width = sub_widthself.__sub_height = sub_heightself.__min_unit = min_unitself.__mode = modeself.__default_w = default_wself.__default_h = default_hself.__all_img = dict()# 对外提供的接口def make(self):aim_im = Image.open(self.__aim_path)aim_width = aim_im.size[0]aim_height = aim_im.size[1]print("计算子图尺寸")if not self.__divide_sub_im(aim_width, aim_height):print("使用默认尺寸")aim_im = aim_im.resize((self.__default_w, self.__default_h), Image.ANTIALIAS)aim_width = aim_im.size[0]aim_height = aim_im.size[1]print("读取图库")start = time.time()self.__read_all_img(self.__db_path, self.__sub_width, self.__sub_height)print("耗时:%f秒" % (time.time() - start))self.__core(aim_im, aim_width, aim_height)def __core(self, aim_im, width, height):new_im = Image.new("RGB", (width, height))# 每行每列的图片数w = width // self.__sub_widthprint("源文件尺寸为:(w:%d  h:%d)" % (width, height))print("子图的尺寸为:(w:%d  h:%d)" % (self.__sub_width, self.__sub_height))print("w:%d" % w)print("开始拼图,请稍等...")start = time.time()n = 1thread_list = list()for i in range(w):task_list = list()for j in range(w):# 多线程版left = i * self.__sub_widthup = j * self.__sub_heightright = (i + 1) * self.__sub_widthdown = (j + 1) * self.__sub_heightbox = (left, up, right, down)cur_sub_im = aim_im.crop(box)t = self.__SubTask(n, cur_sub_im, new_im, self, box)task_list.append(t)n += 1thread = Thread(target=self.__sub_mission, args=(task_list,))thread_list.append(thread)for t in thread_list:t.start()for t in thread_list:t.join()print("拼图完成,共耗时%f秒" % (time.time() - start))# 将原图与拼图合并,提升观感new_im = Image.blend(new_im, aim_im, 0.35)new_im.show()new_im.save(self.__out_path)# 拼图库线程执行的具体函数@staticmethoddef __sub_mission(missions):for task in missions:task.work()# 计算子图大小def __divide_sub_im(self, width, height):flag = Trueg = self.__gcd(width, height)if g < 20:flag = Falsewidth = self.__default_wheight = self.__default_hg = 320if g == width:g = 320self.__sub_width = self.__min_unit * (width // g)self.__sub_height = self.__min_unit * (height // g)return flag# 读取全部图片,按(灰度值,平均RGB,hash值)保存 fin_w,fin_h素材最终尺寸def __read_all_img(self, db_path, fin_w, fin_h):files_name = os.listdir(db_path)n = 1# 开启5个线程加载图片ts = list()for i in range(5):ts.append(list())for file_name in files_name:full_path = db_path + "\\" + file_nameif os.path.isfile(full_path):read_task = self.__ReadTask(n, full_path, fin_w, fin_h, self)ts[n % 5].append(read_task)n += 1tmp = list()for i in ts:t = Thread(target=self.__read_img, args=(i,))t.start()tmp.append(t)for t in tmp:t.join()# 读取图库线程执行的具体函数@staticmethoddef __read_img(tasks):for task in tasks:task.read()# 计算key值def cal_key(self, im):if self.__mode == "RGB":return self.__cal_avg_rgb(im)elif self.__mode == "gray":return self.__cal_gray(im)elif self.__mode == "hash":return self.__cal_hash(im)else:return ""# 获取key值def find_key(self, im):if self.__mode == "RGB":return self.__find_by_rgb(im)elif self.__mode == "gray":return self.__find_by_gray(im)elif self.__mode == "hash":return self.__find_by_hash(im)else:return ""# 计算灰度值@staticmethoddef __cal_gray(im):if im.mode != "L":im = im.convert("L")return reduce(lambda x, y: x + y, im.getdata()) // (im.size[0] * im.size[1])# 计算平均rgb值@staticmethoddef __cal_avg_rgb(im):if im.mode != "RGB":im = im.convert("RGB")pix = im.load()avg_r, avg_g, avg_b = 0, 0, 0n = 1for i in range(im.size[0]):for j in range(im.size[1]):r, g, b = pix[i, j]avg_r += ravg_g += gavg_b += bn += 1avg_r /= navg_g /= navg_b /= nreturn str(avg_r) + "-" + str(avg_g) + "-" + str(avg_b)# 计算hashdef __cal_hash(self, im):im = im.resize((8, 8), Image.ANTIALIAS)im = im.convert("L")avg_gray = self.__cal_gray(im)k = ""_0 = "0"_1 = "1"for i in im.getdata():if i < avg_gray:k += _0else:k += _1return k# 辗转相除法求最大公约数@staticmethoddef __gcd(a, b):while a % b:a, b = b, a % breturn b# 获取最佳素材(按灰度)def __find_by_gray(self, gray):m = 255k = 0for key in self.__all_img.keys():cur_dif = abs(key - gray)if cur_dif < m:k = keym = cur_difreturn self.__all_img[k]# 获取最佳素材(按pHash)def __find_by_hash(self, sub_hash):m = 65k = 0for key in self.__all_img.keys():cur_dif = self.__dif_num(sub_hash, key)if cur_dif < m:k = keym = cur_difreturn self.__all_img[k]@staticmethoddef __dif_num(hash1, hash2):n = 0for i in range(64):if hash1[i] != hash2[i]:n += 1return n# # 获取最佳素材(按平均rgb)def __find_by_rgb(self, sub_rgb):sub_r, sub_g, sub_b = sub_rgb.split("-")m = 255k = ""for key in self.__all_img.keys():src_r, src_g, src_b = key.split("-")cur_dif = abs(float(sub_r) - float(src_r)) + abs(float(sub_g) - float(src_g)) + abs(float(sub_b) - float(src_b))if cur_dif < m:m = cur_difk = keyreturn self.__all_img[k]def get_all_img(self):return self.__all_imgif __name__ == '__main__':m = MosaicMaker("G:\\image", "YUI.jpg","YUI-out-5.jpg")m.make()pass

最后讲一下三种key值的计算。

(一)灰度:使用PIL库的Image.mode可以查看当前图片的mode。常见的有rgb和L。当mode为rgb时Image.load()函数会返回一个三元组,例如(123,245,213)分别表示rgb的值。rgb模式下的灰度值计算公式为:(r*28+g*151+b*77) >> 8。但我在网上没有查到的一致的公式。所以可以用Image.convert()方法将图片转成L模式之后再计算平均灰度值。Image.gatdata()函数可以返回一个图片所有像素的一维数组,方便计算平均灰度。

(二)平均RGB:平均rgb值的计算原理和方法与计算灰度值大同小异,代码描述的应该已经够清楚了,不再赘述

(三)pHash:感知哈希算法(Perceptual hash algorithm),它的作用是对每张图片生成一个"指纹"(fingerprint)字符串,然后比较不同图片的指纹。结果越接近,就说明图片越相似。这个方法的最佳用途是根据缩略图,找出原图。所以不太适合用于实现马赛克拼图。pHash的计算略微复杂一些。
首先将图片缩小到8x8,即64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。然后计算这64个像素的平均灰度值,计算方法如上所述。之后将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。得到指纹以后,就可以对比不同的图片,看看64位中有多少位是不一样的。如果不相同的数据位不超过5,就说明两张图片很相似;如果大于10,就说明这是两张不同的图片。(详见阮一峰的博客)

使用Python简单实现马赛克拼图相关推荐

  1. opengl实现三维动画简单代码_使用Python简单实现马赛克拼图!内附完整代码

    今天小编带大家使用python简单实现马赛克拼图,内容比以往会稍长一些,各位看官老爷可以慢慢细读,若有不足之处还望请斧正,闲话不多说,请看文章. 先看原图: 效果图: 思路: 拼图的原理其实很简单,就 ...

  2. 炫酷!200 行 Python 代码实现马赛克拼图!

    在一图胜千言的时代,没有什么比一张图片更有冲击力的了,那如果一千张图片拼接起来是什么效果呢? 别问,问就是两字 -- 炫酷! 你有没有想过上面的图片是怎么实现的,难道这是用 ps 一张张拼起来的?当然 ...

  3. 用Java实现简单的“马赛克拼图”

    先来一张效果图 这些头像都来自微信好友的,放大看这不过是一张众多头像拼成的大图,缩小或远看能够发现这些头像其实拼出了一个有趣的图案.这个实现思路并不复杂(这个思路暂时只针对黑白的图片,如果要支持彩图会 ...

  4. python 马赛克拼图_使用 python 做到马赛克拼图

    死宅一枚.爬取5000张二次元妹子的图片,生成了头图. 接下来看看怎么实现的: 使用 Scrapy 框架爬取5000张二次元图 使用 opencv 批量格式化图片 将图片按照RGB值的均方根排序,实现 ...

  5. 马赛克 拼图 python_使用Python的马赛克艺术。

    马赛克 拼图 python In my previous article, I have explored an interesting format of representing images c ...

  6. 利用python爬虫爬取图片并且制作马赛克拼图

    想在妹子生日送妹子一张用零食(或者食物类好看的图片)拼成的马赛克拼图,因此探索了一番= =. 首先需要一个软件来制作马赛克拼图,这里使用Foto-Mosaik-Edda(网上也有在线制作的网站,但是我 ...

  7. python open cv 图片对比_用几十万张图片来拼图!Open CV牛逼不是没有道理的!马赛克拼图...

    这是最终得到的效果,如果你的图片集不同,或者参数设置不同,效果也会有差别. 进群:548377875即可获取数十套pdf哦!源码就不分享给大家了! 1,收集图片素材 要做出上述的效果来,首先就需要大量 ...

  8. 【Python3.6爬虫学习记录】(十五)Scrapy爬虫框架的应用及马赛克拼图生成

    目录 目录 前言 1.Scrapy框架应用 1.1.Scrapy准备 1.2.创建项目及配置 1.3.网页分析及代码实现 1.3.1 items.py 中定义存储的数据 1.3.2 spiders文件 ...

  9. 用Java实现的简易马赛克拼图

    用Java实现的简易马赛克拼图 什么是马赛克拼图 效果图 原理 所有代码 什么是马赛克拼图 马赛克拼图 简单来说就是远远看上去是一张大图,放大之后会发现其实是由许多张不同的小图组成 效果图 如果准备的 ...

最新文章

  1. rdp连接工具_如何在Windows10中清除RDP连接历史记录?
  2. 润乾V5手机报表说明文档
  3. mysql 创建端口号_MySQL命令行 不同端口登录 执行SQL文件 创建用户 赋予权限 修改root密码...
  4. 多维列表索引_10分钟带你学会Pandas多层级索引
  5. mysql 主键 下一个值_INNODB自增主键的一些问题 vs mysql获得自增字段下一个值
  6. yii2中的rules 自定义验证规则详解
  7. 递归学习 斐波那契 java代码实现
  8. 刚刚,Python内幕被爆出!老码农:没控制住,心态已崩!
  9. 32 SD配置-合作伙伴确认-设置客户主数据的合作伙伴确定
  10. python 升级服务器_开发服务器之升级到Python2.7
  11. 集合php,php function集合
  12. 8位数控分频器的设计_数控分频器的设计实验报告
  13. 如何使用谷歌“以图找图”图片搜索功能
  14. [armv9]-PAC:Pointer authentication和BTI:Branch target instructions介绍
  15. vlookup函数和vlookup函数与数据有效性
  16. 华为机式(矩阵相乘)
  17. 利用华硕路由器实现创维电视广告屏蔽
  18. 计算机维修工文明操作,初级计算机维修工操作题.doc
  19. 强迫症 之 Android Studio 格式化 XML
  20. MinGW和GCC所有版本下载地址

热门文章

  1. QIIME 2 2019.7 更新
  2. jsp+servlet学子商城项目--servlet、dao层的各项练习
  3. iOS XCode 解决 Showing Recent Messages :-1: Unable to load contents of file list
  4. 【高考】人生的第一次转折
  5. 离散傅立叶变换与逆变换
  6. HTML5兼容HEVC视频格式且支持本地绝对路径访问
  7. LAMP兄弟连PHP开源框架免费在线课报名中
  8. 学用ORACLE AWR和ASH特性(4)-生成指定SQL的统计报表
  9. Linux学习笔记 16(存储设备管理)
  10. ue4远程服务器xcode,UE4 使用Xcode真机调试的方法