Python制作马赛克图片

  • A+
摘要这一篇会使用Python来制作马赛克图像. 主要包含HSV颜色空间的介绍, 和通过HSV颜色空间来比较图像的相似度. 接着来制作马赛克图像.

简介

有的时候我们会在网上看到类似下面的图片, 下面的图片来自淘宝店, 颜之希旗舰店. 通过若干张图片, 制作一个马赛克图片的效果.

Python制作马赛克图片

这一篇我们来介绍一下如何使用python来实现这样的效果, 使用Python来实现马赛克图片. 这一篇主要参考自基于 hsv 的马赛克拼图效果. 这一部分的文章会分为两个部分进行介绍:

  • 我会首先介绍RGB和HSV颜色空间, 我们会基于HSV颜色空间来比较图像的相似度.
  • 接着给出详细的如何使用python制作马赛克图片的教程.

 

参考资料

 

RGB与HSV颜色空间

RGB颜色空间

RGB是我们接触最多的颜色空间, 分别为红色(R), 绿色(G)和蓝色(B). 这三种颜色的不同组合可以形成几乎所有的其他颜色.

但是在自然环境下, 图像容易受自然光照, 遮挡和阴影等情况的影响, 即对亮度比较敏感. 但是RGB颜色空间的三个向量都与亮度相关, 即如果我们要改变亮度, 这三个量都需要进行改变.

同时, 人眼对于RGB这三种颜色的敏感度是不一样的. 在单色中, 人眼对红色最不敏感, 蓝色最敏感. 所以 RGB 颜色空间是一种均匀性较差的颜色空间.

如果我们在RGB的颜色空间上, 直接使用欧式距离来度量颜色的相似度, 最终的结果会与人眼的结果有较大的差距.

 

HSV颜色空间

正是由于上面讲到的RGB空间的一些问题, 于是在图像处理的时候我们会更多的使用HSV颜色空间.

HSV以人类更熟悉的方式封装了关于颜色的信息:“这是什么颜色?深浅如何?明暗如何?”

HSV 表达彩色图像的方式由三个部分组成:

  • Hue (色调, 色相)
  • Saturation (饱和度, 色彩纯净度)
  • Value (明度)

通常我们会使用下面这个圆柱体来表示 HSV 颜色空间. 圆柱体的横截面可以看做是一个极坐标系, H (Hue) 用极坐标的极角表示, S (Saturation) 用极坐标的极轴长度表示, V (Value) 用圆柱中轴的高度表示.

Python制作马赛克图片

颜色圆环上所有的颜色都是光谱上的颜色, 从红色开始按逆时针方向旋转, Hue=0 表示红色, Hue=120表示绿色, Hue=240 表示蓝色等等.

在RGB中, 颜色由三个值共同决定, 比如黄色为即 (255,255,0); 在HSV中, 黄色只由一个值决定, Hue=60即可.

在确定了Hue之后, 我们可以更改其Saturation和Value. 如下所示:

Python制作马赛克图片
  • Saturation (饱和度): 其中水平方向表示饱和度, 饱和度表示颜色接近光谱色的程度. 饱和度越高, 说明颜色越深, 越接近光谱色; 饱和度越低, 说明颜色越浅, 越接近白色. 饱和度为0表示纯白色. 取值范围为0~100, 值越大, 颜色越饱和. 可以理解为, 饱和度减少, 就是将颜色与白色混合, 饱和度为0, 就全部是白色.
  • Value (明度): 竖直方向表示明度, 决定颜色空间中颜色的明暗程度. 明度越高, 表示颜色越明亮, 范围是 0-100. 明度为0表示纯黑色 (此时颜色最暗). 可以理解为, 明度减少, 就是将颜色与黑色进行混合, 明度是0, 就是全黑的.

我们可以在网站, RGB 轉換 HSV 及 HSL, 进行颜色的在线转换测试. 查看每一个分量的含义. 下面如果把value设置为0, 就是全黑的了.

Python制作马赛克图片

RGB与HSV空间的转换

下面看一下如何在RGB和HSV两个颜色空间之间相互转换. 首先是从RGB到HSV空间的转换. 其中max和min分别是r, g, b三色中最大值和最小值. 这里r, g, b是在0-1之间的数字.

Python制作马赛克图片

下面是一个比较完整的从RGB颜色空间计算到HSV颜色空间的例子.

Python制作马赛克图片

接下来看一下如何从HSV转换到RGB空间.这里h的范围是0-360, s和v的范围是0-1.

