# 【功能描述】 # 本项目基于 SMPL 模型,通过随机生成或从库中加载参数,分析人体姿态与压力分布的关系。 # 主要功能包括: # 1. 随机生成人体参数(包括 betas 和 pose) # 2. 从预定义库中加载已保存的参数 # 3. 利用 SMPL 模型生成人体网格和压力分布 # 4. 可视化展示生成的人体模型和压力分布 # 5. 允许用户保存新生成的参数到库中【新增功能】 # streamlit run new_app.py --server.port 8501 import streamlit as st import torch import numpy as np import plotly.graph_objects as go import os import matplotlib.pyplot as plt from sample_utils import PoseSampler, sample_beta, sample_transl4pp from generate_utils import PressureGenerator # --- 1. 初始化 --- st.set_page_config(page_title="SMPL2Pressure", layout="wide") st.markdown(""" """, unsafe_allow_html=True) @st.cache_resource def init_models(_device): p_sampler = PoseSampler(device=_device, dataset='pp') p_gen = PressureGenerator(device=_device) return p_sampler, p_gen DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' pose_sampler, generator = init_models(DEVICE) # 初始化 Session State if 'betas' not in st.session_state: st.session_state.betas = torch.zeros((1, 10)).to(DEVICE) st.session_state.pose = torch.zeros((1, 72)).to(DEVICE) st.session_state.transl = sample_transl4pp(batch_size=1, device=DEVICE) st.session_state.run_trigger = 0 st.session_state.selected_pose_idx = 0 # --- 2. 辅助函数 --- @st.cache_data(show_spinner=False) def compute_single_result(_beta, _pose, _transl, transfer=False, trigger=0): # 确保输入是 tensor 且在正确的设备上 pmap = generator.generate(betas=_beta, transl=_transl, poses=_pose, transfer=transfer) pmap = pmap.flip(1) with torch.no_grad(): output = generator.smpl_model( betas=_beta, global_orient=_pose[:, :3], body_pose=_pose[:, 3:], transl=_transl ) verts = output.vertices[0].cpu().numpy() faces = generator.smpl_model.faces if transfer: verts[:, 1] = 1.80 - verts[:, 1] verts[:, 2] = -verts[:, 2] return verts, faces, pmap.squeeze().cpu().numpy() # --- 3. 侧边栏 --- with st.sidebar: st.title("🎛️ Controls") mode = st.radio("Input Source", ["Random", "Library"], horizontal=True) st.divider() if mode == "Library": st.info("Library mode: Select a pose from the gallery on the right.") else: st.success("Random mode: Generate random parameters.") # --- 4. 主界面标题与顶部控制栏 --- head_c1, head_c2, head_c3 = st.columns([3, 1, 1]) with head_c1: st.subheader("🔬 Pressure Map Synthesis via SMPL Model") # --- 5. 模式逻辑分流 --- if mode == "Random": # --- RANDOM 模式控制按钮 --- with head_c2: if st.button("🔄 Generate Random", type="primary", use_container_width=True): st.session_state.betas = sample_beta(batch_size=1, device=DEVICE) st.session_state.pose = pose_sampler.sample(batch_size=1) st.session_state.transl = sample_transl4pp(batch_size=1, device=DEVICE) st.session_state.run_trigger += 100 with head_c3: if st.button("Save to Library", type="secondary", use_container_width=True): try: save_dir = "static_stats" os.makedirs(save_dir, exist_ok=True) existing_files = [f for f in os.listdir(save_dir) if f.startswith("betas_")] indices = [int(f.split('_')[1].split('.')[0]) for f in existing_files if '_' in f] next_idx = max(indices) + 1 if indices else 1 np.savez(f"{save_dir}/betas_{next_idx}.npz", betas=st.session_state.betas.cpu().numpy().squeeze(0)) np.savez(f"{save_dir}/pose_{next_idx}.npz", pose=st.session_state.pose.cpu().numpy().squeeze(0)) st.toast(f"Saved as index {next_idx}!", icon='✅') except Exception as e: st.error(f"Save failed: {e}") # --- RANDOM 模式渲染 (7:3 布局) --- verts, faces, pmap_np = compute_single_result(st.session_state.betas, st.session_state.pose, st.session_state.transl, trigger=st.session_state.run_trigger) view_c1, view_c2 = st.columns([7.3, 2.7]) with view_c1: st.subheader("🌐 3D Mesh") fig_3d = go.Figure(data=[go.Mesh3d( x=verts[:, 0], y=verts[:, 1], z=verts[:, 2], i=faces[:, 0], j=faces[:, 1], k=faces[:, 2], color='LightBlue', opacity=1.0, flatshading=False, lighting=dict(ambient=0.5, diffuse=0.9, specular=0.5, roughness=0.6), lightposition=dict(x=100, y=200, z=150) )]) fig_3d.update_layout( scene=dict( aspectmode='data', xaxis_visible=False, yaxis_visible=False, zaxis_visible=False, camera=dict( eye=dict(x=0, y=0, z=2.0), # Positioned high on Z-axis up=dict(x=0, y=1, z=0), # Y-axis points "up" on the screen center=dict(x=0., y=0., z=0.) # Looking at the origin ) ), height=720, margin=dict(l=0, r=0, b=0, t=0), paper_bgcolor="white", ) st.plotly_chart(fig_3d, use_container_width=True) with view_c2: m_c1, m_c2 = st.columns(2) m_c1.metric("Peak Pressure", f"{pmap_np.max():.2f}") m_c2.metric("Contact Pixels", f"{(pmap_np > 0.5).sum()}") # with st.container(): # aspect='equal' ensures no distortion fig_2d, ax = plt.subplots(figsize=(5, 8)) im = ax.imshow(pmap_np, cmap='viridis', origin='lower', aspect='equal') plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04) ax.axis('off') # Use container width helps alignment st.pyplot(fig_2d, use_container_width=True) else: # --- LIBRARY 模式 --- st.write("### 1️⃣ Step: Select Human Pose") pose_cols = st.columns(8) for i in range(8): img_path = f"src/static/fig/pose_{i}.png" with pose_cols[i]: if os.path.exists(img_path): st.image(img_path, use_container_width=True) # 点击按钮更新全局索引 if st.button(f"Pose {i}", key=f"btn_p_{i}", use_container_width=True, type="primary" if st.session_state.selected_pose_idx == i else "secondary"): st.session_state.selected_pose_idx = i st.session_state.run_trigger += 1 st.rerun() # import pdb; pdb.set_trace() # 加载选中的数据 try: # p_path = f"src/static/poses/POSE_{st.session_state.selected_pose_idx}.npz" p_path = f"src/static_stats/pose_{st.session_state.selected_pose_idx}.npz" p_data = np.load(p_path)['pose'] current_pose = torch.from_numpy(p_data).float().unsqueeze(0).to(DEVICE) # 加载 5 个 Betas beta_tensors = [] for i in range(5): b_path = f"src/static/betas/MALE_BETA_{i}.npz" if os.path.exists(b_path): b_data = np.load(b_path)['beta'] beta_tensors.append(torch.from_numpy(b_data).float().unsqueeze(0).to(DEVICE)) else: test_beta = sample_beta(batch_size=1, device=DEVICE) beta_tensors.append(test_beta) except Exception as e: # import pdb; pdb.set_trace() st.error(f"Error loading files: {e}. Check if static/poses/ and static/betas/ exist.") st.stop() # 统一的平移量 lib_transl = sample_transl4pp(batch_size=1, device=DEVICE) st.divider() st.write(f"### 2️⃣ Step: Multi-Body Shape Comparison (Current: Pose {st.session_state.selected_pose_idx})") # 并排展示 cols = st.columns(5) for i in range(5): with cols[i]: # print(f"{beta_tensors[i].shape} {beta_tensors[i].cpu().numpy()}") # import pdb; pdb.set_trace() v, f, p = compute_single_result(beta_tensors[i], current_pose, lib_transl, transfer=False, trigger=st.session_state.run_trigger + i*1000) st.markdown(f"