change2color / src /streamlit_app.py
TUAN800's picture
Update src/streamlit_app.py
80f2a5c verified
Raw
History Blame Contribute Delete
6.7 kB
import streamlit as st
import cv2
import numpy as np
from rembg import remove, new_session
import io
from PIL import Image
# ==========================================
# 核心渲染器:绝对精准颜色匹配
# ==========================================
def render_exact_color(orig_img, mask_3d, target_lab):
"""确保100%零色差,保留褶皱"""
orig_lab = cv2.cvtColor(orig_img, cv2.COLOR_BGR2LAB).astype(np.float32)
l_t, a_t, b_t = target_lab
l_orig = orig_lab[:, :, 0]
mask_bool = mask_3d[:, :, 0] > 0.5
if not np.any(mask_bool):
return orig_img
l_mean_orig = np.mean(l_orig[mask_bool])
# 亮度对齐,保留褶皱
l_new = l_orig - l_mean_orig + l_t
l_new = np.clip(l_new, 0, 255).astype(np.uint8)
a_new = np.full_like(l_orig, a_t, dtype=np.uint8)
b_new = np.full_like(l_orig, b_t, dtype=np.uint8)
new_lab = cv2.merge([l_new, a_new, b_new])
new_bgr = cv2.cvtColor(new_lab, cv2.COLOR_LAB2BGR)
final_out = new_bgr.astype(np.float32) * mask_3d + orig_img.astype(np.float32) * (1.0 - mask_3d)
return np.clip(final_out, 0, 255).astype(np.uint8)
# ==========================================
# 工具函数
# ==========================================
def get_lab_metrics(img_bgr):
"""获取参考图片中心区域的 LAB 颜色均值"""
img_lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB)
h, w = img_bgr.shape[:2]
return np.mean(img_lab[int(h * 0.4):int(h * 0.6), int(w * 0.4):int(w * 0.6)], axis=(0, 1))
def generate_ai_mask(orig_bytes, orig_bgr, shape):
"""【终极修复版】人体轮廓 + 肤色剔除 + 暗色剔除 + 背景剔除"""
session = new_session("u2net")
output_data = remove(orig_bytes, session=session)
nparr = np.frombuffer(output_data, np.uint8)
rgba_img = cv2.imdecode(nparr, cv2.IMREAD_UNCHANGED)
person_mask = rgba_img[:, :, 3]
person_mask = cv2.resize(person_mask, (shape[1], shape[0]))
_, person_mask = cv2.threshold(person_mask, 200, 255, cv2.THRESH_BINARY)
hsv = cv2.cvtColor(orig_bgr, cv2.COLOR_BGR2HSV)
lower_skin = np.array([0, 10, 30], dtype=np.uint8)
upper_skin = np.array([30, 255, 255], dtype=np.uint8)
skin_mask = cv2.inRange(hsv, lower_skin, upper_skin)
skin_mask = cv2.dilate(skin_mask, np.ones((5, 5), np.uint8), iterations=3)
dark_mask = cv2.inRange(hsv, np.array([0, 0, 0]), np.array([180, 255, 50]))
dark_mask = cv2.dilate(dark_mask, np.ones((7, 7), np.uint8), iterations=2)
white_mask = cv2.inRange(hsv, np.array([0, 0, 210]), np.array([180, 30, 255]))
white_mask = cv2.dilate(white_mask, np.ones((5, 5), np.uint8), iterations=2)
exclude_mask = cv2.bitwise_or(skin_mask, dark_mask)
exclude_mask = cv2.bitwise_or(exclude_mask, white_mask)
clothes_mask = cv2.bitwise_and(person_mask, cv2.bitwise_not(exclude_mask))
clothes_mask = cv2.morphologyEx(clothes_mask, cv2.MORPH_OPEN, np.ones((5, 5), np.uint8))
clothes_mask = cv2.morphologyEx(clothes_mask, cv2.MORPH_CLOSE, np.ones((15, 15), np.uint8))
contours, _ = cv2.findContours(clothes_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
largest_contour = max(contours, key=cv2.contourArea)
clean_mask = np.zeros_like(clothes_mask)
cv2.drawContours(clean_mask, [largest_contour], -1, 255, thickness=cv2.FILLED)
clothes_mask = clean_mask
clothes_mask = cv2.erode(clothes_mask, np.ones((3, 3), np.uint8), iterations=1)
clothes_mask = cv2.GaussianBlur(clothes_mask, (7, 7), 0)
mask_3d = np.repeat((clothes_mask.astype(np.float32) / 255.0)[:, :, np.newaxis], 3, axis=2)
return mask_3d
# ==========================================
# Streamlit 网页前端界面
# ==========================================
st.set_page_config(page_title="AI 衣服换色系统", layout="wide")
st.title("🎨 AI 衣服精准换色系统")
st.markdown("上传原图和目标颜色图,AI将自动提取衣物并进行零色差渲染。")
# 侧边栏:上传区域
with st.sidebar:
st.header("1. 上传图片")
orig_file = st.file_uploader("上传原图 (需要换色的衣服)", type=['jpg', 'jpeg', 'png'])
ref_files = st.file_uploader("上传参考图 (目标颜色,可多选)", type=['jpg', 'jpeg', 'png'], accept_multiple_files=True)
process_btn = st.button("🚀 开始极速换色", use_container_width=True)
# 主显示区域
if orig_file is not None:
# 将上传的文件转为 OpenCV 格式
orig_bytes = orig_file.getvalue()
orig_nparr = np.frombuffer(orig_bytes, np.uint8)
orig_bgr = cv2.imdecode(orig_nparr, cv2.IMREAD_COLOR)
# 界面预览原图
st.subheader("原图预览")
st.image(cv2.cvtColor(orig_bgr, cv2.COLOR_BGR2RGB), width=400)
if process_btn:
if not ref_files:
st.warning("⚠️ 请至少上传一张参考颜色图!")
else:
with st.spinner('AI 正在提取衣服轮廓和处理换色,请稍候 (首次运行需下载模型)...'):
shape = orig_bgr.shape[:2]
# 1. 提取蒙版
mask_3d = generate_ai_mask(orig_bytes, orig_bgr, shape)
st.subheader("🎉 换色结果")
# 创建多列布局展示结果
cols = st.columns(len(ref_files))
# 2. 遍历参考图换色
for idx, ref_file in enumerate(ref_files):
ref_nparr = np.frombuffer(ref_file.getvalue(), np.uint8)
ref_bgr = cv2.imdecode(ref_nparr, cv2.IMREAD_COLOR)
target_lab = get_lab_metrics(ref_bgr)
final_img = render_exact_color(orig_bgr, mask_3d, target_lab)
# 将 BGR 转为 RGB 供网页显示
final_rgb = cv2.cvtColor(final_img, cv2.COLOR_BGR2RGB)
with cols[idx]:
st.image(cv2.cvtColor(ref_bgr, cv2.COLOR_BGR2RGB), caption=f"参考色: {ref_file.name}", width=150)
st.image(final_rgb, caption=f"换色完成: {ref_file.name}", use_column_width=True)
# 提供下载按钮
result_pil = Image.fromarray(final_rgb)
buf = io.BytesIO()
result_pil.save(buf, format="JPEG")
st.download_button(
label=f"⬇️ 下载该结果",
data=buf.getvalue(),
file_name=f"result_{ref_file.name}",
mime="image/jpeg"
)