Python制作马赛克图片

我们举一个例子来具体说明一下. 对于下图中的第二个颜色, 我们来进行测试.

Python制作马赛克图片

我们使用下面的代码来进行RGB和HSV两个颜色空间的相互转换.

  1. from colorsys import rgb_to_hsv, hsv_to_rgb

首先是从rgb空间转换为hsv的颜色空间. 这里0.333就是120/360.

Python制作马赛克图片

接着是从hsv空间转换为rgb空间. 同样, 再输入h范围的时候是要除360, 使其范围在0-1之间.

Python制作马赛克图片

 

马赛克图片制作

整个马赛克图片制作的流程如下所示:

  • 首先生成素材数据库, 我们将素材图片转换为统一大小, 并计算他的平均HSV值.
  • 接着对需要转换的图片切分为一小块一小块, 对每一块计算他的评价HSV值, 并与素材数据库进行对比, 找出最接近的素材进行替换.
  • 对原图的每一个小块进行替换, 生成一个大图.
  • 将生成的大图与原始图片重合, 融合, 生成最后的图片.
Python制作马赛克图片

 

进行准备-导入需要的库

首先我们导入整个实验中需要用到的库.

  1. import os
  2. from PIL import Image, ImageOps
  3. import time
  4. from multiprocessing import Pool
  5. import random
  6. import math
  7. import sys
  8. from colorsys import rgb_to_hsv, hsv_to_rgb

 

计算图像颜色-平均hsv值

首先, 我们定义一个基类mosaic, 其中包含两个函数:

  • 一个是对图像进行resize的操作;
  • 另一个是计算一个图像的平均hsv值, 之后每一个图像相当于使用一个三个特征的向量进行表示, 我们会通过这个特征来计算图像之间的相似度.
  1. class mosaic(object):
  2.     """定义计算图片的平均hsv值
  3.     """
  4.     def __init__(self, IN_DIR, OUT_DIR, SLICE_SIZE, REPATE, OUT_SIZE):
  5.         self.IN_DIR = IN_DIR # 原始的图像素材所在文件夹
  6.         self.OUT_DIR = OUT_DIR # 输出素材的文件夹, 这些都是计算过hsv和经过resize之后的图像
  7.         self.SLICE_SIZE = SLICE_SIZE # 图像放缩后的大小
  8.         self.REPATE = REPATE # 同一张图片可以重复使用的次数
  9.         self.OUT_SIZE = OUT_SIZE # 最终图片输出的大小
  10.     def resize_pic(self, in_name, size):
  11.         """转换图像大小
  12.         """
  13.         img = Image.open(in_name)
  14.         img = ImageOps.fit(img, (size, size), Image.ANTIALIAS)
  15.         return img
  16.     def get_avg_color(self, img):
  17.         """计算图像的平均hsv
  18.         """
  19.         width, height = img.size
  20.         pixels = img.load()
  21.         if type(pixels) is not int:
  22.             data = [] # 存储图像像素的值
  23.             for x in range(width):
  24.                 for y in range(height):
  25.                     cpixel = pixels[x, y] # 获得每一个像素的值
  26.                     data.append(cpixel)
  27.             h = 0
  28.             s = 0
  29.             v = 0
  30.             count = 0
  31.             for x in range(len(data)):
  32.                 r = data[x][0]
  33.                 g = data[x][1]
  34.                 b = data[x][2] # 得到一个点的GRB三色
  35.                 count += 1
  36.                 hsv = rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)
  37.                 h += hsv[0]
  38.                 s += hsv[1]
  39.                 v += hsv[2]
  40.             hAvg = round(h / count,3)
  41.             sAvg = round(s / count,3)
  42.             vAvg = round(v / count,3)
  43.             if count > 0: # 像素点的个数大于0
  44.                 return (hAvg, sAvg, vAvg)
  45.             else:
  46.                 raise IOError("读取图片数据失败")
  47.         else:
  48.             raise IOError("PIL 读取图片数据失败")

简单说一下如何计算一个图像的平均hsv值:

  • 首先遍历图像的每一个像素点, 获得每一个点的rgb色.
  • 接着使用函数rgb_to_hsv将rgb色转换为hsv的颜色.
  • 最后计算hsv的平均值, 此时一个图像就使用了一个三维的向量表示了.

 

生成素材数据库

