main
cm 1 month ago
parent 4e41cdd210
commit 7787fdcff7

@ -0,0 +1,31 @@
import streamlit as st
from templates.user_info import show_user_info_form
from templates.recommendations import show_recommendations_tab
from templates.recommendations import show_virtual_tryon_tab
# 页面设置
st.set_page_config(page_title="智能穿搭助手", page_icon="👗", layout="wide")
# 标题和介绍
st.title("👗 智能穿搭助手")
# 初始化session state
if 'user_info' not in st.session_state:
st.session_state.user_info = {}
if 'recommendations' not in st.session_state:
st.session_state.recommendations = None
# 侧边栏 - 用户信息收集
show_user_info_form()
# 主界面
tab1, tab2 = st.tabs(["穿搭推荐", "虚拟试衣"])
with tab1:
show_recommendations_tab()
with tab2:
show_virtual_tryon_tab()
if __name__ == "__main__":
st.write("")

@ -0,0 +1,15 @@
import os
from dotenv import load_dotenv
# 中国主要城市中英文对照表
CITY_NAME_MAPPING = {
# 直辖市
"beijing": "北京", "peking": "北京",
"shanghai": "上海",
# ... (保持原有的城市映射不变)
}
# 加载环境变量
load_dotenv()
MOONSHOT_API_KEY = os.getenv("MOONSHOT_API_KEY")
WEATHER_API_KEY = os.getenv("WEATHER_API_KEY")

@ -0,0 +1,176 @@
import streamlit as st
import requests
from PIL import Image
from utils.weather import get_weather_data
from utils.outfits import generate_sample_outfits
from utils.outfits import generate_fallback_outfits
from utils.image_processing import process_image_for_try_on
from config import MOONSHOT_API_KEY
def show_recommendations_tab():
st.header("智能穿搭推荐")
with st.form("scene_form"):
col1, col2 = st.columns(2)
with col1:
scene_input = st.text_input("描述你的场景需求",
placeholder="例如: 今天要和朋友们去公园野餐")
with col2:
city_input = st.text_input("输入城市名称",
placeholder="例如: 北京",
value="北京")
use_weather = st.checkbox("获取当地天气信息", value=True)
submitted = st.form_submit_button("获取穿搭建议")
if submitted and scene_input:
with st.spinner("正在生成穿搭建议..."):
try:
weather_info = ""
if use_weather and city_input:
weather_data = get_weather_data(city_input)
if weather_data:
weather_info = f"""
## 当前天气情况
- 城市: {weather_data['city']}
- 天气状况: {weather_data['description']}
- 温度: {weather_data['temp']}°C
- 湿度: {weather_data['humidity']}%
"""
st.markdown(weather_info)
prompt = build_prompt(scene_input, weather_info if use_weather else "")
response = call_moonshot_api(prompt)
if response:
st.session_state.recommendations = response
st.success("穿搭建议生成成功!")
# 新增:根据建议生成图片
outfit_image = generate_sample_outfits(response)
if outfit_image:
st.image(outfit_image, caption="推荐穿搭可视化", width=300)
st.session_state.outfit_image = outfit_image
else:
st.warning("无法生成穿搭图片,但文字建议有效")
else:
st.error("获取穿搭建议失败")
except Exception as e:
st.error(f"获取建议时出错: {str(e)}")
# recommendations.py 修改后的显示部分
if st.session_state.recommendations:
st.markdown("### 您的专属穿搭建议")
st.markdown(st.session_state.recommendations)
st.markdown("### 搭配示例")
try:
sample_images = generate_sample_outfits(
st.session_state.recommendations,
save_to_local=True
)
if sample_images:
# 确保只显示实际生成的图片数量
num_images = min(3, len(sample_images))
cols = st.columns(num_images)
for i in range(num_images):
with cols[i]:
st.image(
sample_images[i],
use_container_width=True,
caption=f"穿搭方案 {i + 1}"
)
st.success(f"示例 {i + 1}")
else:
st.warning("无法生成任何穿搭示例图片")
except Exception as e:
st.error(f"生成穿搭示例时出错: {str(e)}")
# 显示回退方案
fallback_images = generate_fallback_outfits(save_to_local=True)
cols = st.columns(3)
for i, col in enumerate(cols):
col.image(
fallback_images[i],
use_container_width=True,
caption=f"穿搭方案 {i + 1} (示例)"
)
def build_prompt(scene_input, weather_info):
return f"""
你是一位专业的时尚穿搭助手请根据以下信息为用户提供穿搭建议:
### 用户信息
{st.session_state.user_info}
### 场景描述
{scene_input}
### 天气信息
{weather_info if weather_info else "用户未提供天气信息"}
### 请提供以下内容:
1. 适合该场景的3套穿搭方案
2. 每套方案的风格特点和适用场合说明
3. 颜色搭配建议
4. 材质和面料选择建议
5. 购买建议和品牌推荐(可选)
"""
def call_moonshot_api(prompt):
headers = {
"Authorization": f"Bearer {MOONSHOT_API_KEY}",
"Content-Type": "application/json"
}
data = {
"model": "moonshot-v1-128k",
"messages": [
{
"role": "system",
"content": "你是一位专业的时尚顾问,擅长根据用户特征、场景和天气状况提供个性化的穿搭建议。"
},
{
"role": "user",
"content": prompt
}
],
"temperature": 0.7,
"max_tokens": 2000
}
response = requests.post(
"https://api.moonshot.cn/v1/chat/completions",
headers=headers,
json=data
)
if response.status_code == 200:
result = response.json()
return result["choices"][0]["message"]["content"]
else:
st.error(f"API调用失败: {response.text}")
return None
def show_virtual_tryon_tab():
st.header("虚拟试衣间")
st.info("此功能正在开发中,即将推出!")
uploaded_file = st.file_uploader("上传你的照片进行虚拟试衣", type=["jpg", "png", "jpeg"])
if uploaded_file is not None:
image = Image.open(uploaded_file)
st.image(image, caption="上传的照片", use_container_width=True)
gray, edges = process_image_for_try_on(image)
col1, col2 = st.columns(2)
col1.image(gray, caption="灰度处理", use_container_width=True, channels="GRAY")
col2.image(edges, caption="边缘检测", use_container_width=True, channels="GRAY")

