File size: 7,779 Bytes
f02dec9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | """Procedural object-labelled skins. Labels describe authored objects, not RGB rules."""
import numpy as np
import torch
from torch.utils.data import Dataset
from SkingToolkit.dense_uv_parser.skin_dataset import load_skin
def make_accessory_skin(original, seed, return_components=False):
rng = np.random.default_rng(seed)
uv = original.permute(1, 2, 0).numpy().copy()
objects = np.zeros((64, 64), dtype=np.int64)
components = np.zeros((64,64),dtype=np.int64) # none, inner crown/band, outer crown/band, brim
# All six head faces are authored together. Body pixels remain the source skin.
uv[:16, :32] = 0
uv[:16, 32:] = 0
faces = [(8, 8), (24, 8), (0, 8), (16, 8), (8, 0), (16, 0)]
skin = np.array(rng.choice([[.87,.63,.43],[.45,.28,.19],[.65,.44,.30],
[.95,.78,.62],[.32,.20,.13],[.74,.62,.49]]))
if rng.random() < .12:
skin = rng.uniform(.2, .9, 3)
hair = rng.uniform(.03, .30, 3) if rng.random() < .7 else rng.uniform(.1, .85, 3)
for fi, (x, y) in enumerate(faces):
tex = np.clip(skin[None,None] + rng.normal(0,.025,(8,8,1)), 0, 1)
if fi in (1,4): tex[:] = hair + rng.normal(0,.025,(8,8,1))
if fi in (0,2,3): tex[:rng.integers(1,3)] = hair
uv[y:y+8, x:x+8, :3] = np.clip(tex,0,1)
uv[y:y+8, x:x+8, 3] = 1
# Eyes, eyebrows and mouth are explicit negative facial examples.
for x in (9, 13):
uv[11:12, x:x+2, :3] = rng.uniform(.01,.18,3)
uv[14,11:13,:3] = rng.uniform(.05,.5,3)
if rng.random() < .4:
uv[10,9:11,:3] = hair
uv[10,13:15,:3] = hair
def put(face, mask, color, identity, layer=1, component=0):
x, y = faces[face]; x += 32*layer
patch = uv[y:y+8, x:x+8]
patch[mask, :3] = color[mask] if np.asarray(color).ndim == 3 else color
patch[mask, 3] = 1
objects[y:y+8, x:x+8][mask] = identity
components[y:y+8, x:x+8][mask] = component
yy, xx = np.mgrid[:8,:8]
negative = rng.random() < .18
if not negative and rng.random() < .78:
top = int(rng.integers(2,4)); height = int(rng.integers(3,5))
frame_color = rng.uniform(.025,.23,3) if rng.random()<.70 else rng.uniform(.01,.85,3)
lens_color = rng.uniform(.05,.95,3)
frame = np.zeros((8,8),bool); lenses = np.zeros_like(frame)
# Distinct connected eyewear styles; class ID is independent of palette.
visor = rng.random()<.20
boxes = [(0,3),(4,7)] if visor else [(0,2),(5,7)]
hollow = rng.random()<.30
rimless_top = rng.random()<.45 and not hollow
for box_index,(left, right) in enumerate(boxes):
rectangle = (xx>=left)&(xx<=right)&(yy>=top)&(yy<top+height)
edge = rectangle & ((xx==left)|(xx==right)|(yy==top)|(yy==top+height-1))
if rimless_top:
edge &= (yy!=top)
# Thin inner rim variants preserve two-column lenses on an 8px face.
if not visor and not hollow and rng.random()<.65:
inner_x=right if box_index==0 else left
edge &= ~((xx==inner_x)&(yy<top+height-1))
if rng.random() < .2:
edge &= ~(((xx==left)|(xx==right))&(yy==top+height-1))
frame |= edge
lenses |= rectangle & ~edge
bridge_y = min(7,top+int(rng.integers(0,2)))
frame[bridge_y,2:6] = True
lenses[frame] = False
if hollow: lenses[:] = False # open-frame holes stay empty
color = np.broadcast_to(frame_color,(8,8,3)).copy()
color[lenses] = lens_color
# Lens shading keeps the same object ID across several colours.
color[lenses] *= rng.uniform(.65,1.05,(8,8,1))[lenses]
put(0, frame|lenses, np.clip(color,0,1), 1)
for face in (2,3):
length = int(rng.integers(3,9))
temple = (yy==bridge_y)&(xx<length)
put(face,temple,frame_color,1)
kind = int(rng.choice([0,2,3], p=[.35,.40,.25])) if not negative else 0
if kind==2:
# Geometry and material are independent variables. A whole hat can have
# an inner crown/band and an extruded brim; the band is not background.
color = rng.uniform(.02,.17,3) if rng.random()<.65 else rng.uniform(.05,.85,3)
depth = int(rng.integers(2,6))
stepped = rng.random()<.70
variant = int(rng.choice(3,p=[.75,.15,.10])) # full ring, front visor, partial/asymmetric
band_color = rng.uniform(.04,.95,3)
if rng.random()<.3: band_color=np.array([rng.uniform(.5,.95),rng.uniform(.02,.12),rng.uniform(.02,.12)])
if rng.random()<.2: band_color=color*rng.uniform(.7,1.2)
has_band = rng.random()<.85
crown_layer=0 if stepped else 1
crown_component=1 if stepped else 3
band_component=2 if stepped else 4
put(4,np.ones((8,8),bool),color,0 if stepped else 2,layer=crown_layer,component=crown_component)
for face in (0,1,2,3):
tex=np.clip(np.broadcast_to(color,(8,8,3))+rng.normal(0,.02,(8,8,1)),0,1)
crown=yy<depth-1
put(face,crown,tex,0 if stepped else 2,layer=crown_layer,component=crown_component)
if has_band:
put(face,yy==depth-2,np.clip(band_color,0,1),0 if stepped else 2,layer=crown_layer,component=band_component)
brim=yy==depth-1
if variant==1 and face!=0: brim[:]=False
if variant==2 and face in (1,3): brim &= xx<5
put(face,brim,color,2,component=5)
elif kind==3:
put(4,np.ones((8,8),bool),hair,3)
for face in (0,1,2,3):
depth=int(rng.integers(1,4)) if face==0 else int(rng.integers(2,7))
mask=(yy<depth)|((yy==depth)&(rng.random((8,8))>.45))
tex=np.clip(np.broadcast_to(hair,(8,8,3))+rng.normal(0,.025,(8,8,1)),0,1)
put(face,mask,tex,3)
if return_components:
return torch.from_numpy(uv).permute(2,0,1).float(),torch.from_numpy(objects),torch.from_numpy(components)
return torch.from_numpy(uv).permute(2,0,1).float(), torch.from_numpy(objects)
class AccessoryDataset(Dataset):
def __init__(self, paths, count, seed):
self.paths, self.count, self.seed, self.epoch = list(paths), count, seed, 0
def __len__(self): return self.count
def __getitem__(self, index):
seed = self.seed + index*104729 + self.epoch*self.count*104729
source = load_skin(self.paths[(index + self.epoch*self.count) % len(self.paths)])
uv, objects, components = make_accessory_skin(source, seed, return_components=True)
return {'uv':uv,'objects':objects,'components':components,'seed':seed}
def render_accessories(uv, objects, renderer, views, components=None):
from SkingToolkit.dense_uv_parser.utils import build_dense_parser_batch
images, labels, foregrounds, parts = [], [], [], []
for view in views:
rendered, target = build_dense_parser_batch(uv, renderer, view)
xy = (target['uv']*63).round().long().clamp(0,63)
flat = xy[:,1]*64+xy[:,0]
identity = objects.flatten(1).gather(1,flat.flatten(1)).reshape_as(flat)
identity = identity.masked_fill((target['layer'] != 1) | (target['foreground'][:,0] < .5), 0)
images.append(rendered);labels.append(identity);foregrounds.append(target['foreground'][:,0].bool())
if components is not None:
part=components.flatten(1).gather(1,flat.flatten(1)).reshape_as(flat)
parts.append(part.masked_fill(target['foreground'][:,0]<.5,0))
b = uv.shape[0]
result=(torch.stack(images,1).flatten(0,1),torch.stack(labels,1).flatten(0,1),torch.stack(foregrounds,1).flatten(0,1))
return result+(torch.stack(parts,1).flatten(0,1),) if components is not None else result
|