为了生成马赛克图片, 我们需要准备一些图像. 我们将图片放在images文件夹内. 接着我们对图片进行大小的转换和计算图像的平均hsv颜色. 整体的代码如下所示:

  1. class create_image_db(mosaic):
  2.     """创建所需要的数据
  3.     """
  4.     def __init__(self, IN_DIR, OUT_DIR, SLICE_SIZE, REPATE, OUT_SIZE):
  5.         super(create_image_db, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE, REPATE, OUT_SIZE)
  6.     def get_image_paths(self):
  7.         """获取文件夹内图像的地址
  8.         """
  9.         paths = []
  10.         suffixs = ['png','jpg']
  11.         for file_ in os.listdir(self.IN_DIR):
  12.             suffix = file_.split('.', 1)[1] # 获得文件后缀
  13.             if suffix in suffixs: # 通过后缀判断是否是图片
  14.                 paths.append(self.IN_DIR + file_) # 添加图像路径
  15.             else:
  16.                 print("非图片:%s" % file_)
  17.         if len(paths) > 0:
  18.             print("一共找到了%s" % len(paths) + "张图片")
  19.         else:
  20.             raise IOError("未找到任何图片")
  21.         return paths
  22.     def convert_image(self, path):
  23.         """转换图像大小, 同时计算一个图像的平均hsv值.
  24.         """
  25.         img = self.resize_pic(path, self.SLICE_SIZE)
  26.         color = self.get_avg_color(img)
  27.         img.save(str(self.OUT_DIR) + str(color) + ".png")
  28.     def convert_all_images(self):
  29.         """将所有图像进行转换
  30.         """
  31.         paths = self.get_image_paths()
  32.         print("正在生成马赛克块...")
  33.         pool = Pool() # 多进程处理
  34.         pool.map(self.convert_image, paths) # 对已有的图像进行处理, 转换为对应的色块
  35.         pool.close()
  36.         pool.join()

上面的整体流程如下:

  • 首先遍历素材的文件夹, 获得所有image的地址;
  • 对每一个image, 我们首先将其转换为正方形的指定大小的图像, 接着计算这个图像的平均hsv颜色, 并用这个颜色值对图像进行命名.

最后会在一个文件夹内生成如下所示的图像, 后面我们就会使用这些图像来进行马赛克图片的拼接.

Python制作马赛克图片

 

生成马赛克图片

在有了上面的素材准备之后, 我们可以开始生成马赛克图片了. 有下面的几个步骤:

  • 首先读取素材文件夹内的所有图片, 并将他们的颜色保存在img_db里面.
  • 接着将原始图像分成一个一个的小块, 计算小块的平均hsv颜色.
  • 将这个颜色值与img_db里的颜色做对比, 使用欧式距离, 找出最近的那个颜色, 和对应的图像.
  • 将找到的图像依次拼接, 最后组成马赛克图片. 这里将拼接后的图像保存一次.
  • 最后, 我们将拼接的图像与原始图像进行重叠, 这样可以使得效果好一些.

