save code

This commit is contained in:
colomi
2025-07-13 09:55:49 +08:00
parent 92b1c9b3c6
commit bcebd31667
4 changed files with 582 additions and 304 deletions
+338 -6
View File
@@ -1,7 +1,7 @@
{
"3": {
"inputs": {
"seed": 351739583278114,
"seed": 712463095845292,
"steps": 20,
"cfg": 1,
"sampler_name": "euler",
@@ -432,6 +432,18 @@
"title": "Image Concatenate"
}
},
"45": {
"inputs": {
"images": [
"42",
0
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "Preview Image"
}
},
"57": {
"inputs": {
"brightness": -1,
@@ -474,7 +486,7 @@
},
"61": {
"inputs": {
"image": "girl_full_2.webp"
"image": "girl_full.jpg"
},
"class_type": "LoadImage",
"_meta": {
@@ -505,6 +517,126 @@
"title": "Load Style Model"
}
},
"83": {
"inputs": {
"images": [
"29",
0
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "Preview Image"
}
},
"84": {
"inputs": {
"images": [
"43",
0
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "Preview Image"
}
},
"85": {
"inputs": {
"mask": [
"30",
0
]
},
"class_type": "MaskPreview",
"_meta": {
"title": "MaskPreview"
}
},
"86": {
"inputs": {
"mask": [
"71:2",
1
]
},
"class_type": "MaskPreview+",
"_meta": {
"title": "🔧 Mask Preview"
}
},
"87": {
"inputs": {
"images": [
"38",
0
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "Preview Image"
}
},
"88": {
"inputs": {
"images": [
"37",
0
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "Preview Image"
}
},
"89": {
"inputs": {
"images": [
"57",
0
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "Preview Image"
}
},
"90": {
"inputs": {
"images": [
"59",
0
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "Preview Image"
}
},
"91": {
"inputs": {
"images": [
"27",
1
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "Preview Image"
}
},
"93": {
"inputs": {
"mask": [
"27",
2
]
},
"class_type": "MaskPreview",
"_meta": {
"title": "MaskPreview"
}
},
"94": {
"inputs": {
"value": 1024
@@ -535,7 +667,8 @@
},
"96": {
"inputs": {
"changdu": "yao",
"cloth_len": "yao",
"line_len": 0.6000000000000001,
"kps": [
"95",
1
@@ -546,11 +679,44 @@
"title": "My RenderPeopleKps Mask"
}
},
"97": {
"inputs": {
"mask": [
"61",
1
]
},
"class_type": "MaskPreview",
"_meta": {
"title": "MaskPreview"
}
},
"98": {
"inputs": {
"mask": [
"96",
0
]
},
"class_type": "MaskPreview",
"_meta": {
"title": "MaskPreview"
}
},
"99": {
"inputs": {
"width": 600,
"height": 1024,
"x": 700,
"width": [
"108",
0
],
"height": [
"107",
1
],
"x": [
"109",
0
],
"y": 0,
"image": [
"34",
@@ -575,6 +741,172 @@
"title": "Save Image"
}
},
"104": {
"inputs": {
"image": [
"61",
0
]
},
"class_type": "GetImageSize",
"_meta": {
"title": "Get Image Size"
}
},
"106": {
"inputs": {
"op": "Mul",
"a": [
"107",
1
],
"b": [
"104",
0
]
},
"class_type": "CM_IntBinaryOperation",
"_meta": {
"title": "IntBinaryOperation"
}
},
"107": {
"inputs": {
"image": [
"34",
0
]
},
"class_type": "GetImageSize",
"_meta": {
"title": "Get Image Size"
}
},
"108": {
"inputs": {
"op": "Div",
"a": [
"106",
0
],
"b": [
"104",
1
]
},
"class_type": "CM_IntBinaryOperation",
"_meta": {
"title": "IntBinaryOperation"
}
},
"109": {
"inputs": {
"op": "Sub",
"a": [
"107",
0
],
"b": [
"108",
0
]
},
"class_type": "CM_IntBinaryOperation",
"_meta": {
"title": "IntBinaryOperation"
}
},
"110": {
"inputs": {
"preview": "",
"source": [
"106",
0
]
},
"class_type": "PreviewAny",
"_meta": {
"title": "Preview Any"
}
},
"111": {
"inputs": {
"preview": "",
"source": [
"108",
0
]
},
"class_type": "PreviewAny",
"_meta": {
"title": "Preview Any"
}
},
"112": {
"inputs": {
"preview": "",
"source": [
"109",
0
]
},
"class_type": "PreviewAny",
"_meta": {
"title": "Preview Any"
}
},
"113": {
"inputs": {
"preview": "",
"source": [
"107",
0
]
},
"class_type": "PreviewAny",
"_meta": {
"title": "Preview Any"
}
},
"114": {
"inputs": {
"preview": "",
"source": [
"107",
1
]
},
"class_type": "PreviewAny",
"_meta": {
"title": "Preview Any"
}
},
"115": {
"inputs": {
"preview": "",
"source": [
"104",
0
]
},
"class_type": "PreviewAny",
"_meta": {
"title": "Preview Any"
}
},
"116": {
"inputs": {
"preview": "",
"source": [
"104",
1
]
},
"class_type": "PreviewAny",
"_meta": {
"title": "Preview Any"
}
},
"71:0": {
"inputs": {
"model_name": "sam_vit_h (2.56GB)"
+61 -71
View File
@@ -3,83 +3,70 @@ import uuid
import requests
from flask import Flask, request, jsonify
from urllib.parse import urlparse
from volcenginesdkarkruntime import Ark
import json
import base64
import time
from PIL import Image
from datetime import datetime
from volcenginesdkarkruntime import Ark
import shutil
APP_ROOT = os.path.dirname(os.path.abspath(__file__))
STATIC_FOLDER = os.path.join(APP_ROOT, 'static')
app = Flask(__name__)
# 请确保您已将 API Key 存储在环境变量 ARK_API_KEY 中
# 初始化Ark客户端,从环境变量中读取您的API Key
client = Ark(
# 此为默认路径,您可根据业务所在地域进行配置
base_url="https://ark.cn-beijing.volces.com/api/v3",
# 从环境变量中获取您的 API Key。此为默认方式,您可根据需要进行修改
# api_key=os.environ.get("ARK_API_KEY"),
api_key='14fc0280-fc65-462d-ac2d-50178c0212e3'
)
def parse_facial_data(data_str):
print(f'原始字符串 {data_str}')
# 去除多余的标记和换行符
json_str_clean = data_str.strip('```json\n').strip('```').strip()
# 转换为Python字典
data = json.loads(json_str_clean)
s = data['面部年龄']
s = s.replace('', '')
parts = s.split('-')
start_age = int(parts[0])
end_age = int(parts[1])
data['start_age'] = start_age
data['end_age'] = end_age
return data
# 此为默认路径,您可根据业务所在地域进行配置
base_url="https://ark.cn-beijing.volces.com/api/v3",
# 从环境变量中获取您的 API Key。此为默认方式,您可根据需要进行修改
# api_key=os.environ.get("ARK_API_KEY"),
api_key='14fc0280-fc65-462d-ac2d-50178c0212e3'
)
def GetPicDesc(img_url):
response = client.chat.completions.create(
# 指定您创建的方舟推理接入点 ID,此处已帮您修改为您的推理接入点 ID
model="doubao-1.5-vision-pro-250328",
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": img_url
response = client.chat.completions.create(
# 指定您创建的方舟推理接入点 ID,此处已帮您修改为您的推理接入点 ID
model="doubao-1.5-vision-pro-250328",
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": img_url
},
},
},
{"type": "text", "text": "告诉我图片中的服装长度, 只要答案, 选项有(腰部以上、腰、跨、大腿上部1/3、大腿中部、膝盖、小腿、小腿上部1/3、脚踝、拖地、难以辨认)"},
],
}
],
)
{"type": "text", "text": "图片是一件服装的照片,告诉我图片中的服装穿到正常人身上的长度会覆盖到人体的哪个部位, 只要答案, 选项有(胸、腰、跨、大腿、膝盖、小腿、脚踝、拖地、难以辨认)"},
],
}
],
)
text = response.choices[0].message.content
result = {}
text = response.choices[0].message.content
print(text)
return text
try:
result = parse_facial_data(text)
except:
print(f'Error {text}')
def change(human_name, cloth_name, c_width, c_height, cloth_url):
return result
cloth_len = GetPicDesc(cloth_url)
if cloth_len == '难以辨认':
return None, "get image type error"
queue = requests.get("http://localhost:8188/queue").json()
if queue["queue_running"] or queue["queue_pending"]:
return None, "cur gpu is busy"
def change(human_name, cloth_name, c_width, c_height):
with open('/home/szlc/code/ComfyUI/change_cloth/change_new.json', 'r', encoding='utf-8') as file:
prompt_text = file.read()
prompt = json.loads(prompt_text)
prompt["99"]["inputs"]["width"] = int((c_width/c_height) * 1024)
prompt["96"]["inputs"]["cloth_len"] = cloth_len
# prompt["99"]["inputs"]["width"] = int((c_width/c_height) * 1024)
#input cloth img
prompt["22"]["inputs"]["image"] = cloth_name
@@ -109,14 +96,16 @@ def change(human_name, cloth_name, c_width, c_height):
# 3. 从历史记录中获取结果
history = requests.get("http://localhost:8188/history").json()
print("History:", history)
# print("History:", history)
outputs = history[prompt_id]["outputs"]
for out in outputs:
if 'images' in outputs[out]:
for out_img_name in outputs[out]['images']:
if 'filename' in out_img_name:
return out_img_name['filename']
return None
for out_img in outputs[out]['images']:
if 'filename' in out_img:
out_img_file_name = out_img['filename']
if out_img_name in out_img_file_name:
return out_img_file_name, "success"
return None, "can not find output image"
def save_base64_image(base64_str, prefix):
"""
@@ -146,7 +135,7 @@ def save_base64_image(base64_str, prefix):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
unique_id = str(uuid.uuid4())[:8]
filename = f"{prefix}_{timestamp}_{unique_id}{file_ext}"
filepath = os.path.join('/home/szlc/code/ComfyUI/change_cloth/static', filename)
filepath = os.path.join('/home/szlc/code/ComfyUI/change_cloth/static/imgs', filename)
# 解码并保存图片
with open(filepath, 'wb') as f:
@@ -192,16 +181,17 @@ def image_to_base64(file_path, mime_type=None):
# 组合成Data URI格式
return f"data:{mime_type};base64,{encoded_string}"
def process_change_cloth(human_filename, cloth_filename, output_format):
def process_change_cloth(human_filename, cloth_filename, output_format, img_url):
w,h = get_image_dimensions(f'/home/szlc/code/ComfyUI/input/{human_filename}')
out_put_name = change(human_filename, cloth_filename, w, h)
out_put_name, msg = change(human_filename, cloth_filename, w, h, img_url)
if out_put_name == None:
return jsonify({"ret":-1, 'msg': 'Failed to change cloth'}), 500
print(f'Failed to change cloth {msg}')
return jsonify({"ret":-1, 'msg': f'Failed to change cloth {msg}'}), 200
image = Image.open(f'/home/szlc/code/ComfyUI/output/{out_put_name}')
jpg_name = out_put_name.replace(".png", ".jpg")
jpg_path_name = f'/home/szlc/code/ComfyUI/change_cloth/static/{jpg_name}'
jpg_path_name = f'/home/szlc/code/ComfyUI/change_cloth/static/imgs/{jpg_name}'
image.save(jpg_path_name, quality=95)
if 'base64' in output_format:
@@ -214,7 +204,7 @@ def process_change_cloth(human_filename, cloth_filename, output_format):
return jsonify({
"ret":0,
"msg":"success",
"url":f"http://112.126.94.241:18018/static/{jpg_name}"
"url":f"http://112.126.94.241:18888/static/imgs/{jpg_name}"
})
@app.route('/change_cloth_base64', methods=['POST'])
@@ -242,7 +232,9 @@ def change_cloth_base64():
if not cloth_filename:
return jsonify({"ret":-1, 'msg': 'Failed to save cloth image'}), 500
return process_change_cloth(human_filename, cloth_filename, output_format)
img_url = f"http://112.126.94.241:18888/static/imgs/{cloth_filename}"
return process_change_cloth(human_filename, cloth_filename, output_format, img_url)
except Exception as e:
return jsonify({"ret":-1, 'error': str(e)}), 500
@@ -269,16 +261,14 @@ def save_image_from_url(image_url):
# 获取原始图片格式
ext = get_file_extension(image_url)
filename = f"{uuid.uuid4()}{ext}"
filepath = os.path.join('/home/szlc/code/ComfyUI/change_cloth/static', filename)
filepath = os.path.join('/home/szlc/code/ComfyUI/change_cloth/static/imgs', filename)
with open(filepath, "wb") as f:
for chunk in response.iter_content(1024):
f.write(chunk)
input_filepath = os.path.join('/home/szlc/code/ComfyUI/input', filename)
with open(input_filepath, "wb") as f:
for chunk in response.iter_content(1024):
f.write(chunk)
shutil.copy(filepath, input_filepath)
return filename
except Exception as e:
@@ -309,7 +299,7 @@ def change_cloth():
output_format = data.get('output_format')
return process_change_cloth(human_filename, cloth_filename, output_format)
return process_change_cloth(human_filename, cloth_filename, output_format, cloth_url)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8018, debug=True)
app.run(host="0.0.0.0", port=8888, debug=True)
-227
View File
@@ -1,227 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>服装更换工具</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
h1 {
text-align: center;
color: #333;
}
.image-upload-container {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.image-upload-box {
width: 48%;
border: 2px dashed #ccc;
padding: 15px;
text-align: center;
border-radius: 5px;
}
.image-upload-box h3 {
margin-top: 0;
}
.image-preview {
max-width: 100%;
max-height: 300px;
margin-top: 10px;
display: none;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
display: block;
margin: 20px auto;
}
button:hover {
background-color: #45a049;
}
#result {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
display: none;
}
#loading {
text-align: center;
display: none;
margin: 20px 0;
}
.spinner {
border: 5px solid #f3f3f3;
border-top: 5px solid #3498db;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 2s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
color: red;
}
</style>
</head>
<body>
<h1>服装更换工具</h1>
<div class="image-upload-container">
<div class="image-upload-box">
<h3>人体图片</h3>
<input type="file" id="humanInput" accept="image/*">
<img id="humanPreview" class="image-preview" alt="人体图片预览">
</div>
<div class="image-upload-box">
<h3>衣服图片</h3>
<input type="file" id="clothInput" accept="image/*">
<img id="clothPreview" class="image-preview" alt="衣服图片预览">
</div>
</div>
<button id="submitBtn">提交处理</button>
<div id="loading">
<div class="spinner"></div>
<p>正在处理中,请稍候...</p>
</div>
<div id="result"></div>
<script>
// 图片预览功能
document.getElementById('humanInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(event) {
const img = document.getElementById('humanPreview');
img.src = event.target.result;
img.style.display = 'block';
};
reader.readAsDataURL(file);
}
});
document.getElementById('clothInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(event) {
const img = document.getElementById('clothPreview');
img.src = event.target.result;
img.style.display = 'block';
};
reader.readAsDataURL(file);
}
});
// 提交处理
document.getElementById('submitBtn').addEventListener('click', function() {
const humanFile = document.getElementById('humanInput').files[0];
const clothFile = document.getElementById('clothInput').files[0];
if (!humanFile || !clothFile) {
alert('请同时选择人体图片和衣服图片!');
return;
}
// 显示加载动画
document.getElementById('loading').style.display = 'block';
document.getElementById('result').style.display = 'none';
// 读取图片并转换为Base64(包含完整前缀)
Promise.all([
readFileAsDataURL(humanFile),
readFileAsDataURL(clothFile)
]).then(([humanDataURL, clothDataURL]) => {
// 准备请求数据
const data = {
human_img: humanDataURL, // 包含完整前缀的Base64
cloth_img: clothDataURL, // 包含完整前缀的Base64
output_format: "url"
};
console.log("准备发送的数据:", data); // 调试用
// 调用API
fetch('http://112.126.94.241:18888/change_cloth_base64', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP错误! 状态: ${response.status}`);
}
return response.json();
})
.then(data => {
// 隐藏加载动画
document.getElementById('loading').style.display = 'none';
// 显示结果
const resultDiv = document.getElementById('result');
if (data.url) {
resultDiv.innerHTML = `
<h3>处理结果</h3>
<p>处理成功!点击查看结果:</p>
<a href="${data.url}" target="_blank">${data.url}</a>
<p><img src="${data.url}" style="max-width: 100%; margin-top: 10px;"></p>
`;
} else {
resultDiv.innerHTML = `
<h3>处理结果</h3>
<p class="error">处理完成,但未返回预期的URL</p>
<p>返回数据:</p>
<pre>${JSON.stringify(data, null, 2)}</pre>
`;
}
resultDiv.style.display = 'block';
})
.catch(error => {
console.error('Error:', error);
document.getElementById('loading').style.display = 'none';
document.getElementById('result').innerHTML = `
<h3 class="error">错误</h3>
<p>处理过程中出现错误:${error.message}</p>
<p>请检查控制台获取更多信息</p>
`;
document.getElementById('result').style.display = 'block';
});
});
});
// 辅助函数:读取文件为DataURL(包含完整前缀的Base64
function readFileAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(file);
});
}
</script>
</body>
</html>
+183
View File
@@ -0,0 +1,183 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>人体换装工具</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
.container {
display: flex;
flex-direction: column;
gap: 20px;
}
.input-group {
display: flex;
flex-direction: column;
gap: 5px;
}
input[type="text"] {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
width: 100%;
box-sizing: border-box;
}
button {
padding: 10px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
position: relative;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.button-loading::after {
content: "";
position: absolute;
width: 16px;
height: 16px;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
border: 4px solid transparent;
border-top-color: #ffffff;
border-radius: 50%;
animation: button-loading-spinner 1s ease infinite;
}
@keyframes button-loading-spinner {
from {
transform: rotate(0turn);
}
to {
transform: rotate(1turn);
}
}
.result {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
display: none;
}
.result-image {
max-width: 100%;
margin-top: 10px;
border: 1px solid #eee;
}
.error {
color: red;
margin-top: 10px;
display: none;
}
</style>
</head>
<body>
<div class="container">
<h1>人体换装工具</h1>
<div class="input-group">
<label for="human-url">人体图片URL:</label>
<input type="text" id="human-url" value="http://112.126.94.241:18888/static/imgs/human_20250713_091718_fb1f8a05.jpg">
</div>
<div class="input-group">
<label for="cloth-url">衣服图片URL:</label>
<input type="text" id="cloth-url" value="http://112.126.94.241:18888/static/imgs/cloth_20250713_092536_279335d2.jpg">
</div>
<button id="submit-btn">提交处理</button>
<div id="error-message" class="error"></div>
<div id="result" class="result">
<h3>处理结果:</h3>
<p>处理后的图片URL: <a id="result-url" href="" target="_blank"></a></p>
<img id="result-image" class="result-image" src="" alt="处理后的图片">
</div>
</div>
<script>
document.getElementById('submit-btn').addEventListener('click', async function() {
const humanUrl = document.getElementById('human-url').value;
const clothUrl = document.getElementById('cloth-url').value;
const submitBtn = document.getElementById('submit-btn');
// 隐藏之前的结果和错误
document.getElementById('result').style.display = 'none';
document.getElementById('error-message').style.display = 'none';
// 验证URL是否为空
if (!humanUrl || !clothUrl) {
showError('请输入人体图片和衣服图片的URL');
return;
}
// 设置按钮为加载状态
submitBtn.disabled = true;
submitBtn.classList.add('button-loading');
submitBtn.textContent = '处理中...';
try {
// 发送POST请求
const response = await fetch('http://112.126.94.241:18888/change_cloth', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
human_url: humanUrl,
cloth_url: clothUrl,
output_format: 'url'
})
});
const data = await response.json();
if (data.ret === 0 && data.url) {
// 显示处理结果
const resultUrl = document.getElementById('result-url');
const resultImage = document.getElementById('result-image');
resultUrl.href = data.url;
resultUrl.textContent = data.url;
resultImage.src = data.url;
document.getElementById('result').style.display = 'block';
} else {
showError('处理失败: ' + (data.message || '未知错误'));
}
} catch (error) {
showError('请求出错: ' + error.message);
} finally {
// 恢复按钮状态
submitBtn.disabled = false;
submitBtn.classList.remove('button-loading');
submitBtn.textContent = '提交处理';
}
});
function showError(message) {
const errorElement = document.getElementById('error-message');
errorElement.textContent = message;
errorElement.style.display = 'block';
}
</script>
</body>
</html>