Spaces:
Runtime error
Runtime error
| 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" | |
| ) |