Files
colomi 0eb61f3e60 初始化换发型项目:3个微服务代码 + 部署脚本
包含:
- hair_service_sd: 换发型/换发色算法服务 (端口 8801)
- photo_service: LoRA 训练调度服务 (端口 32678)
- stable-diffusion-webui: SD WebUI 推理服务 (端口 57860)
- kohya_ss_home: 训练环境代码
- meidaojia: 监控测试脚本
- setup.sh: 一键部署脚本 (conda环境恢复 + 配置生成 + 完整性检查)
- start_all_services.sh: 启动3个服务
- configure.ini.template: 路径模板化 (BASE_DIR自动推导)
- conda_envs/py310.yml: py310 环境定义

大文件 (weights/, models/, data/, conda_envs/*.tar.gz 等) 通过 .gitignore 排除,
由网盘单独上传。
2026-07-11 18:11:49 +08:00

299 lines
10 KiB
Python

from __future__ import division
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.nn.init as init
import torch.utils.model_zoo as model_zoo
from torchvision import models
# general libs
import cv2
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
import math
import time
import tqdm
import os
import argparse
import copy
import sys
from utils.helpers import *
class ResBlock(nn.Module):
def __init__(self, backbone, indim, outdim=None, stride=1):
super(ResBlock, self).__init__()
self.backbone = backbone
if outdim == None:
outdim = indim
if indim == outdim and stride == 1:
self.downsample = None
else:
self.downsample = nn.Conv2d(indim, outdim, kernel_size=3, padding=1, stride=stride)
self.conv1 = nn.Conv2d(indim, outdim, kernel_size=3, padding=1, stride=stride)
self.conv2 = nn.Conv2d(outdim, outdim, kernel_size=3, padding=1)
def forward(self, x):
if self.backbone == 'resnest101':
r = self.conv1(F.relu(x, inplace=True))
r = self.conv2(F.relu(r, inplace=True))
else:
r = self.conv1(F.relu(x))
r = self.conv2(F.relu(r))
if self.downsample is not None:
x = self.downsample(x)
return x + r
class Encoder_M(nn.Module):
def __init__(self, backbone):
super(Encoder_M, self).__init__()
if backbone == 'resnest101':
self.conv1_m = nn.Conv2d(1, 128, kernel_size=7, stride=2, padding=3, bias=False)
self.conv1_o = nn.Conv2d(1, 128, kernel_size=7, stride=2, padding=3, bias=False)
else:
self.conv1_m = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)
self.conv1_o = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)
if backbone == 'resnet50':
resnet = models.resnet50(pretrained=True)
elif backbone == 'resnet18':
resnet = models.resnet18(pretrained=True)
self.conv1 = resnet.conv1
self.bn1 = resnet.bn1
self.relu = resnet.relu # 1/2, 64
self.maxpool = resnet.maxpool
self.res2 = resnet.layer1 # 1/4, 256
self.res3 = resnet.layer2 # 1/8, 512
self.res4 = resnet.layer3 # 1/8, 1024
self.register_buffer('mean', torch.FloatTensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1))
self.register_buffer('std', torch.FloatTensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1))
def forward(self, in_f, in_m, in_o):
f = (in_f - self.mean) / self.std
m = torch.unsqueeze(in_m, dim=1).float() # add channel dim
o = torch.unsqueeze(in_o, dim=1).float() # add channel dim
x = self.conv1(f) + self.conv1_m(m) + self.conv1_o(o)
x = self.bn1(x)
c1 = self.relu(x) # 1/2, 64
x = self.maxpool(c1) # 1/4, 64
r2 = self.res2(x) # 1/4, 256
r3 = self.res3(r2) # 1/8, 512
r4 = self.res4(r3) # 1/8, 1024
return r4, r3, r2, c1, f
class Encoder_Q(nn.Module):
def __init__(self, backbone):
super(Encoder_Q, self).__init__()
if backbone == 'resnet50':
resnet = models.resnet50(pretrained=True)
elif backbone == 'resnet18':
resnet = models.resnet18(pretrained=True)
self.conv1 = resnet.conv1
self.bn1 = resnet.bn1
self.relu = resnet.relu # 1/2, 64
self.maxpool = resnet.maxpool
self.res2 = resnet.layer1 # 1/4, 256
self.res3 = resnet.layer2 # 1/8, 512
self.res4 = resnet.layer3 # 1/8, 1024
self.register_buffer('mean', torch.FloatTensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1))
self.register_buffer('std', torch.FloatTensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1))
def forward(self, in_f):
f = (in_f - self.mean) / self.std
x = self.conv1(f)
x = self.bn1(x)
c1 = self.relu(x) # 1/2, 64
x = self.maxpool(c1) # 1/4, 64
r2 = self.res2(x) # 1/4, 256
r3 = self.res3(r2) # 1/8, 512
r4 = self.res4(r3) # 1/8, 1024
return r4, r3, r2, c1, f
class Refine(nn.Module):
def __init__(self, backbone, inplanes, planes, scale_factor=2):
super(Refine, self).__init__()
self.convFS = nn.Conv2d(inplanes, planes, kernel_size=(3, 3), padding=(1, 1), stride=1)
self.ResFS = ResBlock(backbone, planes, planes)
self.ResMM = ResBlock(backbone, planes, planes)
self.scale_factor = scale_factor
def forward(self, f, pm):
s = self.ResFS(self.convFS(f))
m = s + F.interpolate(pm, scale_factor=self.scale_factor, mode='bilinear', align_corners=False)
m = self.ResMM(m)
return m
class Decoder(nn.Module):
def __init__(self, mdim, scale_rate, backbone):
super(Decoder, self).__init__()
self.backbone = backbone
if backbone == 'resnest101':
self.convFM = nn.Conv2d(256, mdim, kernel_size=(3, 3), padding=(1, 1), stride=1)
else:
self.convFM = nn.Conv2d(1024 // scale_rate, mdim, kernel_size=(3, 3), padding=(1, 1), stride=1)
self.ResMM = ResBlock(backbone, mdim, mdim)
self.RF3 = Refine(backbone, 512 // scale_rate, mdim) # 1/8 -> 1/4
self.RF2 = Refine(backbone, 256 // scale_rate, mdim) # 1/4 -> 1
self.pred2 = nn.Conv2d(mdim, 2, kernel_size=(3, 3), padding=(1, 1), stride=1)
def forward(self, r4, r3, r2):
m4 = self.ResMM(self.convFM(r4))
m3 = self.RF3(r3, m4) # out: 1/8, 256
m2 = self.RF2(r2, m3) # out: 1/4, 256
if self.backbone == 'resnest101':
p2 = self.pred2(F.relu(m2, inplace=True))
else:
p2 = self.pred2(F.relu(m2))
p = F.interpolate(p2, scale_factor=4, mode='bilinear', align_corners=False)
return p # , p2, p3, p4
class Memory(nn.Module):
def __init__(self):
super(Memory, self).__init__()
def forward(self, m_in, m_out, q_in, q_out): # m_in: o,c,t,h,w
B, D_e, T, H, W = m_in.size()
_, D_o, _, _, _ = m_out.size()
mi = m_in.view(B, D_e, T * H * W)
mi = torch.transpose(mi, 1, 2) # b, THW, emb
qi = q_in.view(B, D_e, H * W) # b, emb, HW
p = torch.bmm(mi, qi) # b, THW, HW
p = p / math.sqrt(D_e)
p = F.softmax(p, dim=1) # b, THW, HW
mo = m_out.view(B, D_o, T * H * W)
mem = torch.bmm(mo, p) # Weighted-sum B, D_o, HW
mem = mem.view(B, D_o, H, W)
mem_out = torch.cat([mem, q_out], dim=1)
return mem_out, p
class KeyValue(nn.Module):
# Not using location
def __init__(self, indim, keydim, valdim):
super(KeyValue, self).__init__()
self.Key = nn.Conv2d(indim, keydim, kernel_size=(3, 3), padding=(1, 1), stride=1)
self.Value = nn.Conv2d(indim, valdim, kernel_size=(3, 3), padding=(1, 1), stride=1)
def forward(self, x):
return self.Key(x), self.Value(x)
class STM(nn.Module):
def __init__(self, backbone='resnet50'):
super(STM, self).__init__()
self.backbone = backbone
assert backbone == 'resnet50' or backbone == 'resnet18' or backbone == 'resnest101'
scale_rate = (1 if (backbone == 'resnet50' or backbone == 'resnest101') else 4)
self.Encoder_M = Encoder_M(backbone)
self.Encoder_Q = Encoder_Q(backbone)
self.KV_M_r4 = KeyValue(1024 // scale_rate, keydim=128 // scale_rate, valdim=512 // scale_rate)
self.KV_Q_r4 = KeyValue(1024 // scale_rate, keydim=128 // scale_rate, valdim=512 // scale_rate)
self.Memory = Memory()
self.Decoder = Decoder(256, scale_rate, backbone)
def Pad_memory(self, mems, num_objects, K):
pad_mems = []
for mem in mems:
pad_mem = ToCuda(torch.zeros(1, K, mem.size()[1], 1, mem.size()[2], mem.size()[3]))
pad_mem[0, 1:num_objects + 1, :, 0] = mem
pad_mems.append(pad_mem)
return pad_mems
def memorize(self, frame, masks, num_objects):
# memorize a frame
num_objects = num_objects[0].item()
_, K, H, W = masks.shape # B = 1
(frame, masks), pad = pad_divide_by([frame, masks], 16, (frame.size()[2], frame.size()[3]))
# make batch arg list
B_list = {'f': [], 'm': [], 'o': []}
for o in range(1, num_objects + 1): # 1 - no
B_list['f'].append(frame)
B_list['m'].append(masks[:, o])
B_list['o'].append((torch.sum(masks[:, 1:o], dim=1) + \
torch.sum(masks[:, o + 1:num_objects + 1], dim=1)).clamp(0, 1))
# make Batch
B_ = {}
for arg in B_list.keys():
B_[arg] = torch.cat(B_list[arg], dim=0)
r4, _, _, _, _ = self.Encoder_M(B_['f'], B_['m'], B_['o'])
k4, v4 = self.KV_M_r4(r4) # num_objects, 128 and 512, H/16, W/16
k4, v4 = self.Pad_memory([k4, v4], num_objects=num_objects, K=K)
return k4, v4
def Soft_aggregation(self, ps, K):
num_objects, H, W = ps.shape
em = ToCuda(torch.zeros(1, K, H, W))
em[0, 0] = torch.prod(1 - ps, dim=0) # bg prob
em[0, 1:num_objects + 1] = ps # obj prob
em = torch.clamp(em, 1e-7, 1 - 1e-7)
logit = torch.log((em / (1 - em)))
return logit
def segment(self, frame, keys, values, num_objects):
num_objects = num_objects[0].item()
_, K, keydim, T, H, W = keys.shape # B = 1
# pad
[frame], pad = pad_divide_by([frame], 16, (frame.size()[2], frame.size()[3]))
r4, r3, r2, _, _ = self.Encoder_Q(frame)
k4, v4 = self.KV_Q_r4(r4) # 1, dim, H/16, W/16
# expand to --- no, c, h, w
k4e, v4e = k4.expand(num_objects, -1, -1, -1), v4.expand(num_objects, -1, -1, -1)
r3e, r2e = r3.expand(num_objects, -1, -1, -1), r2.expand(num_objects, -1, -1, -1)
# memory select kv:(1, K, C, T, H, W)
m4, viz = self.Memory(keys[0, 1:num_objects + 1], values[0, 1:num_objects + 1], k4e, v4e)
logits = self.Decoder(m4, r3e, r2e)
ps = F.softmax(logits, dim=1)[:, 1] # no, h, w
# ps = indipendant possibility to belong to each object
logit = self.Soft_aggregation(ps, K) # 1, K, H, W
if pad[2] + pad[3] > 0:
logit = logit[:, :, pad[2]:-pad[3], :]
if pad[0] + pad[1] > 0:
logit = logit[:, :, :, pad[0]:-pad[1]]
return logit
def forward(self, *args, **kwargs):
if args[1].dim() > 4: # keys
return self.segment(*args, **kwargs)
else:
return self.memorize(*args, **kwargs)