【前言】
在 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
<<: Day 21 - 实战演练 — Button / ButtonGroup / IconButton
>>: Day 22. Zabbix 通知设定 - SMTP - Mail
最一开始的程序是机器代码(machine code),演变成组合代码(assembly code),...
Quantum Key Distribution Polarisation can be one o...
目录 JavaScript Day01 - 说明 说明与工具 JavaScript Day02 - ...
随着技术的进步,现在的蓝牙相较於早期 除了能达成所需的功耗愈来愈低以外,资料的传输量却能做得愈来愈高...
NSubstitute 基本介绍与安装 NSubstitute(简称 NSub)是一套友善的 .NE...