Python 制作馬賽克拼合圖像
文章目錄
- Python 制作馬賽克拼合圖像
- 知識點
- 效果:
- 環境
- 原理
- RGB 色彩空間
- HSV 色彩空間
- RGB 與 HSV 色彩空間的轉換
- 馬賽克圖檔拼合
- 資料準備:
- 導入需要的庫
- 計算圖像平均 HSV 值
- 生成素材資料庫
- 生成馬賽克圖檔
- 效果展示
- 完整代碼:
200 行 Python 代碼完成生成馬賽克圖檔的腳本
知識點
- 什麼是 RGB
- HSV 色彩空間
- Python 基礎
- 包括 pillow 庫的使用
- 多程序庫 multiprocessing 的使用
- 制作馬賽克圖像的原理
效果:
可以生成類似下面的圖檔:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5iN4ATM3AjN4EGOwQGN1ImNzYzX4QDMyUTM5AzLcFTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.jpg)
放大上圖之後,可以看到上圖是由許多小的圖像組成的:
環境
- Python 3.5.2
- numpy==1.18.1
- Pillow==8.0.0
我們會使用 Python 中的 pillow(PIL)庫來處理圖像,使用 numpy 來進行一些數值計算。 我們首先使用下面的指令來安裝這兩個庫:
pip install numpy
pip install pillow
原理
一張圖像是通過許多的像素組成的。 為了生成馬賽克圖檔,我們的想法是,将原有圖像的每一個小部分,使用顔色與這一小部分相似的圖像進行替換,進而生成馬賽克風格的圖像。
下面是整個結構(圖檔可能會有點小,可以點選檢視大圖),我們會依次進行介紹:
在接下來的中,我們會:
- 首先介紹 RGB 與 HSV 色彩空間,并介紹如何從 RGB 色彩空間轉換到 HSV 色彩空間。
- 接着我們會介紹馬賽克圖檔拼合的實驗步驟,主要包含對素材圖像的處理,生成圖像資料庫;
- 最後,我們使用處理之後的素材圖像,生成馬賽克圖像。
RGB 色彩空間
RGB 色彩空間是,由三個通道表示一幅圖像。三個通道分别為紅色®,綠色(G)和藍色(B)。 RGB 色彩空間由紅綠藍三原色的色度定義,借此可以定義出相應的色三角,生成其它顔色。 通過這三種顔色的不同組合,可以形成幾乎所有的其他顔色。 最常見的 RGB 空間是 sRGB。
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-WSxWTo1u-1636963261977)(https://doc.shiyanlou.com/courses/3007/246442/43c98ec982b69be0a54c798540467cde-1)]
但是,在自然環境下,圖像容易受自然光照、遮擋等情況的影響。 也就是人眼觀察圖檔會對圖檔的亮度比較敏感。 而 RGB 色彩空間的三個分量都與亮度密切相關,隻要亮度改變,RGB 顔色的三個分量都會改變.
同時,由于人眼對于這 RGB 這三種顔色的敏感程度是不一樣的。 在單色中,人眼對紅色最不敏感,藍色最敏感,是以 RGB 色彩空間是一種均勻性較差的色彩空間。
如果在 RGB 的色彩空間上,我們直接用歐氏距離來度量衡量顔色的相似度,他的結果與人眼視覺會有較大的偏差。
HSV 色彩空間
由于上面講到的 RGB 色彩空間不能友善的比較顔色之間的相似度,于是在處理這一類問題的時候我們更多的是使用 HSV 色彩空間。 HSV 色彩空間也是由三個分量組成的,分别是:
- Hue(色調)
- Saturation (飽和度)
- Value (明度)
我們會常用下圖的圓柱體來表示 HSV 色彩空間,其中:
- H 用極坐标的極角表示;
- S 用極坐标的軸的長度表示;
- V 用圓柱的高度表示;
在 RGB 色彩空間中,顔色由三個共同的值決定。 例如黃色對應的 RGB 值為
(255, 255, 0)
。 但是在 HSV 色彩空間中,黃色隻有 H(Hue)決定,即
Hue=60
即可。 下圖為在 HSV 空間中,黃色的表示:
在确定了 H(Hue)之後,我們可以更改 Saturation 和 Value。
- Saturation(飽和度):飽和度表示顔色接近光譜色的程度。飽和度越高,說明顔色越深,越接近光譜色;飽和度越低,說明顔色越淺,越接近白色。
- Value(明度):明度決定色彩空間中顔色的明暗程度。明度越高,表示顔色越明亮。明度為 0 的時候,為全黑。下圖為明度為 0 的時候,最後顔色是黑色。
RGB 與 HSV 色彩空間的轉換
接下來我們介紹一下如何将顔色從 RGB 色彩空間轉換為 HSV 色彩空間。 首先我們定義max = \max(R, G, B)max=max(R,G,B),min = \min(R, G, B)min=min(R,G,B)。 接下來分别計算 H、S、V 的值。 其中 V 的計算式子如下所示:
V = maxV=max
S 的計算式子如下所示:
S = \begin{cases} & \frac{max-min}{max} ,& if \ V \neq 0 \ & 0 ,& if \ V = 0 \end{cases}S={maxma**x−min,0,i**f V\=0i**f V=0
H 的計算式子如下所示:
h = \begin{cases} & 60° \times (0 + \frac{G-B}{max-min}) , & if \ max = R \ & 60° \times (2 + \frac{B-R}{max-min}) , & if \ max = G \ & 60° \times (4 + \frac{R-G}{max-min}) , & if \ max = B \end{cases}h=⎩⎪⎪⎨⎪⎪⎧60°×(0+max−min**G−B),60°×(2+max−min**B−R),60°×(4+max−min**R−G),i**f max=Rif max=Gif max=B
我們不需要自己寫轉換的函數,可以直接使用
colorsys
這個庫。 其中包含兩個函數,分别是
rgb_to_hsv
和
hsv_to_rgb
。 分别是将顔色從 RGB 空間轉換到 HSV 色彩空間,和将顔色從 HSV 色彩空間轉換為 RGB 色彩空間。 我們下面用黃色做一個小的轉換測試。
我們首先将黃色從 RGB 色彩空間轉換為 HSV 色彩空間。 我們需要注意的是,這裡
rgb_to_hsv
需要将 RGB 值轉換為 0 到 1 之間,是以我們除以 255。
上面結果中的 0.1666 就是\frac{60}{360}36060。 接着我們将其從 HSV 色彩空間轉換為 RGB 色彩空間。
馬賽克圖檔拼合
分别是:
- 生成圖像素材資料庫;
- 對原始圖像每一小塊進行分析,與圖像資料庫進行比較,找出最接近的圖檔進行替換;
于是我們将上面的功能寫成兩個類,分别是
mosaic.create_image_db
,用來生成素材資料庫;
mosaic.create_mosaic
用來生成馬賽克圖像。 這兩個類都繼承自基類,
mosaic.mosaic
。這個基類裡有兩個方法,分别是實作調整圖檔的大小(
resize_pic
)和計算圖像的評價 HSV 值(
get_avg_color
)。 下面我們會依次對這三個類進行介紹。
資料準備:
https://labfile.oss.aliyuncs.com/courses/3007/mosaic_images.zip
導入需要的庫
我們首先導入必要的庫。 在檔案中,我們會使用 Python 中的 pillow(PIL)庫來處理圖像,使用 numpy 來進行一些數值計算。 我們在
mosaic.py
檔案中先引入需要的庫。
import os
import sys
import time
import math
import numpy as np
from PIL import Image, ImageOps
from multiprocessing import Pool
from colorsys import rgb_to_hsv, hsv_to_rgb
from typing import List, Tuple, Union
計算圖像平均 HSV 值
上面我們提到,通過 HSV 色彩空間來比較圖檔顔色的相似度是比較好的。 是以這裡我們希望實作這樣的一個功能:輸入一個圖檔,傳回這個圖檔的平均 HSV 值。
我們的想法是周遊這個圖像的每一個像素點,獲得每一個像素點的 RGB 值。 接着通過上面介紹的函數
rgb_to_hsv
,将 RGB 值轉換為 HSV 值。 最後分别求 H(Hue)、S(Saturation)和 V(Saturation)的平均值。
因為之後我們需要對素材圖檔和待轉換的圖檔都求平均 HSV 值,是以我們建立一個父類
mosaic
,裡面包含一個計算圖像平均 HSV 值的方法,之後可以繼承這個類。 同時,因為之後會用到圖像大小的轉換,是以我們也在這個類裡定義一個圖像
resize
的方法。
class mosaic(object):
"""定義計算圖檔的平均hsv值
"""
def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
OUT_SIZE: int) -> None:
self.IN_DIR = IN_DIR # 原始的圖像素材所在檔案夾
self.OUT_DIR = OUT_DIR # 輸出素材的檔案夾, 這些都是計算過hsv和經過resize之後的圖像
self.SLICE_SIZE = SLICE_SIZE # 圖像放縮後的大小
self.REPATE = REPATE # 同一張圖檔可以重複使用的次數
self.OUT_SIZE = OUT_SIZE # 最終圖檔輸出的大小
def resize_pic(self, in_name: str, size: int) -> Image:
"""轉換圖像大小
"""
img = Image.open(in_name)
img = ImageOps.fit(img, (size, size), Image.ANTIALIAS)
return img
def get_avg_color(self, img: Image) -> Tuple[float, float, float]:
"""計算圖像的平均hsv
"""
width, height = img.size
pixels = img.load()
if type(pixels) is not int:
data = [] # 存儲圖像像素的值
for x in range(width):
for y in range(height):
cpixel = pixels[x, y] # 獲得每一個像素的值
data.append(cpixel)
h = 0
s = 0
v = 0
count = 0
for x in range(len(data)):
r = data[x][0]
g = data[x][1]
b = data[x][2] # 得到一個點的GRB三色
count += 1
hsv = rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)
h += hsv[0]
s += hsv[1]
v += hsv[2]
hAvg = round(h / count, 3)
sAvg = round(s / count, 3)
vAvg = round(v / count, 3)
if count > 0: # 像素點的個數大于0
return (hAvg, sAvg, vAvg)
else:
raise IOError("讀取圖檔資料失敗")
else:
raise IOError("PIL 讀取圖檔資料失敗")
生成素材資料庫
之前我們把我們的素材圖檔都下載下傳并解壓到了
images
檔案夾内。 但由于我們準備的圖檔可能在大小上不同。 于是,為了友善之後圖檔的生成,我們先對原始素材圖檔進行一次處理,主要包含兩個部分:
- 将原始素材圖檔轉換為統一的格式,這裡使用在上面類内定義的
方法完成;resize_pic
- 計算圖檔的平均 HSV 值,并将其作為新的檔案名進行儲存;
于是,我們周遊整個素材檔案夾,對其中的每一張圖檔進行大小的轉換和計算平均 HSV 值。 并将新的圖檔儲存在檔案夾
OUT_DIR
内。 這裡我們會使用多程序,使用
multiprocessing
來完成多程序的使用。 這一部分完整的類如下所示:
class create_image_db(mosaic):
"""建立所需要的資料
"""
def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
OUT_SIZE: int) -> None:
super(create_image_db, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE,
REPATE, OUT_SIZE)
def make_dir(self) -> None:
os.makedirs(os.path.dirname(self.OUT_DIR), exist_ok=True) # 沒有就建立檔案夾
def get_image_paths(self) -> List[str]:
"""擷取檔案夾内圖像的位址
"""
paths = []
suffixs = ['png', 'jpg']
for file_ in os.listdir(self.IN_DIR):
suffix = file_.split('.', 1)[1] # 獲得檔案字尾
if suffix in suffixs: # 通過字尾判斷是否是圖檔
paths.append(self.IN_DIR + file_) # 添加圖像路徑
else:
print("非圖檔:%s" % file_)
if len(paths) > 0:
print("一共找到了%s" % len(paths) + "張圖檔")
else:
raise IOError("未找到任何圖檔")
return paths
def convert_image(self, path):
"""轉換圖像大小, 同時計算一個圖像的平均hsv值.
"""
img = self.resize_pic(path, self.SLICE_SIZE)
color = self.get_avg_color(img)
img.save(str(self.OUT_DIR) + str(color) + ".png")
def convert_all_images(self) -> None:
"""将所有圖像進行轉換
"""
self.make_dir()
paths = self.get_image_paths()
print("正在生成馬賽克塊...")
pool = Pool() # 多程序處理
pool.map(self.convert_image, paths) # 對已有的圖像進行處理, 轉換為對應的色塊
pool.close()
pool.join()
之後運作這一部分代碼之後,會在目前檔案夾下生成一個
outputImages
的檔案夾。 這裡是我們經過處理之後的圖像,所有圖像的大小是一樣的,同時圖檔的名稱改為了這個圖檔的平均 HSV 值。 下面是之後運作
mosaic.py
會生成的檔案夾,和其中的圖檔。
暫時我們在這裡先不運作,先繼續往後面編寫,完成生成馬賽克圖檔的類。 之後會運作
mosaic.py
檔案,我們可以再進行檢視。
生成馬賽克圖檔
在有了處理好的素材照片之後,下面我們就可以開始生成馬賽克圖檔了。 這裡的整個流程流程為:
- 首先周遊我們生成的素材檔案夾,獲得裡面所有圖檔的平均 HSV 值,儲存在一個 list 中;
- 接着我們将原始圖檔分為一小塊一小塊,每一個小塊會計算他的平均 HSV 值;
- 接着我們在上面生成素材的平均 HSV 值的 list 中,找到與這個小塊的平均 HSV 值最接近的那張圖檔,并用那張圖檔來替換這個小塊;
- 依次對整個圖形進行這樣的操作,這樣就可以使用素材圖像生成一個圖像;
- 最後可以選擇将生成的圖像與原始圖像重疊,使用
完成,這一步是可以選擇的;Image.blend
我們将上面的步驟寫在一個類裡面,下面是完整的代碼。
class create_mosaic(mosaic):
"""建立馬賽克圖檔
"""
def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
OUT_SIZE: int) -> None:
super(create_mosaic, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE, REPATE,
OUT_SIZE)
def read_img_db(self) -> List[List[Union[float, int]]]:
"""讀取所有的圖檔
"""
img_db = [] # 存儲color_list
for file_ in os.listdir(self.OUT_DIR):
if file_ == 'None.png':
pass
else:
file_ = file_.split('.png')[0] # 獲得檔案名
file_ = file_[1:-1].split(',') # 獲得hsv三個值
file_ = [float(i) for i in file_]
file_.append(0) # 最後一位計算圖像使用次數
img_db.append(file_)
return img_db
def find_closiest(self, color: Tuple[float, float, float],
list_colors: List[List[Union[float, int]]]) -> str:
"""尋找與像素塊顔色最接近的圖像
"""
FAR = 10000000
for cur_color in list_colors: # list_color是圖像庫中是以圖像的平均hsv顔色
n_diff = np.sum((color - np.absolute(cur_color[:3]))**2)
if cur_color[3] <= self.REPATE: # 同一個圖檔使用次數不能太多
if n_diff < FAR: # 修改最接近的顔色
FAR = n_diff
cur_closer = cur_color
cur_closer[3] += 1
return "({}, {}, {})".format(cur_closer[0], cur_closer[1],
cur_closer[2]) # 傳回hsv顔色
def make_puzzle(self, img: str) -> bool:
"""制作拼圖
"""
img = self.resize_pic(img, self.OUT_SIZE) # 讀取圖檔并修改大小
color_list = self.read_img_db() # 擷取所有的顔色的list
width, height = img.size # 獲得圖檔的寬度和高度
print("Width = {}, Height = {}".format(width, height))
background = Image.new('RGB', img.size,
(255, 255, 255)) # 建立一個空白的背景, 之後向裡面填充圖檔
total_images = math.floor(
(width * height) / (self.SLICE_SIZE * self.SLICE_SIZE)) # 需要多少小圖檔
now_images = 0 # 用來計算完成度
for y1 in range(0, height, self.SLICE_SIZE):
for x1 in range(0, width, self.SLICE_SIZE):
try:
# 計算目前位置
y2 = y1 + self.SLICE_SIZE
x2 = x1 + self.SLICE_SIZE
# 截取圖像的一小塊, 并計算平均hsv
new_img = img.crop((x1, y1, x2, y2))
color = self.get_avg_color(new_img)
# 找到最相似顔色的照片
close_img_name = self.find_closiest(color, color_list)
close_img_name = self.OUT_DIR + str(
close_img_name) + '.png' # 圖檔的位址
paste_img = Image.open(close_img_name)
# 計算完成度
now_images += 1
now_done = math.floor((now_images / total_images) * 100)
r = '\r[{}{}]{}%'.format("#" * now_done,
" " * (100 - now_done), now_done)
sys.stdout.write(r)
sys.stdout.flush()
background.paste(paste_img, (x1, y1))
except IOError:
print('建立馬賽克塊失敗')
# 保持最後的結果
background.save('out_without_background.jpg')
img = Image.blend(background, img, 0.5)
img.save('out_with_background.jpg')
return True
這裡的參數
REPATE
表示每一張圖檔可以最多重複的次數。 如果我們圖檔足夠多,可以設定為
REPATE=1
,此時每一張圖檔隻能使用一次。
效果展示
上面我們完成了代碼的主體架構,下面我們運作一下看一下結果。 首先我們下載下傳使用的測試圖檔:
https://labfile.oss.aliyuncs.com/courses/3007/Zelda.jpg
也是塞爾達中的一張圖檔,如下圖所示:
接着我們編寫
main
函數:
if __name__ == "__main__":
filePath = os.path.dirname(os.path.abspath(__file__)) # 擷取目前的路徑
start_time = time.time() # 程式開始運作時間, 記錄一共運作了多久
# 建立馬賽克塊, 建立素材庫
createdb = create_image_db(IN_DIR=os.path.join(filePath, 'images/'),
OUT_DIR=os.path.join(filePath, 'outputImages/'),
SLICE_SIZE=100,
REPATE=20,
OUT_SIZE=5000)
createdb.convert_all_images()
# 建立拼圖 (這裡使用絕對路徑)
createM = create_mosaic(IN_DIR=os.path.join(filePath, 'images/'),
OUT_DIR=os.path.join(filePath, 'outputImages/'),
SLICE_SIZE=100,
REPATE=20,
OUT_SIZE=5000)
out = createM.make_puzzle(img=os.path.join(filePath, 'Zelda.jpg'))
# 列印時間
print("耗時: %s" % (time.time() - start_time))
print("已完成")
接着我們儲存并關閉剛剛書寫的檔案,
mosaic.py
。 為了更好的觀察圖檔的效果,我們需要安裝一款檢視圖檔的軟體,Eye of GNOME(ego)。
運作mosaic.py檔案
這裡大概會等待 2-3 分鐘左右。 運作過程大緻如下圖所示:
運作結束之後,會在我們目前目錄下生成兩張圖檔,一張是沒有和原始圖檔進行融合的,檔案名為
'out_without_background.jpg'
。 我們使用 eog 來檢視圖像:
沒有與原始圖檔融合,此時效果如下圖所示:
同樣我們也可以檢視與原始圖檔融合之後的圖檔的效果。
最終的效果如下圖所示:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5iN4ATM3AjN4EGOwQGN1ImNzYzX4QDMyUTM5AzLcFTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.jpg)
完整代碼:
import os
import sys
import time
import math
import numpy as np
from PIL import Image, ImageOps
from multiprocessing import Pool
from colorsys import rgb_to_hsv, hsv_to_rgb
from typing import List, Tuple, Union
class mosaic(object):
"""定義計算圖檔的平均hsv值
"""
def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
OUT_SIZE: int) -> None:
self.IN_DIR = IN_DIR # 原始的圖像素材所在檔案夾
self.OUT_DIR = OUT_DIR # 輸出素材的檔案夾, 這些都是計算過hsv和經過resize之後的圖像
self.SLICE_SIZE = SLICE_SIZE # 圖像放縮後的大小
self.REPATE = REPATE # 同一張圖檔可以重複使用的次數
self.OUT_SIZE = OUT_SIZE # 最終圖檔輸出的大小
def resize_pic(self, in_name: str, size: int) -> Image:
"""轉換圖像大小
"""
img = Image.open(in_name)
img = ImageOps.fit(img, (size, size), Image.ANTIALIAS)
return img
def get_avg_color(self, img: Image) -> Tuple[float, float, float]:
"""計算圖像的平均hsv
"""
width, height = img.size
pixels = img.load()
if type(pixels) is not int:
data = [] # 存儲圖像像素的值
for x in range(width):
for y in range(height):
cpixel = pixels[x, y] # 獲得每一個像素的值
data.append(cpixel)
h = 0
s = 0
v = 0
count = 0
for x in range(len(data)):
r = data[x][0]
g = data[x][1]
b = data[x][2] # 得到一個點的GRB三色
count += 1
hsv = rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)
h += hsv[0]
s += hsv[1]
v += hsv[2]
hAvg = round(h / count, 3)
sAvg = round(s / count, 3)
vAvg = round(v / count, 3)
if count > 0: # 像素點的個數大于0
return (hAvg, sAvg, vAvg)
else:
raise IOError("讀取圖檔資料失敗")
else:
raise IOError("PIL 讀取圖檔資料失敗")
class create_image_db(mosaic):
"""建立所需要的資料
"""
def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
OUT_SIZE: int) -> None:
super(create_image_db, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE,
REPATE, OUT_SIZE)
def make_dir(self) -> None:
os.makedirs(os.path.dirname(self.OUT_DIR), exist_ok=True) # 沒有就建立檔案夾
def get_image_paths(self) -> List[str]:
"""擷取檔案夾内圖像的位址
"""
paths = []
suffixs = ['png', 'jpg']
for file_ in os.listdir(self.IN_DIR):
suffix = file_.split('.', 1)[1] # 獲得檔案字尾
if suffix in suffixs: # 通過字尾判斷是否是圖檔
paths.append(self.IN_DIR + file_) # 添加圖像路徑
else:
print("非圖檔:%s" % file_)
if len(paths) > 0:
print("一共找到了%s" % len(paths) + "張圖檔")
else:
raise IOError("未找到任何圖檔")
return paths
def convert_image(self, path):
"""轉換圖像大小, 同時計算一個圖像的平均hsv值.
"""
img = self.resize_pic(path, self.SLICE_SIZE)
color = self.get_avg_color(img)
img.save(str(self.OUT_DIR) + str(color) + ".png")
def convert_all_images(self) -> None:
"""将所有圖像進行轉換
"""
self.make_dir()
paths = self.get_image_paths()
print("正在生成馬賽克塊...")
pool = Pool() # 多程序處理
pool.map(self.convert_image, paths) # 對已有的圖像進行處理, 轉換為對應的色塊
pool.close()
pool.join()
class create_mosaic(mosaic):
"""建立馬賽克圖檔
"""
def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
OUT_SIZE: int) -> None:
super(create_mosaic, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE, REPATE,
OUT_SIZE)
def read_img_db(self) -> List[List[Union[float, int]]]:
"""讀取所有的圖檔
"""
img_db = [] # 存儲color_list
for file_ in os.listdir(self.OUT_DIR):
if file_ == 'None.png':
pass
else:
file_ = file_.split('.png')[0] # 獲得檔案名
file_ = file_[1:-1].split(',') # 獲得hsv三個值
file_ = [float(i) for i in file_]
file_.append(0) # 最後一位計算圖像使用次數
img_db.append(file_)
return img_db
def find_closiest(self, color: Tuple[float, float, float],
list_colors: List[List[Union[float, int]]]) -> str:
"""尋找與像素塊顔色最接近的圖像
"""
FAR = 10000000
for cur_color in list_colors: # list_color是圖像庫中是以圖像的平均hsv顔色
n_diff = np.sum((color - np.absolute(cur_color[:3]))**2)
if cur_color[3] <= self.REPATE: # 同一個圖檔使用次數不能太多
if n_diff < FAR: # 修改最接近的顔色
FAR = n_diff
cur_closer = cur_color
cur_closer[3] += 1
return "({}, {}, {})".format(cur_closer[0], cur_closer[1],
cur_closer[2]) # 傳回hsv顔色
def make_puzzle(self, img: str) -> bool:
"""制作拼圖
"""
img = self.resize_pic(img, self.OUT_SIZE) # 讀取圖檔并修改大小
color_list = self.read_img_db() # 擷取所有的顔色的list
width, height = img.size # 獲得圖檔的寬度和高度
print("Width = {}, Height = {}".format(width, height))
background = Image.new('RGB', img.size,
(255, 255, 255)) # 建立一個空白的背景, 之後向裡面填充圖檔
total_images = math.floor(
(width * height) / (self.SLICE_SIZE * self.SLICE_SIZE)) # 需要多少小圖檔
now_images = 0 # 用來計算完成度
for y1 in range(0, height, self.SLICE_SIZE):
for x1 in range(0, width, self.SLICE_SIZE):
try:
# 計算目前位置
y2 = y1 + self.SLICE_SIZE
x2 = x1 + self.SLICE_SIZE
# 截取圖像的一小塊, 并計算平均hsv
new_img = img.crop((x1, y1, x2, y2))
color = self.get_avg_color(new_img)
# 找到最相似顔色的照片
close_img_name = self.find_closiest(color, color_list)
close_img_name = self.OUT_DIR + str(
close_img_name) + '.png' # 圖檔的位址
paste_img = Image.open(close_img_name)
# 計算完成度
now_images += 1
now_done = math.floor((now_images / total_images) * 100)
r = '\r[{}{}]{}%'.format("#" * now_done,
" " * (100 - now_done), now_done)
sys.stdout.write(r)
sys.stdout.flush()
background.paste(paste_img, (x1, y1))
except IOError:
print('建立馬賽克塊失敗')
# 保持最後的結果
background.save('out_without_background.jpg')
img = Image.blend(background, img, 0.5)
img.save('out_with_background.jpg')
return True
if __name__ == "__main__":
filePath = os.path.dirname(os.path.abspath(__file__)) # 擷取目前的路徑
start_time = time.time() # 程式開始運作時間, 記錄一共運作了多久
# 建立馬賽克塊, 建立素材庫
createdb = create_image_db(IN_DIR=os.path.join(filePath, 'images/'),
OUT_DIR=os.path.join(filePath, 'outputImages/'),
SLICE_SIZE=100,
REPATE=20,
OUT_SIZE=5000)
createdb.convert_all_images()
# 建立拼圖 (這裡使用絕對路徑)
createM = create_mosaic(IN_DIR=os.path.join(filePath, 'images/'),
OUT_DIR=os.path.join(filePath, 'outputImages/'),
SLICE_SIZE=100,
REPATE=20,
OUT_SIZE=5000)
out = createM.make_puzzle(img=os.path.join(filePath, 'Zelda.jpg'))
# 列印時間
print("耗時: %s" % (time.time() - start_time))
print("已完成")