save code

This commit is contained in:
Your Name
2026-03-08 16:02:37 +00:00
parent 339e7dc1f6
commit 01fa717f5f
40 changed files with 196 additions and 22 deletions
+108
View File
@@ -0,0 +1,108 @@
# change_app.py API 文档
## 1. GET /
返回主页静态文件 `index.html`
---
## 2. POST /change_cloth
**推荐入口**,带 Redis 队列排队,避免并发冲突。
### 请求参数(JSON
| 参数 | 类型 | 必选 | 说明 |
|------|------|------|------|
| human_url | string | 是 | 人物图片 URL |
| cloth_url | string | 是 | 服装图片 URL |
| output_format | string | 是 | 输出格式,包含 `"base64"` 则返回 base64,否则返回 URL |
| no2 | bool | 否 | true=跳过第一步(脱衣/生成写真),直接换衣;默认 false |
| tuodi | bool | 否 | true=使用拖地款工作流;默认 false |
| kuzi | bool | 否 | 是否同时换裤子(配合 kuzi_url);默认 false |
| cloth_len | string | 否 | 手动指定服装长度(如"膝盖""拖地"等),不传则 AI 自动判断 |
| suit | bool | 否 | true=使用 Gemini 模型换装(套装模式);默认 false |
### 响应
```json
// 成功(URL 模式)
{
"ret": 0,
"state": 0,
"msg": "success",
"url": "https://xiangsilian.oss-cn-beijing.aliyuncs.com/xxx.jpg",
"second_url": "换衣前中间步骤图 URL",
"first_url": "第一步(脱衣/写真)图 URL",
"is_girl": true
}
// 成功(base64 模式)
{
"ret": 0,
"state": 0,
"msg": "success",
"data": "data:image/jpeg;base64,...",
"second_step_data": "...",
"first_step_data": "...",
"is_girl": true
}
// 失败
{ "ret": -1, "state": -1, "msg": "错误信息" }
```
---
## 3. POST /change_cloth_base64
`/change_cloth` 功能相同,图片以 Base64 格式传入。
### 请求参数(JSON
| 参数 | 类型 | 必选 | 说明 |
|------|------|------|------|
| human_img | string | 是 | 人物图片 base64(带 data: 头,如 `data:image/jpeg;base64,...` |
| cloth_img | string | 是 | 服装图片 base64 |
| output_format | string | 是 | 同上 |
| kuzi_img | string | 否 | 裤子图片 base64 |
| no2 | bool | 否 | 同上 |
| tuodi | bool | 否 | 同上 |
| suit | bool | 否 | 同上 |
| cloth_len | string | 否 | 同上 |
### 响应
`/change_cloth`
---
## 4. POST /do_change_cloth
**直接调用**,无队列保护,通常由内部或 `/change_cloth_base64` 调用,不建议外部直接使用。
### 请求参数(JSON
| 参数 | 类型 | 必选 | 说明 |
|------|------|------|------|
| human_url | string | 是 | 人物图片 URL |
| cloth_url | string | 是 | 服装图片 URL |
| output_format | string | 是 | 同上 |
| no2 | bool | 否 | 同上 |
| tuodi | bool | 否 | 同上 |
| kuzi_url | string | 否 | 裤子图片 URL |
| cloth_len | string | 否 | 同上 |
| suit | bool | 否 | 同上 |
### 响应
`/change_cloth`
---
## 备注
- `cloth_len` 可选值:`胸、腰、跨、大腿、膝盖、小腿、脚踝、拖地、裤子、难以辨认`
- 若服装图片中检测到人物,`cloth_len` 自动设为 `拖地`
- 结果图片同时上传至阿里云 OSS(bucket: `xiangsilian`region: 北京)
- 服务默认监听端口由 `config.py` 中的 `base64_test_port` 决定
+78 -12
View File
@@ -66,7 +66,9 @@ def get_redis_conn():
"""获取 Redis 连接""" """获取 Redis 连接"""
return redis.Redis(connection_pool=redis_pool) return redis.Redis(connection_pool=redis_pool)
app = Flask(__name__) app = Flask(__name__, static_folder='static', static_url_path='/static')
from flask_cors import CORS
CORS(app)
client = Ark( client = Ark(
# 此为默认路径,您可根据业务所在地域进行配置 # 此为默认路径,您可根据业务所在地域进行配置
@@ -225,10 +227,16 @@ def generate_from_face(human_name, sex_girl):
prompt["93"]["inputs"]["image"] = human_name prompt["93"]["inputs"]["image"] = human_name
# if sex_girl:
# prompt["6"]["inputs"]["text"] = "asian girl,knee-length shot ,model pose,hands are at the sides of the body,smile,wearing black tube top and micro skirt,simple ,white background,32k,high detail,and face is illuminated by soft side light and natural light. The sunlight spills over the white wall behind."
# else:
# prompt["6"]["inputs"]["text"] = "asian man,tall,knee-length shot,model pose,hands are at the sides of the body,smile,wearing black short pants,simple white background,The sunlight spills over the white wall behind,32k."
if sex_girl: if sex_girl:
prompt["6"]["inputs"]["text"] = "asian girl,knee-length shot ,model pose,hands are at the sides of the body,smile,wearing black tube top and micro skirt,simple ,white background,32k,high detail,and face is illuminated by soft side light and natural light. The sunlight spills over the white wall behind." prompt["6"]["inputs"]["text"] = "asian girl,knee-length shot,model pose,hands are at the sides of the body,smile,wearing black tube top and micro skirt,solid very dark gray background,32k,high detail,and face is illuminated by soft side light and natural light."
else: else:
prompt["6"]["inputs"]["text"] = "asian man,tall,knee-length shot,model pose,hands are at the sides of the body,smile,wearing black short pants,simple white background,The sunlight spills over the white wall behind,32k." prompt["6"]["inputs"]["text"] = "asian man,tall,knee-length shot,model pose,hands are at the sides of the body,smile,shirtless,wearing black short pants,very dark gray background,solid dark gray background,32k,"
#out put name #out put name
out_img_name = str(uuid.uuid4())[:8] out_img_name = str(uuid.uuid4())[:8]
@@ -639,9 +647,61 @@ def process_change_banana(human_filename, cloth_filename, output_format):
"msg":"failure" "msg":"failure"
}) })
def process_change_cloth(human_filename, cloth_filename, output_format, img_url, human_url, no2, tuodi, kuzi_url, cloth_len): def process_change_cloth(human_filename, cloth_filename, output_format, img_url, human_url, no2, tuodi, kuzi_url, cloth_len, kuzi_img=None):
w,h = get_image_dimensions(f'{APP_ROOT}/../input/{human_filename}') w,h = get_image_dimensions(f'{APP_ROOT}/../input/{human_filename}')
if kuzi_url:
print(f'换裤子 kuzi_url:{kuzi_url}')
human_json_str = GetHumanDesDesc(human_url)
print(f"human_json_str {human_json_str}")
human_json_data = json.loads(human_json_str)
sex_type = human_json_data['性别']
sex_girl = True
if '' in sex_type:
sex_girl = False
new_data = {
"model_image": image_to_base64(f'{APP_ROOT}/../input/{human_filename}', False),
"shirt_image": image_to_base64(f'{APP_ROOT}/../input/{cloth_filename}', False),
"pants_image": kuzi_img if kuzi_img else image_to_base64(save_image_from_url(kuzi_url), False),
}
response = requests.post(f'http://117.50.44.174:47698/try-on', json=new_data)
result_image_data = response.json().get("result_image", "")
if result_image_data:
print(f"result_image_data: {result_image_data[:30]}...") # 打印前30个字符以验证数据格式
if 'base64' in output_format:
return jsonify({
"ret":0,
"state": 0,
"msg":"success",
"is_girl":sex_girl,
"second_step_data":new_data["model_image"],
"first_step_data":new_data["model_image"],
"data":result_image_data
})
else:
result_image_bytes = base64.b64decode(result_image_data.split(',')[1])
result_image_stream = BytesIO(result_image_bytes)
image = Image.open(result_image_stream)
jpg_name = f"{uuid.uuid4()}.png"
jpg_path_name = f'{APP_ROOT}/static/imgs/{jpg_name}'
image.save(jpg_path_name, quality=90)
upload_to_oss(jpg_path_name, jpg_name)
https_url = f'https://xiangsilian.oss-cn-beijing.aliyuncs.com/{jpg_name}'
return jsonify({
"ret":0,
"state": 0,
"msg":"success",
"second_url":https_url,
"first_url":https_url,
"is_girl":sex_girl,
"url":https_url
})
out_put_name, out_human_name, is_girl, msg = change(human_filename, cloth_filename, w, h, img_url, human_url, no2, tuodi, cloth_len) out_put_name, out_human_name, is_girl, msg = change(human_filename, cloth_filename, w, h, img_url, human_url, no2, tuodi, cloth_len)
if out_put_name == None: if out_put_name == None:
print(f'Failed to change cloth {msg}') print(f'Failed to change cloth {msg}')
@@ -748,7 +808,13 @@ def save_image_from_url(image_url):
except Exception as e: except Exception as e:
print(f"Error saving image from URL: {e}") print(f"Error saving image from URL: {e}")
return None return None
@app.route('/')
def index():
"""返回主页"""
from flask import send_from_directory
return send_from_directory('static', 'index.html')
@app.route('/do_change_cloth', methods=['POST']) @app.route('/do_change_cloth', methods=['POST'])
def do_change_cloth(): def do_change_cloth():
@@ -801,7 +867,7 @@ def do_change_cloth():
if 'cloth_len' in data: if 'cloth_len' in data:
cloth_len = data['cloth_len'] cloth_len = data['cloth_len']
try: try:
return process_change_cloth(human_filename, cloth_filename, output_format, cloth_url, human_url, no2, tuodi, kuzi_url, cloth_len) return process_change_cloth(human_filename, cloth_filename, output_format, cloth_url, human_url, no2, tuodi, kuzi_url, cloth_len, data.get("kuzi_img") )
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
print(f"错误详情:{e}") print(f"错误详情:{e}")
@@ -912,7 +978,7 @@ def change_cloth_base64():
return jsonify({"ret":-1, 'msg': 'Failed to save human image'}), 500 return jsonify({"ret":-1, 'msg': 'Failed to save human image'}), 500
data['human_img'] = None data['human_img'] = None
human_url = f"http://112.126.94.241:{base64_test_port}/static/imgs/{human_filename}" human_url = f"http://117.50.44.174:{base64_test_port}/static/imgs/{human_filename}"
# 保存服装图片 # 保存服装图片
cloth_filename = save_base64_image(cloth_img, 'cloth') cloth_filename = save_base64_image(cloth_img, 'cloth')
@@ -920,7 +986,7 @@ def change_cloth_base64():
return jsonify({"ret":-1, 'msg': 'Failed to save cloth image'}), 500 return jsonify({"ret":-1, 'msg': 'Failed to save cloth image'}), 500
data['cloth_img'] = None data['cloth_img'] = None
cloth_url = f"http://112.126.94.241:{base64_test_port}/static/imgs/{cloth_filename}" cloth_url = f"http://117.50.44.174:{base64_test_port}/static/imgs/{cloth_filename}"
data["human_url"] = human_url data["human_url"] = human_url
data["cloth_url"] = cloth_url data["cloth_url"] = cloth_url
@@ -928,8 +994,8 @@ def change_cloth_base64():
kuzi_img = data.get('kuzi_img') kuzi_img = data.get('kuzi_img')
if kuzi_img: if kuzi_img:
kuzi_filename = save_base64_image(kuzi_img, 'kuzi') kuzi_filename = save_base64_image(kuzi_img, 'kuzi')
data['kuzi_img'] = None # data['kuzi_img'] = None
kuzi_url = f"http://112.126.94.241:{base64_test_port}/static/imgs/{kuzi_filename}" kuzi_url = f"http://117.50.44.174:{base64_test_port}/static/imgs/{kuzi_filename}"
data['kuzi_url'] = kuzi_url data['kuzi_url'] = kuzi_url
no2 = data.get('no2') no2 = data.get('no2')
@@ -953,7 +1019,7 @@ def change_cloth_base64():
# 在内部调用第二个HTTP请求 # 在内部调用第二个HTTP请求
try: try:
# 调用第二个API(可以是外部服务或自己的另一个端点) # 调用第二个API(可以是外部服务或自己的另一个端点)
response = requests.post(f'http://112.126.94.241:{base64_test_port}/do_change_cloth', json=data) response = requests.post(f'http://117.50.44.174:{base64_test_port}/do_change_cloth', json=data)
return Response( return Response(
response=response.content, response=response.content,
@@ -967,4 +1033,4 @@ def change_cloth_base64():
if __name__ == '__main__': if __name__ == '__main__':
app.run(host="0.0.0.0", port=8888, debug=True) app.run(host="0.0.0.0", port=base64_test_port, debug=True)
+1 -1
View File
@@ -347,7 +347,7 @@ def change_cloth_base64():
if not cloth_filename: if not cloth_filename:
return jsonify({"ret":-1, 'msg': 'Failed to save cloth image'}), 500 return jsonify({"ret":-1, 'msg': 'Failed to save cloth image'}), 500
img_url = f"http://112.126.94.241:18888/static/imgs/{cloth_filename}" img_url = f"http://117.50.44.174:18888/static/imgs/{cloth_filename}"
return process_change_cloth(human_filename, cloth_filename, output_format, img_url, is_generated) return process_change_cloth(human_filename, cloth_filename, output_format, img_url, is_generated)
Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

+3 -3
View File
@@ -92,12 +92,12 @@
<div class="form-group"> <div class="form-group">
<label for="clothImgUrl">服装图片URL:</label> <label for="clothImgUrl">服装图片URL:</label>
<input type="text" id="clothImgUrl" name="cloth_img" <input type="text" id="clothImgUrl" name="cloth_img"
value="http://112.126.94.241:18888/static/imgs/cloth_20250713_092536_279335d2.jpg" value="http://117.50.44.174:18888/static/imgs/cloth_20250713_092536_279335d2.jpg"
placeholder="输入服装图片的URL" required> placeholder="输入服装图片的URL" required>
<div class="preview"> <div class="preview">
<div class="preview-item"> <div class="preview-item">
<span>URL图片预览:</span> <span>URL图片预览:</span>
<img id="urlPreview" src="http://112.126.94.241:18888/static/imgs/cloth_20250713_092536_279335d2.jpg" <img id="urlPreview" src="http://117.50.44.174:18888/static/imgs/cloth_20250713_092536_279335d2.jpg"
alt="URL图片预览" onerror="this.style.display='none'"> alt="URL图片预览" onerror="this.style.display='none'">
</div> </div>
</div> </div>
@@ -149,7 +149,7 @@
statusDiv.className = ""; statusDiv.className = "";
const formData = new FormData(this); const formData = new FormData(this);
const apiUrl = "http://47.94.244.112:19001/process-image"; const apiUrl = "http://117.50.44.174:19001/process-image";
fetch(apiUrl, { fetch(apiUrl, {
method: 'POST', method: 'POST',
Binary file not shown.

After

Width:  |  Height:  |  Size: 709 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+1 -1
View File
@@ -257,7 +257,7 @@
console.log("准备发送的数据:", data); // 调试用 console.log("准备发送的数据:", data); // 调试用
// 调用API // 调用API
fetch('http://112.126.94.241:28888/change_cloth_base64', { fetch('http://117.50.44.174:28888/change_cloth_base64', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
+3 -3
View File
@@ -94,12 +94,12 @@
<div class="input-group"> <div class="input-group">
<label for="human-url">人体图片URL:</label> <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"> <input type="text" id="human-url" value="http://117.50.44.174:18888/static/imgs/human_20250713_091718_fb1f8a05.jpg">
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="cloth-url">衣服图片URL:</label> <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"> <input type="text" id="cloth-url" value="http://117.50.44.174:18888/static/imgs/cloth_20250713_092536_279335d2.jpg">
</div> </div>
<button id="submit-btn">提交处理</button> <button id="submit-btn">提交处理</button>
@@ -136,7 +136,7 @@
try { try {
// 发送POST请求 // 发送POST请求
const response = await fetch('http://112.126.94.241:18888/change_cloth', { const response = await fetch('http://117.50.44.174:18888/change_cloth', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
+1 -1
View File
@@ -246,7 +246,7 @@
// 获取服务器状态数据 // 获取服务器状态数据
async function fetchServerStatus() { async function fetchServerStatus() {
try { try {
const response = await fetch('http://112.126.94.241:18018/get_server_state'); const response = await fetch('http://117.50.44.174:18018/get_server_state');
if (!response.ok) throw new Error('网络响应不正常'); if (!response.ok) throw new Error('网络响应不正常');
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
+1 -1
View File
@@ -27,7 +27,7 @@ def image_to_base64(file_path, mime_type=None):
return f"data:{mime_type};base64,{encoded_string}" return f"data:{mime_type};base64,{encoded_string}"
if __name__ == '__main__': if __name__ == '__main__':
url = 'http://112.126.94.241:18018/change_cloth_base64' url = 'http://117.50.44.174:18018/change_cloth_base64'
headers = {'Content-Type': 'application/json'} headers = {'Content-Type': 'application/json'}
img64_human_str = image_to_base64("/home/xsl/code/ComfyUI/input/girl_full.jpg") img64_human_str = image_to_base64("/home/xsl/code/ComfyUI/input/girl_full.jpg")
data = {} data = {}