@ -0,0 +1,24 @@
import streamlit as st
def show_user_info_form():
with st.sidebar:
st.header("个人信息")
with st.form("user_info_form"):
gender = st.selectbox("性别", ["", "", "其他"])
age = st.slider("年龄", 10, 80, 25)
height = st.number_input("身高(cm)", 100, 250, 165)
weight = st.number_input("体重(kg)", 30, 150, 55)
skin_tone = st.selectbox("肤色", ["白皙", "自然", "小麦色", "深色"])
style_preference = st.multiselect("风格偏好", ["休闲", "正式", "运动", "甜美", "复古", "街头", "商务"])
submitted = st.form_submit_button("保存信息")
if submitted:
st.session_state.user_info = {
"gender": gender,
"age": age,
"height": height,
"weight": weight,
"skin_tone": skin_tone,
"style_preference": style_preference
}
st.success("个人信息已保存!")

@ -0,0 +1,3 @@
from .weather import get_weather_data
from .outfits import generate_sample_outfits
from .image_processing import process_image_for_try_on

@ -0,0 +1,10 @@
import cv2
import numpy as np
from PIL import Image
def process_image_for_try_on(image):
"""处理上传的试衣图片"""
img_array = np.array(image)
gray = cv2.cvtColor(img_array, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 100, 200)
return gray, edges

