使用Python简单实现马赛克拼图
先上效果图
这是原图:
思路:
拼图的原理其实很简单,就是把原图划分成很多个小块,然后根据灰度或者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简单实现马赛克拼图相关推荐
- opengl实现三维动画简单代码_使用Python简单实现马赛克拼图!内附完整代码
今天小编带大家使用python简单实现马赛克拼图,内容比以往会稍长一些,各位看官老爷可以慢慢细读,若有不足之处还望请斧正,闲话不多说,请看文章. 先看原图: 效果图: 思路: 拼图的原理其实很简单,就 ...
- 炫酷!200 行 Python 代码实现马赛克拼图!
在一图胜千言的时代,没有什么比一张图片更有冲击力的了,那如果一千张图片拼接起来是什么效果呢? 别问,问就是两字 -- 炫酷! 你有没有想过上面的图片是怎么实现的,难道这是用 ps 一张张拼起来的?当然 ...
- 用Java实现简单的“马赛克拼图”
先来一张效果图 这些头像都来自微信好友的,放大看这不过是一张众多头像拼成的大图,缩小或远看能够发现这些头像其实拼出了一个有趣的图案.这个实现思路并不复杂(这个思路暂时只针对黑白的图片,如果要支持彩图会 ...
- python 马赛克拼图_使用 python 做到马赛克拼图
死宅一枚.爬取5000张二次元妹子的图片,生成了头图. 接下来看看怎么实现的: 使用 Scrapy 框架爬取5000张二次元图 使用 opencv 批量格式化图片 将图片按照RGB值的均方根排序,实现 ...
- 马赛克 拼图 python_使用Python的马赛克艺术。
马赛克 拼图 python In my previous article, I have explored an interesting format of representing images c ...
- 利用python爬虫爬取图片并且制作马赛克拼图
想在妹子生日送妹子一张用零食(或者食物类好看的图片)拼成的马赛克拼图,因此探索了一番= =. 首先需要一个软件来制作马赛克拼图,这里使用Foto-Mosaik-Edda(网上也有在线制作的网站,但是我 ...
- python open cv 图片对比_用几十万张图片来拼图!Open CV牛逼不是没有道理的!马赛克拼图...
这是最终得到的效果,如果你的图片集不同,或者参数设置不同,效果也会有差别. 进群:548377875即可获取数十套pdf哦!源码就不分享给大家了! 1,收集图片素材 要做出上述的效果来,首先就需要大量 ...
- 【Python3.6爬虫学习记录】(十五)Scrapy爬虫框架的应用及马赛克拼图生成
目录 目录 前言 1.Scrapy框架应用 1.1.Scrapy准备 1.2.创建项目及配置 1.3.网页分析及代码实现 1.3.1 items.py 中定义存储的数据 1.3.2 spiders文件 ...
- 用Java实现的简易马赛克拼图
用Java实现的简易马赛克拼图 什么是马赛克拼图 效果图 原理 所有代码 什么是马赛克拼图 马赛克拼图 简单来说就是远远看上去是一张大图,放大之后会发现其实是由许多张不同的小图组成 效果图 如果准备的 ...
最新文章
- rdp连接工具_如何在Windows10中清除RDP连接历史记录?
- 润乾V5手机报表说明文档
- mysql 创建端口号_MySQL命令行 不同端口登录 执行SQL文件 创建用户 赋予权限 修改root密码...
- 多维列表索引_10分钟带你学会Pandas多层级索引
- mysql 主键 下一个值_INNODB自增主键的一些问题 vs mysql获得自增字段下一个值
- yii2中的rules 自定义验证规则详解
- 递归学习 斐波那契 java代码实现
- 刚刚,Python内幕被爆出!老码农:没控制住,心态已崩!
- 32 SD配置-合作伙伴确认-设置客户主数据的合作伙伴确定
- python 升级服务器_开发服务器之升级到Python2.7
- 集合php,php function集合
- 8位数控分频器的设计_数控分频器的设计实验报告
- 如何使用谷歌“以图找图”图片搜索功能
- [armv9]-PAC:Pointer authentication和BTI:Branch target instructions介绍
- vlookup函数和vlookup函数与数据有效性
- 华为机式(矩阵相乘)
- 利用华硕路由器实现创维电视广告屏蔽
- 计算机维修工文明操作,初级计算机维修工操作题.doc
- 强迫症 之 Android Studio 格式化 XML
- MinGW和GCC所有版本下载地址
热门文章
- QIIME 2 2019.7 更新
- jsp+servlet学子商城项目--servlet、dao层的各项练习
- iOS XCode 解决 Showing Recent Messages :-1: Unable to load contents of file list
- 【高考】人生的第一次转折
- 离散傅立叶变换与逆变换
- HTML5兼容HEVC视频格式且支持本地绝对路径访问
- LAMP兄弟连PHP开源框架免费在线课报名中
- 学用ORACLE AWR和ASH特性(4)-生成指定SQL的统计报表
- Linux学习笔记 16(存储设备管理)
- ue4远程服务器xcode,UE4 使用Xcode真机调试的方法