1 总叙
本文主要介绍将两张图片合成为一张图片的方法,包括:将一张图片粘贴至另一张图片的paste()
函数、将指定目标与背景融合的seamlessClone()
函数和通过位运算合成两张 mask 图的bitewise
系列函数。
2. paste() 函数
PIL
库中的paste()
函数的作用是将一张前景图片覆盖到另一张背景图片的指定位置上。函数的声明为
def paste (self, img, box=None, mask=None)
其中:参数 img 表示背景图片,参数 box 是坐标,参数 mask 表示前景图片的蒙版。如是具有透明度的前景图像,则 alpha 通道将用作遮罩。前景图片要求是带有 mask 的 png 图片,背景图片则不做要求。如是 jpg 图片,转为 png 格式即可。废话不多说,下面上代码
# _*_coding:utf-8_*_
from PIL import Image
def myPaste(fgr_path, bgd_path, save_path):
# 前景 (png)
fgr = Image.open(fgr_path)
# 前景
bgd = Image.open(bgd_path)
bgd = bgd.convert('RGBA') # 转为 png
# bgd = Image.new("RGBA", (3840, 2160), (155, 255, 120)) # 绿幕背景
# bgd = Image.new("RGBA", (3840, 2160)) # 透明背景
# 粘贴
(a1, a2) = fgr.size
(b1, b2) = bgd.size
dot = ((b1 - a1) // 2, (b2 - a2) // 2) # 左上角坐标
bgd.paste(fgr, dot, fgr)
bgd.save(save_path)
return bgd
if __name__ == "__main__":
fgr_path = r"vehicle.png"
bgd_path = r"bgd.jpg"
save_path = r"out.png"
out = myPaste(fgr_path, bgd_path, save_path)
out.show()
效果如下
![]() | ![]() |
---|---|
背景图 | 前景图 |
![]() | ![]() |
合成图片背景 | 合成绿幕背景 |
3. seamlessClone() 函数
泊松融合是图像处理领域著名的图像融合算法,seamlessClone()
函数是OpenCV
中泊松融合的实现方法。函数的声明为
def seamlessClone (src, dst, mask, p, blend, flags )
其中:src 是源图像,dst 是目标图像,mask 是遮罩,p 是融合位置中心点坐标,blend 是融合结果,flags 是融合类别(有 NORMAL_CLONE、MIXED_CLONE 和 MONOCHROME_TRANSFER三类)。废话说完了,开始上代码
# _*_coding:utf-8_*_
import cv2
import numpy as np
def mySeamlessClone(fgr_path, bgd_path):
obj = cv2.imread(fgr_path)
im = cv2.imread(bgd_path)
# 创建全白的 mask
mask = 255 * np.ones(obj.shape, obj.dtype)
# 背景图片的中心点
width, height, channels = im.shape
center = (height // 2, width // 2)
# 三种不同的融合方式
normal_clone = cv2.seamlessClone(obj, im, mask, center, cv2.NORMAL_CLONE)
mixed_clone = cv2.seamlessClone(obj, im, mask, center, cv2.MIXED_CLONE)
monochrome_clone = cv2.seamlessClone(obj, im, mask, center, cv2.MONOCHROME_TRANSFER)
# 结果保存
cv2.imwrite("normal-clone.jpg", normal_clone)
cv2.imwrite("mixed-clone.jpg", mixed_clone)
cv2.imwrite("monochrome-clone.jpg", monochrome_clone)
if __name__ == "__main__":
fgr_path = r"love.jpg"
bgd_path = r"wood.jpg"
mySeamlessClone(fgr_path, bgd_path)
效果如下
![]() | ![]() |
---|---|
背景图 | 前景图 |
![]() | ![]() | ![]() |
---|---|---|
Normal | Mixed | Monochrome |
4. bitwise_or() 或运算
图像的位运算是指对图像的数值按照二进制值逐位进行取反、与、或、异或操作。比如按位取反就是将数值根据每个 bit 位 1 变0,0 变 1,比如 0xf0 按位取反就变成了 0x0f。如果是 uint8 类型的数据,取反前后的数据相加结果为 0xff(255)。图像的或运算主要用于获取两张图像加起来的部分。相关代码如下
# _*_coding:utf-8_*_
import cv2
import numpy as np
from PIL import Image
# 闭运算:先膨胀后腐蚀
def closed(img):
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
return img
# 图片调整
def img_convert(img, dot):
tran_bgd = Image.new("RGBA", (1920, 1080))
tran_bgd.paste(img, dot, img)
return tran_bgd
def myPaste(fgr_path, bgd_path, com_save, pha_paste_path):
# 电动车
vehicle = Image.open(bgd_path)
vehicle = vehicle.resize((554, 536), Image.ANTIALIAS)
dot1 = (640, 180)
bgd = img_convert(vehicle, dot1)
pha_bgd = bgd.split()[-1]
# 女孩
girl = Image.open(fgr_path)
girl = girl.resize((280, 800), Image.ANTIALIAS)
dot2 = (950, 5)
fgr = img_convert(girl, dot2)
pha_fgr = fgr.split()[-1]
# paste方法合成
bgd.paste(fgr, (0, 0), fgr)
bgd.save(com_save)
bgd_green = Image.new("RGBA", (1920, 1080), (155, 255, 120))
bgd_green.paste(bgd, (0, 0), bgd)
bgd_green.save(com_save.replace('com', 'green'))
pha_paste = bgd.split()[-1]
pha_paste.save(pha_paste_path)
# or方法合成
e = np.array(pha_bgd)
f = np.array(pha_fgr)
pha_or = cv2.bitwise_or(e, f)
cv2.imwrite(pha_paste_path.replace('paste', 'or'), pha_or)
return
if __name__ == "__main__":
fgr_path = r"girl.png"
bgd_path = r"vehicle.png"
com_save = r"com.png"
pha_paste = r"pha_paste.jpg"
out = myPaste(fgr_path, bgd_path, com_save, pha_paste)
image = cv2.imread(com_save, -1)
pha_com = cv2.split(image)[-1]
pha_com = closed(pha_com)
cv2.imwrite(pha_paste.replace('paste', 'close'), pha_com)
以上代码主要比较了paste()
和bitwise_or()
两种方法的不同。效果如下
![]() | ![]() | ![]() |
---|---|---|
电动车 | 女舞者 | 合成效果 |
![]() | ![]() | ![]() |
Paste方法 | 闭运算 | 或运算 |
5. 传统方法
至于传统方法,就是更改背景图片的像素值,涉及大量矩阵的处理。思路就是用前景的像素值覆盖背景中选定区域的像素值,方法太过繁琐低效。故而此处只记录这一解决思路,并不基于这一思路进行实现。
【参考】