iT邦幫忙

2021 iThome 鐵人賽

DAY 24
1
Modern Web

All In One NFT Website Development系列 第 24

Day 24【Random Picture Blending in Python】return bool;

  • 分享至 

  • xImage
  •  

hk11gv14riy11.png

【前言】
在 Project 之中剛好有一份工作是要把每個部件合成,雖然跟主題沒有關聯不過因為篇幅好像還夠,就把我使用的方法分享給大家!我研究了一陣子不知道要用什麼 Hash Function,如果大家有覺得很適合的方法歡迎提供給我。

【Step.0 -Blending Rules】
我們先來看看圖片組合的規則吧!每隻恐龍總共有 12 個部位,某些部位需要和某些部位是同一個顏色,而某些部位的樣式也需要是一組的。

body_1-green.png

Blending Order ( from top layer to bottom layer ):

  1. clothes
  2. hat
  3. nostril
  4. eyebrow (color-tied)
  5. eye
  6. mouth (shape-tied)
  7. teeth (shape-tied)
  8. jaw (shape-tied) (color-tied)
  9. armor
  10. body (color-tied)
  11. hand (color-tied)
  12. bg (background)

Blending Rules:

  1. eyebrow, jaw, body, hand 需要綁定顏色,目前有 green, blue 和 aqua 三種。
  2. mouth, teeth, jaw 需要綁定下顎形狀,有分 open 和 closed 兩種。

Name Rules:

  1. 元件命名方式:部位_編號-(顏色/形狀)

  2. 一個檔案(不論圖案、顏色)就算一個編號

  3. 不同部位獨自編號(牙齒獨立編號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 才能特別縮放),會以原本的解析度呈現,如果圖片太大或者電腦螢幕太小就會顯示不完全。所以特別增加一個專門用來顯示的圖片,如此也比較模組化可以特別調整想顯示的圖片。

如果對字串格式化的處理有疑問可以看這篇文章。

[Python] 字串格式化

建造部件資料庫,也就是眼睛、嘴巴、顏色…等。比較特別的是因為每張圖片的命名方式都不同,如果需要遍歷一個資料夾裡面的所有檔案的話要使用到。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"

blend.png

可以透過 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 hashinglinked-listchain,但發現機率太低,不如重新做一個比較簡單!

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()

#0.jpg

【小結】
跟寫網站相比,合成圖片算是容易許多的!也把我這陣子被摧毀的自信心慢慢治癒哈哈哈哈哈哈。

【參考資料】
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


上一篇
Day 23【Tokens' Owner】FUN SIDE PROJECT
下一篇
Day 25【Deploy NFT - Layers Blending & MetaData】Read the License
系列文
All In One NFT Website Development32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
alincode
iT邦研究生 5 級 ‧ 2021-11-14 18:55:27

有圖壞掉了說 /images/emoticon/emoticon06.gif

我要留言

立即登入留言