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

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 21 - 实战演练 — Button / ButtonGroup / IconButton

>>:  Day 22. Zabbix 通知设定 - SMTP - Mail

Day2关於『程序』的起源和特性&演算法

最一开始的程序是机器代码(machine code),演变成组合代码(assembly code),...

Day 27 Quantum Protocols and Quantum Algorithms

Quantum Key Distribution Polarisation can be one o...

JavaScript Day31 - 系列目录

目录 JavaScript Day01 - 说明 说明与工具 JavaScript Day02 - ...

[Day28]无线耳机与蓝牙技术-组合应用

随着技术的进步,现在的蓝牙相较於早期 除了能达成所需的功耗愈来愈低以外,资料的传输量却能做得愈来愈高...

Day 16-隔离框架 (isolation Framework) - NSubstitute 基本介绍 (核心技术-8)

NSubstitute 基本介绍与安装 NSubstitute(简称 NSub)是一套友善的 .NE...