下面是关于生成马赛克图片的完整的代码.

  1. class create_mosaic(mosaic):
  2.     """创建马赛克图片
  3.     """
  4.     def __init__(self, IN_DIR, OUT_DIR, SLICE_SIZE, REPATE, OUT_SIZE):
  5.         super(create_mosaic, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE, REPATE,
  6.                                            OUT_SIZE)
  7.     def read_img_db(self):
  8.         """读取所有的图片
  9.         """
  10.         img_db = []  # 存储color_list
  11.         for file_ in os.listdir(self.OUT_DIR):
  12.             if file_ == 'None.png':
  13.                 pass
  14.             else:
  15.                 file_ = file_.split('.png')[0]  # 获得文件名
  16.                 file_ = file_[1:-1].split(',')  # 获得hsv三个值
  17.                 file_ = [float(i) for i in file_]
  18.                 file_.append(0)  # 最后一位计算图像使用次数
  19.                 img_db.append(file_)
  20.         return img_db
  21.     def find_closiest(self, color, list_colors):
  22.         """寻找与像素块颜色最接近的图像
  23.         """
  24.         FAR = 10000000
  25.         for cur_color in list_colors:  # list_color是图像库中所以图像的平均hsv颜色
  26.             n_diff = np.sum((color - np.absolute(cur_color[:3]))**2)
  27.             if cur_color[3] <= self.REPATE:  # 同一个图片使用次数不能太多
  28.                 if n_diff < FAR:  # 修改最接近的颜色
  29.                     FAR = n_diff
  30.                     cur_closer = cur_color
  31.         cur_closer[3] += 1
  32.         return "({}, {}, {})".format(cur_closer[0], cur_closer[1],
  33.                                      cur_closer[2])  # 返回hsv颜色
  34.     def make_puzzle(self, img):
  35.         """制作拼图
  36.         """
  37.         img = self.resize_pic(img, self.OUT_SIZE)  # 读取图片并修改大小
  38.         color_list = self.read_img_db()  # 获取所有的颜色的list
  39.         width, height = img.size  # 获得图片的宽度和高度
  40.         print("Width = {}, Height = {}".format(width, height))
  41.         background = Image.new('RGB', img.size,
  42.                                (255, 255, 255))  # 创建一个空白的背景, 之后向里面填充图片
  43.         total_images = math.floor(
  44.             (width * height) / (self.SLICE_SIZE * self.SLICE_SIZE))  # 需要多少小图片
  45.         now_images = 0  # 用来计算完成度
  46.         for y1 in range(0, height, self.SLICE_SIZE):
  47.             for x1 in range(0, width, self.SLICE_SIZE):
  48.                 try:
  49.                     # 计算当前位置
  50.                     y2 = y1 + self.SLICE_SIZE
  51.                     x2 = x1 + self.SLICE_SIZE
  52.                     # 截取图像的一小块, 并计算平均hsv
  53.                     new_img = img.crop((x1, y1, x2, y2))
  54.                     color = self.get_avg_color(new_img)
  55.                     # 找到最相似颜色的照片
  56.                     close_img_name = self.find_closiest(color, color_list)
  57.                     close_img_name = self.OUT_DIR + str(
  58.                         close_img_name) + '.png'  # 图片的地址
  59.                     paste_img = Image.open(close_img_name)
  60.                     # 计算完成度
  61.                     now_images += 1
  62.                     now_done = math.floor((now_images / total_images) * 100)
  63.                     r = '\r[{}{}]{}%'.format("#" * now_done,
  64.                                              " " * (100 - now_done), now_done)
  65.                     sys.stdout.write(r)
  66.                     sys.stdout.flush()
  67.                     background.paste(paste_img, (x1, y1))
  68.                 except IOError:
  69.                     print('创建马赛克块失败')
  70.         # 保持最后的结果
  71.         background.save('out_without_background.jpg')
  72.         img = Image.blend(background, img, 0.5)
  73.         img.save('out_with_background.jpg')
  74.         return True

 

整体代码结构

上面三个类的关系如下所示, 下面mosaic.create_mosaic少打了一个e, 我在上面修改了, 下面这张图暂时不修改了:

Python制作马赛克图片

马赛克图片的效果

我们使用我在塞尔达里截的300张图片, 来作为素材图像, 来简单做一个示范. 整个文件夹的框架如下所示:

  1. ├─images (存储原始的图像数据)
  2. ├─outputImages (存储修改后的图像数据)
  3. ├─mosaic.py (主文件)

其中images中存储的是我们原始的截图, 之后会使用上面的create_image_db来对原始的素材图像进行大小的变化, 和计算图像的平均hsv颜色.

  1. # 创建马赛克块, 创建素材库
  2. createdb = create_image_db(IN_DIR=os.path.join(filePath, 'images/'),
  3.                             OUT_DIR=os.path.join(filePath, 'outputImages/'),
  4.                             SLICE_SIZE=100,
  5.                             REPATE=20,
  6.                             OUT_SIZE=5000)
  7. createdb.convert_all_images()

之后我们根据上面创建的素材图像, 来创建马赛克拼图.

  1. # 创建拼图
  2. createM = create_mosaic(IN_DIR=os.path.join(filePath, 'images/'),
  3.                         OUT_DIR=os.path.join(filePath, 'outputImages/'),
  4.                         SLICE_SIZE=100,
  5.                         REPATE=20,
  6.                         OUT_SIZE=5000)
  7. out = createM.make_puzzle(img=os.path.join(filePath, 'Zelda.jpg'))

最后会生成两张图片, 第一张是没有与原始的图像进行重叠的, 纯使用小的照片拼接起来的. 这里因为图像素材比较少, 所以最后的马赛克图片的效果一般:

Python制作马赛克图片

但是, 我们将其与原始的图像进行重合之后, 效果就会好上很多.

Python制作马赛克图片

最后就可以得到上面的图像, 放大看就可以看到每一个小的图像, 是由每一个小的图像拼接而来.

Python制作马赛克图片
  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: