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

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

先看原图:

682dfb37a796c92c504a8725b9c5f4e4.png

效果图:

ffbf242942b9ea905dbbf3de4632b7fe.png

思路:

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

阮一峰博客:http://www.ruanyifeng.com/blog/2011/07/principle_of_similar_image_search.html

代码:

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

  def divide_sub_im(self, width, height):        flag = True        g = self.gcd(width, height)        if g < 20:            flag = False            width = self.__default_w            height = self.__default_h            g = 320        self.__sub_width = self.__min_unit * (width // g)        self.__sub_height = self.__min_unit * (height // g)        return flag      # 辗转相除法求最大公约数    @staticmethod    def gcd(a, b):        while a % b:            a, b = b, a % b        return b

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

    def read_all_img(self, db_path, fin_w, fin_h):        files_name = os.listdir(db_path)        n = 1        for file_name in files_name:            full_path = db_path + "" + file_name            if 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 ""      # 计算灰度值    @staticmethod    def 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值    @staticmethod    def cal_avg_rgb(im):        if im.mode != "RGB":            im = im.convert("RGB")        pix = im.load()        avg_r, avg_g, avg_b = 0, 0, 0        n = 1        for i in range(im.size[0]):            for j in range(im.size[1]):                r, g, b = pix[i, j]                avg_r += r                avg_g += g                avg_b += b                n += 1        avg_r /= n        avg_g /= n        avg_b /= n        return str(avg_r) + "-" + str(avg_g) + "-" + str(avg_b)     # 计算pHash    def 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 += _0            else:                k += _1        return k

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

    def core(self, aim_im, width, height):        new_im = Image.new("RGB", (width, height))        # 每行每列的图片数        w = width // self.__sub_width        print("源文件尺寸为:(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 = 1        for i in range(w):            for j in range(w):                print("正在拼第%d张素材" % n)                left = i * self.__sub_width                up = j * self.__sub_height                right = (i + 1) * self.__sub_width                down = (j + 1) * self.__sub_height                box = (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 += 1        print("拼图完成,共耗时%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 osimport timefrom functools import reducefrom threading import Threadfrom PIL import Image  class MosaicMaker(object):    # 内部类,执行多线程拼图的任务类    class __SubTask:        def __init__(self, n, cur_sub_im, new_im, m, box):            self.n = n            self.cur_sub_im = cur_sub_im            self.new_im = new_im            self.m = m            self.box = box         def 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 = n            self.full_path = full_path            self.fin_w = fin_w            self.fin_h = fin_h            self.m = m         def 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_path        self.__aim_path = aim_path        self.__out_path = out_path        self.__sub_width = sub_width        self.__sub_height = sub_height        self.__min_unit = min_unit        self.__mode = mode        self.__default_w = default_w        self.__default_h = default_h        self.__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_width        print("源文件尺寸为:(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 = 1        thread_list = list()        for i in range(w):            task_list = list()            for j in range(w):                # 多线程版                left = i * self.__sub_width                up = j * self.__sub_height                right = (i + 1) * self.__sub_width                down = (j + 1) * self.__sub_height                box = (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 += 1            thread = 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)     # 拼图库线程执行的具体函数    @staticmethod    def __sub_mission(missions):        for task in missions:            task.work()     # 计算子图大小    def __divide_sub_im(self, width, height):        flag = True        g = self.__gcd(width, height)        if g < 20:            flag = False            width = self.__default_w            height = self.__default_h            g = 320         if g == width:            g = 320        self.__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_name            if os.path.isfile(full_path):                read_task = self.__ReadTask(n, full_path, fin_w, fin_h, self)                ts[n % 5].append(read_task)                n += 1        tmp = list()        for i in ts:            t = Thread(target=self.__read_img, args=(i,))            t.start()            tmp.append(t)        for t in tmp:            t.join()     # 读取图库线程执行的具体函数    @staticmethod    def __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 ""     # 计算灰度值    @staticmethod    def __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值    @staticmethod    def __cal_avg_rgb(im):        if im.mode != "RGB":            im = im.convert("RGB")        pix = im.load()        avg_r, avg_g, avg_b = 0, 0, 0        n = 1        for i in range(im.size[0]):            for j in range(im.size[1]):                r, g, b = pix[i, j]                avg_r += r                avg_g += g                avg_b += b                n += 1        avg_r /= n        avg_g /= n        avg_b /= n        return str(avg_r) + "-" + str(avg_g) + "-" + str(avg_b)     # 计算hash    def __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 += _0            else:                k += _1        return k     # 辗转相除法求最大公约数    @staticmethod    def __gcd(a, b):        while a % b:            a, b = b, a % b        return b     # 获取最佳素材(按灰度)    def __find_by_gray(self, gray):        m = 255        k = 0        for key in self.__all_img.keys():            cur_dif = abs(key - gray)            if cur_dif < m:                k = key                m = cur_dif        return self.__all_img[k]     # 获取最佳素材(按pHash)    def __find_by_hash(self, sub_hash):        m = 65        k = 0        for key in self.__all_img.keys():            cur_dif = self.__dif_num(sub_hash, key)            if cur_dif < m:                k = key                m = cur_dif        return self.__all_img[k]     @staticmethod    def __dif_num(hash1, hash2):        n = 0        for i in range(64):            if hash1[i] != hash2[i]:                n += 1        return n     # # 获取最佳素材(按平均rgb)    def __find_by_rgb(self, sub_rgb):        sub_r, sub_g, sub_b = sub_rgb.split("-")        m = 255        k = ""        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_dif                k = key        return self.__all_img[k]     def get_all_img(self):        return self.__all_img  if __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开发工程师,这里有我自己整理了一套最新的python系统学习教程,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。想要这些资料的可以关注小编,并在后台私信小编:“01”即可领取。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558475.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

耳机不支持android,安卓手机为什么不能用EarPods耳机 原因分析【图解】

相信很多人都有这样的疑问&#xff0c;iPhone和安卓手机的耳机孔是一样的&#xff0c; 安卓手机为什么不能用EarPods耳机? 这是什么原因&#xff1f;本次在这里就给大家分析下。安卓手机为什么不能用EarPods耳机?目前市面上大多数的手机仍采用3.5mm的耳机接口。但很多人不知道…

python自然语言处理库_Python 自然语言处理(NLP)工具库汇总

最近正在用nltk 对中文网络商品评论进行褒贬情感分类&#xff0c;计算评论的信息熵&#xff08;entropy&#xff09;、互信息&#xff08;point mutual information&#xff09;和困惑值&#xff08;perplexity&#xff09;等&#xff08;不过这些概念我其实也还理解不深...只是…

android代码清除锁屏密码,清除Android手机锁屏密码的三个小妙招

大家有没有把锁屏密码忘记过&#xff1f;发生这种情况的概率应该很小吧&#xff0c;但为了以防万一大家还是看一下本文的解锁教程吧&#xff01;这种解锁的方法要求&#xff0c;被锁上的手机是联网的状态&#xff0c;并登录了google账号&#xff0c;账号密码已知。如果以上条件…

c语言数组最大可定义多少位_C语言求数组的最大值三种方法

/* 黄哥Python培训 黄哥所写*/#include int maxValue(int* arr, int n);int maxRecursionValue(int* arr, int n);int maxDividAndConquerValue(int* arr, int left, int right);int main(void) { int arr[] {500, -1, 30, 7, 99, 12}; printf("数组中的元素最大…

android 百度map 一个layout加载多个mapview,android 百度地图API 使用Marker和InfoWindow

前言&#xff1a;在android开发过程中&#xff0c;百度地图的使用是比较普遍的&#xff0c;但是如何使用&#xff0c;使用什么版本的百度API还是需要一些讲究。在项目过程中&#xff0c;需要用到百度地图的marker和InfoWindow的功能。标注覆盖物(百度地图官方图)布局文件很简单…

python数据分析实验报告_Python 数据分析入门实战

本训练营中&#xff0c;我们将学习怎么样使用 Python 进行数据分析。课程将从数据分析基础开始&#xff0c;一步步深入讲解。从 Python 的基础用法到数据分析的各种算法&#xff0c;并结合各种实例&#xff0c;讲解数据分析过程中的方方面面。 课程内容将分为以下四个部分&…

go 写文件_如何在 Ubuntu 20.04 上安装 Go

本文最先发布在&#xff1a;如何在 Ubuntu 20.04 上安装 Go​www.itcoder.techGo&#xff0c;通常被称为 golang&#xff0c;它是一门由 Google 创建的现代化的开源编程语言&#xff0c;它允许你构建实时并且高效的应用。很多流行的应用程序&#xff0c;例如 Kubernetes&#x…

java汽车管理系统_坑爹!花费2亿耗时2年,网站没建完Java都写不好,顶级咨询公司埃森哲被告上法庭...

乾明 发自 凹非寺 量子位 报道 | 公众号 QbitA耗费2个多亿&#xff0c;耗时2年多&#xff0c;连一个可用的网站或者APP都没有交付出来。想要完工&#xff1f;那就再交1000万美元。这件事的受害方、美国汽车租赁公司赫兹(Hertz)一怒之下&#xff0c; 将顶级咨询公司埃森哲(Accen…

Android接口一般定义格式,Android开发规范

原标题&#xff1a;Android开发规范一.书写规范1. 编码方式统一用UTF-8.2. 花括号不要单独一行&#xff0c;和它前面的代码同一行。而且&#xff0c;花括号与前面的代码之间用一个空格隔开。3. 空格的使用if、else、for、switch、while等逻辑关键字与后面的语句留一个空格隔开。…

c++将小写转换为大写函数_必须掌握的基础函数组合应用技巧,提高效率,准时下班...

点击上方"Excel函数公式"免费订阅货币&#xff0c;生活中必不可少的东西&#xff0c;是物品价值等的直接体现&#xff0c;在实际的工作中也经常遇到&#xff0c;如果给定的数据中&#xff0c;要对其进行格式的设置&#xff0c;你会怎么做&#xff1f;一、Dollar函数&…

jenkins使用哪个版本号_Linux下安装JDK及jenkins

往期相关文章推荐&#xff1a;Linux ping不通域名安装JDK依赖(8/11)一.(推荐)// 查看yum仓库中可安装的jdk版本yum -y list java*// 安装示例yum install -y java-1.8.0-openjdk-devel.x86_64java --version 二.1.获取JDK安装包&#xff0c;可以win下下载&#xff0c;再用scp…

骁龙660鸿蒙系统,骁龙660双摄测试机偷跑 核心数/GPU证实

中关村在线消息&#xff1a;高通会在今年推出一款全新的中端处理器——骁龙660。此前有网友在微博上曝光一组疑似骁龙660的跑分&#xff0c;安兔兔总成绩为105576分。现在&#xff0c;微博上又出现搭载骁龙660双摄工程机的谍照&#xff0c;该机支持2K分辨率&#xff0c;采用6GB…

tensorflow 模型可视化_基于tensorflow-2.x的yolov3实现

YOLO v3可以说是单阶段检测器中的佼佼者&#xff0c;融合了多个框架的优势&#xff0c;在保持模型简洁性的同时&#xff0c;性能上也在当时达到了stoa。YOLO v3的主干网络是darknet-53的前面的52层&#xff0c;所以它是一个全卷积网络&#xff0c;并且为了降低池化带来的梯度负…

android闹钟延时,android闹钟定时启动延时或者直接不启动

自己写的android闹钟功能&#xff0c;需要实现timepicker选择完成后将选择的时间设定为闹钟的启动时间&#xff0c;但是不管怎么改总是没法定时启动alertDialog new AlertDialog.Builder(context).setView(view).setCustomTitle(viewTitle).setNegativeButton("确定"…

switch语句可以被代替吗_爬楼梯可以代替跑步吗?

转载&#xff1a;有很多人在下雨天选择爬楼梯作为运动方式&#xff0c;前几天就有人问老王&#xff1a;爬楼梯可以代替跑步吗&#xff1f;爬楼梯是在一个坡度上下移动&#xff0c;上楼梯时&#xff0c;腿部需要承受自身体重1.5-2.5倍的重量&#xff1b;下楼梯时则要承受自身体重…

gsonformat插件_吐血推荐珍藏的IDEA插件

之前给大家推荐了一些我自己常用的VS Code插件&#xff0c;很多同学表示很受用&#xff0c;并私信我说要再推荐一些IDEA插件。作为一名职业Java程序员/业余js开发者&#xff0c;我平时还是用IDEA比较多&#xff0c;所以也确实珍藏了一些IDEA插件。今天就一并分享给大家。在最开…

html城市手机搜索,原生js实现html手机端城市列表索引选择城市

本文实例为大家分享了js实现手机端城市列表索引选择城市的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下html部分&#xff1a;定位城市上海市css部分&#xff1a;*{margin: 0;padding: 0;list-style: none;}html{font-size: 12px;}body {background-color: #f5f5f5;…

html 图片使用scale,缩放:scale() - CSS3 | 绿叶学习网

在CSS3中&#xff0c;我们可以使用transform属性的scale()方法来实现元素的缩放效果。缩放&#xff0c;指的是“缩小”和“放大”的意思。语法&#xff1a;transform: scaleX(x); /*沿X轴方向缩放*/transform: scaleY(y); /*沿Y轴方向缩放*/transform: scale(x, y); /*沿X轴和Y…

dbeaver无法修改表数据_隐藏彩蛋:你知道python有一个内置的数据库吗?

全文共2520字&#xff0c;预计学习时长7分钟如果你是软件开发人员&#xff0c;相信你一定知道甚至曾经使用过一个非常轻量级的数据库——SQLite。它几乎拥有作为一个关系数据库所需的所有功能&#xff0c;而且这些有功能都保存在一个文件中。下面是一些官方网站显示可以使用SQL…

批量删除HTML链接软件,3种方法教你一次性删除word文档中的所有超链接

您可能感兴趣的话题&#xff1a;Word核心提示&#xff1a;在编辑文档时&#xff0c;可能会在文档以外复制一些内容进来&#xff0c;但是总会有一些烦人的链接很难去掉。或是WORD 从网上下了一些资料,存到 word 里面,里面很多文字和图片都带有超链接。下面小编就为大家介绍3种方…