@ -0,0 +1,159 @@
import numpy as np
import matplotlib.pyplot as plt
import cv2
from io import BytesIO
from PIL import Image
import streamlit as st
try:
import tensorflow as tf
from tensorflow.keras.datasets import fashion_mnist
HAS_TF = True
except ImportError:
HAS_TF = False
import os
from datetime import datetime
def map_keyword_to_indices(text, class_names):
"""私有方法:从推荐文本解析服装类别索引"""
keyword_map = {
# 中文关键词映射
"t恤": 0, "衬衫": 6, "外套": 4, "套头衫": 2,
"裤子": 1, "牛仔裤": 1,
"连衣裙": 3, "裙子": 3,
"凉鞋": 5, "运动鞋": 7, "短靴": 9, "靴子": 9
}
matched_indices = []
text_lower = text.lower()
for keyword, idx in keyword_map.items():
if keyword in text_lower:
matched_indices.append(idx)
return list(set(matched_indices)) # 去重
# outfits.py 修改后的代码
def generate_sample_outfits(recommendation_text, save_to_local=False, output_dir="C:/Users/Administrator/Desktop"):
"""生成示例穿搭图片(包含连衣裙选项)并可选保存到本地"""
outfits = []
# 创建输出目录
if save_to_local and not os.path.exists(output_dir):
os.makedirs(output_dir)
if HAS_TF:
try:
(train_images, train_labels), _ = fashion_mnist.load_data()
class_names = ['T恤', '裤子', '套头衫', '连衣裙', '外套',
'凉鞋', '衬衫', '运动鞋', '', '短靴']
# 从推荐文本中提取服装类别索引
matched_indices = map_keyword_to_indices(recommendation_text, class_names)
# 根据匹配结果生成图片
if not matched_indices:
st.warning("无法从推荐文本中识别有效服装类别")
return generate_fallback_outfits(save_to_local, output_dir)
# 生成3套穿搭方案
for i in range(3):
try:
# 随机决定是生成连衣裙组合还是上下装组合
use_dress = np.random.choice([True, False]) and (3 in matched_indices)
if use_dress:
# 连衣裙+鞋子组合
shoes_indices = [i for i in matched_indices if i in [5, 7, 9]]
if not shoes_indices:
continue
shoes_idx = np.random.choice(shoes_indices)
dress_img = train_images[np.random.choice(np.where(train_labels == 3)[0])]
shoes_img = train_images[np.random.choice(np.where(train_labels == shoes_idx)[0])]
dress_img = cv2.resize(dress_img, (128, 256), interpolation=cv2.INTER_NEAREST)
shoes_img = cv2.resize(shoes_img, (128, 128), interpolation=cv2.INTER_NEAREST)
combined = np.vstack([dress_img, shoes_img])
combined_rgb = np.stack([combined] * 3, axis=-1)
title = f"{class_names[3]}+{class_names[shoes_idx]}"
else:
# 上下装+鞋子组合
top_indices = [i for i in matched_indices if i in [0, 2, 4, 6]]
bottom_indices = [i for i in matched_indices if i in [1]]
shoes_indices = [i for i in matched_indices if i in [5, 7, 9]]
if not top_indices or not bottom_indices or not shoes_indices:
continue
top_idx = np.random.choice(top_indices)
bottom_idx = np.random.choice(bottom_indices)
shoes_idx = np.random.choice(shoes_indices)
top_img = train_images[np.random.choice(np.where(train_labels == top_idx)[0])]
bottom_img = train_images[np.random.choice(np.where(train_labels == bottom_idx)[0])]
shoes_img = train_images[np.random.choice(np.where(train_labels == shoes_idx)[0])]
top_img = cv2.resize(top_img, (128, 128), interpolation=cv2.INTER_NEAREST)
bottom_img = cv2.resize(bottom_img, (128, 128), interpolation=cv2.INTER_NEAREST)
shoes_img = cv2.resize(shoes_img, (128, 128), interpolation=cv2.INTER_NEAREST)
combined = np.vstack([top_img, bottom_img, shoes_img])
combined_rgb = np.stack([combined] * 3, axis=-1)
title = f"{class_names[top_idx]}+{class_names[bottom_idx]}+{class_names[shoes_idx]}"
# 直接使用PIL保存图片避免plt保存问题
img = Image.fromarray(combined_rgb)
if save_to_local:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{output_dir}/outfit_{timestamp}_{i}.png"
img.save(filename, format='PNG', quality=95)
outfits.append(img)
except Exception as e:
st.warning(f"生成第{i + 1}套穿搭图片时出错: {str(e)}")
outfits.append(generate_single_fallback_outfit(i + 1, save_to_local, output_dir))
continue
return outfits if outfits else generate_fallback_outfits(save_to_local, output_dir)
except Exception as e:
st.warning(f"生成服装图片出错: {str(e)},使用文字替代")
return generate_fallback_outfits(save_to_local, output_dir)
return generate_fallback_outfits(save_to_local, output_dir)
def generate_single_fallback_outfit(index, save_to_local=False, output_dir=None):
"""生成单个回退的文字穿搭方案"""
fig, ax = plt.subplots(figsize=(3, 4))
ax.set_facecolor('#f0f2f6')
ax.text(0.5, 0.5, f"穿搭方案 {index}\n(图片生成失败)",
ha='center', va='center', fontsize=10)
ax.axis('off')
buf = BytesIO()
fig.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.1, dpi=100)
buf.seek(0)
img = Image.open(buf)
plt.close(fig)
if save_to_local and output_dir:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{output_dir}/outfit_{timestamp}_{index}_fallback.png"
img.save(filename)
return img
def generate_fallback_outfits(save_to_local=False, output_dir="C:/Users/Administrator/Desktop"):
"""生成回退的文字穿搭方案"""
outfits = []
for i in range(3):
outfits.append(generate_single_fallback_outfit(i + 1, save_to_local, output_dir))
return outfits

@ -0,0 +1,34 @@
import requests
from config import CITY_NAME_MAPPING, WEATHER_API_KEY
def get_weather_data(city, api_key=WEATHER_API_KEY):
"""获取天气数据"""
base_url = "http://api.openweathermap.org/data/2.5/weather"
params = {
"q": city,
"appid": api_key,
"units": "metric",
"lang": "zh_cn"
}
try:
response = requests.get(base_url, params=params, timeout=10)
data = response.json()
if response.status_code == 200:
normalized_input = city.lower().replace(" ", "")
chinese_name = CITY_NAME_MAPPING.get(normalized_input, data.get("name", city))
return {
"city": chinese_name,
"temp": data["main"]["temp"],
"humidity": data["main"]["humidity"],
"description": data["weather"][0]["description"],
"icon": data["weather"][0]["icon"]
}
else:
return None
except Exception as e:
print(f"获取天气数据时出错: {str(e)}")
return None
Loading…
Cancel
Save