多图合成一图:
import os
import tqdm
import glob
import json
import math
import random
import numpy as np
from pathlib import Path
from PIL import Image, ImageDraw
foresuffix = '_foreground.jpg'
pseudosuffix = '_pseudo.jpg'
jsonsuffix = '.jpg.json'
# 旋转图片,没有黑边
def rotateImage(img):
image = np.asarray(img)
rotated = np.rot90(image) # 逆时针旋转!
'''
# 通道特殊处理
# Image 通道是 “RGB”
# numpy 通道是 “BGR”
r, g, b = rotated[:, :, 0], rotated[:, :, 1], rotated[:, :, 2]
h, w = r.shape
new = np.zeros((h, w, 3))
new[:, :, 0] = r
new[:, :, 1] = g
new[:, :, 2] = b
'''
newimage = Image.fromarray(np.uint8(rotated))
return newimage
# 规划拼接区域
def splitArea(count, width, height):
if count == 2:
# 左右拼接
if width >= height:
#########
# A #
#########
# B #
#########
w, h = width // 2, height
x1, y = w // 2, h // 2
x2 = x1 * 3
boxlist = [(x1, y, w, h), (x2, y, w, h)]
# 上下拼接
else:
#############
# A # B #
#############
w, h = width, height//2
x, y1 = w // 2, h // 2
y2 = y1 * 3
boxlist = [(x, y1, w, h), (x, y2, w, h)]
elif count == 3:
if width >= height:
#############
# # B #
# A #######
# # C #
#############
w, h1, h2 = width // 2, height, height // 2
x1, y1, y2 = w // 2, h1 // 2, h2 // 2
x2 = x1 * 3
y3 = y2 * 3
boxlist = [(x1, y1, w, h1), (x2, y2, w, h2), (x2, y3, w, h2)]
# 上下拼接
else:
#############
# A #
#############
# B # C #
#############
w1, w2, h = width, width // 2, height // 2
x1, x2, y1 = w1 // 2, w2 // 2, h // 2
y2 = y1 * 3
x3 = x2 * 3
boxlist = [(x1, y1, w1, h), (x2, y2, w2, h), (x3, y2, w2, h)]
elif count == 4:
#############
# A # C #
#############
# B # D #
#############
w, h = width//2, height//2
x1, y1 = w // 2, h // 2
x2, y2 = x1 * 3, y1 * 3
boxlist = [(x1, y1, w, h), (x1, y2, w, h), (x2, y1, w, h), (x2, y2, w, h)]
else:
pass
return boxlist
# 旋转坐标点
def processPoints(pointList, boundingbox, lx, ly, fw, fh, w, h):
pointlist = []
offsetx, offsety = boundingbox[0], boundingbox[1]
for point in pointList:
# 把衣服框从原图中抠出来
x, y = point[0]-offsetx, point[1]-offsety
# 旋转
"""
点point1绕点point2旋转angle后的点
======================================
在平面坐标上,任意点P(x1,y1),绕一个坐标点Q(x2,y2)旋转θ角度后,新的坐标设为(x, y)的计算公式:
x= (x1 - x2)*cos(θ) - (y1 - y2)*sin(θ) + y2 ;
y= (x1 - x2)*sin(θ) + (y1 - y2)*cos(θ) + x2 ;
======================================
将图像坐标(x,y)转换到平面坐标(x`,y`):
x`=x
y`=height-y
:param point1:
:param point2: base point (基点)
:param angle: 旋转角度,正:表示逆时针,负:表示顺时
"""
tfw, tfh = fw, fh
if (w >= h and fw < fh) or (w < h and fw >= fh):
x1, y1 = x, y
rotx, roty = tfw // 2, tfh // 2
# # 将图像坐标转换到平面坐标
y1 = tfh - y1
roty = tfh - roty
# 逆时针
x = (x1 - rotx) * math.cos(math.pi/2) - (y1 - roty) * math.sin(math.pi/2) + roty
y = (x1 - rotx) * math.sin(math.pi/2) + (y1 - roty) * math.cos(math.pi/2) + rotx
# 旋转之后,长宽对调,将平面坐标转换到图像坐标
tfw, tfh = tfh, tfw
y = tfh - y
# 缩放
ratiox, ratioy = w / tfw, h / tfh
x *= ratiox
y *= ratioy
# # 拼接到新图的新区域
x += lx
y += ly
pointlist.append([int(x), int(y)])
return pointlist
# 拼接
def joint(baseimage, forelist, imagesavepath, savejsonpath=None, threshold=50, debug=False):
# 获取基底图片尺寸
base = Image.open(baseimage)
if debug:
print(f"baseimage mode is :{base.mode}")
base.show()
# 设置拼接坐标
count = len(forelist)
assert count >= 2, "ERROR: the count of foregrounds is not enough !"
assert count <= 4, "ERROR: the count of foregrounds is too many !"
width, height = base.size
boxlist = splitArea(count, width, height)
if savejsonpath is not None:
# 拼接原图,从基底图片中截取一小部分进行放大,作为新的图片的背景
box=(0, 0, threshold, threshold)
background = base.crop(box)
background = background.resize((width, height))
else:
# 拼接pseudo 和 foreground,直接生成纯黑背景
background = Image.new("RGB", (width, height), "#000000")
if debug:
background.show()
# 拼接前景图片
jsonlist = []
for idx, (fore, box) in enumerate(zip(forelist, boxlist)):
fimg = Image.open(fore)
boundingbox = fimg.getbbox()
fimg = fimg.crop(boundingbox)
if debug:
print(fore)
fimg.show()
# 获取拼接区域
cx, cy, w, h = box
fw, fh = fimg.size
if (w >= h and fw < fh) or (w < h and fw >= fh):
fimg = rotateImage(fimg)
if debug:
print(fore, "旋转了")
fimg.show()
fimg = fimg.resize((w, h))
if debug:
fimg.show()
# 获取mask
mask, _, _ = fimg.split()
mask = np.array(mask)
mask[mask > 10] = 255
mask[mask <= 10] = 0
mask = Image.fromarray(mask)
# 拼接
lx, ly = cx-w//2, cy-h//2
rx, ry = cx+w//2, cy+h//2
background.paste(fimg, (lx, ly, rx, ry), mask)
# 修改该json文件
if savejsonpath is not None:
readpath = fore.replace(foresuffix, jsonsuffix)
with open(readpath, 'r') as reader:
jsonfile = json.load(reader)
# 如果只有一件衣服的图片中存在多个标注连通域,则警告
if len(jsonfile) > 1 or len(jsonfile) == 0:
with open('./runningLog.json', 'a') as writer:
info = f"WARNING: {fore}\n"
writer.write(info)
# 遍历每个标注区域
for object in jsonfile:
savejson = dict()
savejson['name'] = object['name']
savejson['color'] = object['color']
savejson['labelIdx'] = idx + 1
savejson['points'] = processPoints(object['points'], boundingbox, lx, ly, fw, fh, w, h)
jsonlist.append(savejson)
# 保存图片及json文件
background.save(imagesavepath)
if len(jsonlist) > 0:
with open(savejsonpath, 'w') as writer:
json.dump(jsonlist, writer)
if debug:
background.show()
if __name__ == '__main__':
imageroot = './hx_clothes_1122/hx_clothes_imgs'
maskroot = './hx_clothes_1122/hx_clothes_masks'
idx = 0
for _ in tqdm.tqdm(range(4)):
imagelist = os.listdir(imageroot)
while len(imagelist) > 1:
idx += 1
if len(imagelist) < 4:
count = len(imagelist) # 如果剩余图片不足4张,则都拼一块
else:
count = random.randint(2, 4) # 随机产生【2,4】闭区间上的一个整数
forelist = []
psedolist = []
jsonlist = []
imglist = []
for i in range(count):
img = random.choice(imagelist)
imgpath = os.path.join(imageroot, img)
imglist.append(imgpath)
forename = img.replace('.jpg', foresuffix)
forepath = os.path.join(maskroot, forename)
forelist.append(forepath)
pseudoname = img.replace('.jpg', pseudosuffix)
pseudopath = os.path.join(maskroot, pseudoname)
psedolist.append(pseudopath)
jsonname = img.replace('.jpg', jsonsuffix)
jsonpath = os.path.join(maskroot, jsonname)
jsonlist.append(jsonpath)
imagelist.remove(img)
saveimagename = f"hx_clothes_1122/joint_images/aug_{idx}.jpg"
savejsonname = f"hx_clothes_1122/joint_masks/aug_{idx}.jpg.json"
saveforename = f"hx_clothes_1122/joint_masks/aug_{idx}_foreground.jpg"
savepseudoname = f"hx_clothes_1122/joint_masks/aug_{idx}_pseudo.jpg"
joint(imglist[0], forelist, saveimagename, savejsonname) # , debug=True
joint(forelist[0], forelist, saveforename)
joint(psedolist[0], psedolist, savepseudoname)
with open('./runningLog.json', 'a') as writer:
info = f"{json.dumps(imglist)}\n===> {str(idx)}\n\n"
writer.write(info)
# break
print(f"\r剩余:{len(imagelist)}", end='', flush=True)
# break