【前言】
在 Project 之中剛好有一份工作是要把每個部件合成,雖然跟主題沒有關聯不過因為篇幅好像還夠,就把我使用的方法分享給大家!我研究了一陣子不知道要用什麼 Hash Function,如果大家有覺得很適合的方法歡迎提供給我。
【Step.0 -Blending Rules】
我們先來看看圖片組合的規則吧!每隻恐龍總共有 12 個部位,某些部位需要和某些部位是同一個顏色,而某些部位的樣式也需要是一組的。
Blending Order ( from top layer to bottom layer ):
Blending Rules:
Name Rules:
元件命名方式:部位_編號-(顏色/形狀)
一個檔案(不論圖案、顏色)就算一個編號
不同部位獨自編號(牙齒獨立編號1-10; 眼睛獨立編號1-20)
e.g. mouth_1-none
e.g. eyes_12-blue
【Step.1 - Install & Import the Module】
首先先下載套件,這是我下載的方法,提供給大家。如果有疑惑的話也可以去 google 一下!
開始 -> 尋找 python -> 開啟檔案位置
-> 如果是找到的是捷徑的話可以 右鍵-內容-目標位置 來找到真正的位置
-> 開啟 python 安裝位置的資料夾之後,會看到有一個 Scripts 資料夾 -> 複製位置
-> 打開 cmd
-> cd C:\Users\LuLu\AppData\Local\Programs\Python\Python38-32\Scripts
-> pip install opencv-python
如果版本太老可以 pip install --upgrade pip 來作升級
引入套件,其中除了我們讀取圖片會用到的 Image, cv2
以外,os
也是非常重要,後面會解釋!
import cv2
import os
import random
from PIL import Image
【Step.2 - Construct the Data Structure】
每一隻恐龍的資料,也就是 #0001, #0032, #9999…等。並且建造一個成員函數 print_dinoDATA()
來印出這隻恐龍的部件資料,以及 show_dinoIMG()
來顯示當前恐龍的圖片!
class DINO:
# Constructor
def __init__(self, img, number, clothes, hat, nostril, eyebrow, eye, mouth, teeth, jaw, armor, body, hand, bg):
self.img = img # 圖片
self.resize_img = self.resize() # 修改大小之後的圖片,以方便在電腦上顯示
self.number = number # 圖片編號
self.clothes = clothes
self.hat = hat
self.nostril= nostril
self.eyebrow = eyebrow
self.eye = eye
self.mouth = mouth
self.teeth = teeth
self.jaw = jaw
self.armor = armor
self.body = body
self.hand = hand
self.bg = bg
# Method
def print_dinoDATA(self):
print(f"The Dino {self.number} Data is 1. Clothes: {self.clothes}\n \
2. hat: {self.hat}\n \
3. nostril: {self.nostril}\n \
4. eyebrow: {self.eyebrow}\n \
5. eye: {self.eye}\n \
6. mouth: {self.mouth}\n \
7. teeth: {self.teeth}\n \
8. jaw: {self.jaw}\n \
9. armor: {self.armor}\n \
10. body: {self.body}\n \
11. hand: {self.hand}\n \
12. bg: {self.bg}")
def resize(self):
showing_img = cv2.resize(self.img, (720, 720)) # 修改大小之後的圖片,以方便在電腦上顯示
return showing_img
def show_dinoIMG(self):
# cv2.imshow("Dino Image", self.img)
cv2.imshow("Dino Image", self.resize_img)
cv2.waitKey()
比較需要注意的有 resize_img
,因為圖片沒辦法在cv2.imshow
開啟的視窗縮放(或 call window 才能特別縮放),會以原本的解析度呈現,如果圖片太大或者電腦螢幕太小就會顯示不完全。所以特別增加一個專門用來顯示的圖片,如此也比較模組化可以特別調整想顯示的圖片。
如果對字串格式化的處理有疑問可以看這篇文章。
建造部件資料庫,也就是眼睛、嘴巴、顏色…等。比較特別的是因為每張圖片的命名方式都不同,如果需要遍歷一個資料夾裡面的所有檔案的話要使用到。for filename in os.listdir(folder):
這個敘述來遍歷,其中檔案的路徑選取方法為 os.path.join(folder,filename)
。
class DATA:
def __init__(self, name, numbers, quantities):
self.name = name
self.numbers = numbers # 部位編號
self.quantities = quantities # 部位數量
self.location = self.location_init() # 部位資料夾相對位置,string
self.ImgBase = self.load_ImgBase() # list
def location_init(self):
location = "./part_image/" + str(self.numbers) + ". " + str(self.name) + "/"
return location
def load_ImgBase(self):
self.ImgBase = []
if self.name == "bg":
folder = "./part_image/" + str(self.numbers) + ". " + str(self.name) + " (background)"
else:
folder = "./part_image/" + str(self.numbers) + ". " + str(self.name)
for filename in os.listdir(folder):
img = cv2.imread(os.path.join(folder, filename), cv2.IMREAD_UNCHANGED)
img = cv2.resize(img, (1000, 1000))
if img is not None:
self.ImgBase.append(PART(self.name, filename, img))
# for i in range(self.quantities):
# print(self.ImgBase[i].shape)
return self.ImgBase
def show_ImgBase(self):
for i in range(self.quantities):
showing_img = cv2.resize(self.ImgBase[i].img, (720, 720)) # 修改大小之後的圖片,以方便在電腦上顯示
cv2.imshow("Dino Image", showing_img)
cv2.waitKey()
單一部位的單一部件,也就是 eye_1.png, teeth_1-closed.png, bg_1.png...等。
class PART:
def __init__(self, part, filename, img):
self.part = part
self.filename = filename
self.img = img
self.shape = self.shape_init()
self.color = self.color_init()
def shape_init(self):
if "open" in self.filename:
return "open"
elif "closed" in self.filename:
return "closed"
else:
return "None"
def color_init(self):
if "green" in self.filename:
return "green"
elif "blue" in self.filename:
return "blue"
elif "aqua" in self.filename:
return "aqua"
else:
return "None"
可以透過 show_ImgBase()
來呈現整個目標部件資料庫的圖片。
clothes.show_ImgBase()
jaw.show_ImgBase()
儲存所有產出恐龍的資料,show_DataBase()
就是把所有圖片的呈現出來,save_DataBase()
就是把所有 Data Base 裡面的圖片存起來,其中 [cv2.IMWRITE_JPEG_QUALITY, 100]
是專門給 JPG 的畫質控制。
class DataBase:
def __init__(self, Quantities):
self.Base = []
self.Quantities = Quantities
def show_DataBase(self):
for i in range(self.Quantities):
self.Base[i].print_dinoDATA()
cv2.imshow("Dino Image", self.Base[i].resize_img)
cv2.waitKey()
def save_DataBase(self):
for i in range(self.Quantities):
cv2.imwrite("./Final/#" + str(self.Base[i].number) + ".jpg", self.Base[i].img, [cv2.IMWRITE_JPEG_QUALITY, 100])
關於讀取檔案的路徑如果有疑惑可以看下面這篇文章。
初學Python手記#1-資料前處理(相對/絕對路徑、資料選取)
【Step.3 - Declaration】
clothes = DATA("clothes", 1, 3)
hat = DATA("head", 2, 2)
nostril = DATA("nostril", 3, 2)
eyebrow = DATA("eyebrow", 4, 6)
eye = DATA("eye" , 5, 4)
mouth = DATA("mouth", 6, 1)
teeth = DATA("teeth", 7, 4)
jaw = DATA("jaw", 8, 6)
armor = DATA("armor", 9, 4)
body = DATA("body", 10, 3)
hand = DATA("hand", 11, 5)
bg = DATA("bg", 12, 2)
【Step.4 - Random Blending the Parts】
初始化 Dino_DataBase
,其中 10000
就是想要產出的圖片數量。這邊利用 Dino_HashTable
來避免產出同樣的圖片。
Dino_DataBase = DataBase(10000)
Dino_HashTable = {}
這裡我先在每個部件隨機選出一個樣式,分別儲存在 now_part-name
這個變數裡面。然後Hash Function
是根據每個部件的編號為底數,以該部件該樣式的編號作為次方,並且 mod 一個質數,把十二個部件加起來。如果可以在 Dino_HashTable
裡面找到表示 collision
,本來想說要做 double hashing
或 linked-list
的 chain
,但發現機率太低,不如重新做一個比較簡單!
def hash_unique(HashTable):
# now_... 是指 index,所以需要減 1
now_clothes = random.randint(1, clothes.quantities) - 1
now_hat = random.randint(1, hat.quantities) - 1
now_nostril = random.randint(1, nostril.quantities) - 1
now_jaw = random.randint(1, jaw.quantities) - 1 # 必須先固定 jaw
now_eyebrow = random.randint(1, eyebrow.quantities) - 1
while jaw.ImgBase[now_jaw].color != eyebrow.ImgBase[now_eyebrow].color:
now_eyebrow = random.randint(1, eyebrow.quantities) - 1
now_eye = random.randint(1, eye.quantities) - 1
now_mouth = random.randint(1, mouth.quantities) - 1 # 目前嘴巴只有一種所以下面先註解掉
# while mouth.ImgBase[now_mouth].shape != jaw.ImgBase[now_jaw].shape:
# now_mouth = random.randint(1, mouth.quantities) - 1
now_teeth = random.randint(1, teeth.quantities) - 1
while mouth.ImgBase[now_mouth].shape != teeth.ImgBase[now_teeth].shape:
now_teeth = random.randint(1, teeth.quantities) - 1
now_armor = random.randint(1, armor.quantities) - 1
now_body = random.randint(1, body.quantities) - 1
while jaw.ImgBase[now_jaw].color != body.ImgBase[now_body].color:
now_body = random.randint(1, body.quantities) - 1
now_hand = random.randint(1, hand.quantities) - 1
while jaw.ImgBase[now_jaw].color != hand.ImgBase[now_hand].color:
now_hand = random.randint(1, hand.quantities) - 1
now_bg = random.randint(1, bg.quantities) - 1
# hash function
now_hash = pow(clothes.numbers, now_clothes % 11) + \
pow(hat.numbers, now_hat % 11) + \
pow(nostril.numbers, now_nostril % 11) + \
pow(eyebrow.numbers, now_eyebrow % 11) + \
pow(eye.numbers, now_eye % 11) + \
pow(mouth.numbers, now_mouth % 11) + \
pow(teeth.numbers, now_teeth % 7) + \
pow(jaw.numbers, now_jaw % 7) + \
pow(armor.numbers, now_armor % 7) + \
pow(body.numbers, now_body % 7) + \
pow(hand.numbers, now_hand % 7) + \
pow(bg.numbers, now_bg % 7)
# print(now_hash)
if HashTable.get(str(now_hash)) != None:
now_clothes, now_hat, now_nostril, now_jaw, now_eyebrow, now_eye, now_mouth, now_teeth, now_armor, now_body, now_hand, now_bg = hash_unique(HashTable)
else:
HashTable[str(now_hash)] = now_hash
return now_clothes, now_hat, now_nostril, now_jaw, now_eyebrow, now_eye, now_mouth, now_teeth, now_armor, now_body, now_hand, now_bg
兩張圖片疊合的函數,這裡比較特別的是將所有像素通道加起來來作疊合,而不是利用常見的 cv2.add()
或 cv2.addWeighted()
,因為會有透明度的問題所以沒辦法使用這兩個函式。還有需要注意的是 img
一定要用 .copy()
來作複製,否則會因為指到同一塊記憶體而在 Blending
之後影響到其他圖片的結果。
def Blending(img_1, img_2):
img1 = img_1.copy()
img2 = img_2.copy()
x_offset = y_offset = 0
y1, y2 = y_offset, y_offset + img2.shape[0]
x1, x2 = x_offset, x_offset + img2.shape[1]
alpha_s = img2[:, :, 3] / 255.0
alpha_l = 1.0 - alpha_s
for c in range(0, 3):
img1[y1:y2, x1:x2, c] = alpha_s * img2[:, :, c] + alpha_l * img1[y1:y2, x1:x2, c]
return img1
根據規則還有哈希之後的值依序合成圖片,並且加入 Dino_DataBase.Base
之中。
for i in range(Dino_DataBase.Quantities):
# Hash
now_clothes, now_hat, now_nostril, now_jaw, now_eyebrow, now_eye, now_mouth, now_teeth, now_armor, now_body, now_hand, now_bg = hash_unique(Dino_HashTable)
# Blending
new_picture = []
new_picture = Blending(bg.ImgBase[now_bg].img, hand.ImgBase[now_hand].img)
new_picture = Blending(new_picture, body.ImgBase[now_body].img)
new_picture = Blending(new_picture, armor.ImgBase[now_armor].img)
new_picture = Blending(new_picture, jaw.ImgBase[now_jaw].img)
new_picture = Blending(new_picture, teeth.ImgBase[now_teeth].img)
new_picture = Blending(new_picture, mouth.ImgBase[now_mouth].img)
new_picture = Blending(new_picture, eye.ImgBase[now_eye].img)
new_picture = Blending(new_picture, eyebrow.ImgBase[now_eyebrow].img)
new_picture = Blending(new_picture, nostril.ImgBase[now_nostril].img)
new_picture = Blending(new_picture, hat.ImgBase[now_hat].img)
new_picture = Blending(new_picture, clothes.ImgBase[now_clothes].img)
Dino_DataBase.Base.append(DINO(new_picture, i, now_clothes, now_hat, now_nostril, now_eyebrow, now_eye, now_mouth, now_teeth, now_jaw, now_armor, now_body, now_hand, now_bg))
# print(len(Dino_DataBase.Base))
【Step.5 - Save Image】
結束!
Dino_DataBase.show_DataBase()
Dino_DataBase.save_DataBase()
【小結】
跟寫網站相比,合成圖片算是容易許多的!也把我這陣子被摧毀的自信心慢慢治癒哈哈哈哈哈哈。
【參考資料】
Python for Art - Blending Two Images using OpenCV
Blend overlapping images in python
Image-blending using Python and OpenCV
Python影像辨識筆記(三):Open CV操作筆記
overlay a smaller image on a larger image python OpenCv