ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

4DAnyone开源项目解析:从单目视频生成可驱动换装数字人

4DAnyone开源项目解析:从单目视频生成可驱动换装数字人 这次我们来看一个名为4DAnyone的开源项目。它的核心目标很直接从一段普通的单目视频中生成一个可驱动、可换装的4D数字人。这意味着你不再需要昂贵的多摄像头阵列或复杂的动作捕捉设备仅凭手机或普通相机拍摄的一段视频就能创建一个动态的、三维立体的数字人化身。这个项目最值得关注的点在于其技术路径和实用性。它基于**4D高斯泼溅4D Gaussian Splatting**技术能够从视频中重建出高保真的人体几何与外观并实现时序上的动态一致性。简单来说它能把2D视频“膨胀”成一个4D3D空间时间的数字人模型并且支持后续的换装、驱动等操作。对于开发者、内容创作者或对数字人技术感兴趣的朋友来说4DAnyone 提供了一个相对低门槛的入口。本文将带你快速了解它的核心能力、部署门槛并通过一套通用的验证流程让你知道如何在自己的环境中跑起来、测效果以及需要注意哪些关键点。1. 核心能力速览在深入部署之前我们先通过一个表格快速把握 4DAnyone 的核心规格和特点。这些信息基于其开源项目描述和技术论文实际体验可能因环境而异。能力项说明项目类型4D数字人生成与编辑框架核心技术4D Gaussian Splatting从单目视频重建动态3D人体输入要求一段单人、背景相对简单的单目视频如手机拍摄主要输出可驱动的4D数字人模型包含几何、纹理、动态序列核心功能1. 从视频重建4D数字人2. 数字人驱动表情、动作3.数字人换装支持更换上衣、下装等4. 新视角合成从任意角度观看动态人物硬件门槛较高。主要依赖GPU进行训练与推理显存需求大。显存占用训练阶段根据视频长度和分辨率通常需要20GB显存例如RTX 3090/4090。推理和轻量化编辑需求可能降低。支持平台Linux推荐理论上WindowsWSL2也可运行但依赖配置更复杂。启动方式命令行脚本启动。需按步骤配置环境、准备数据、运行训练和推理脚本。是否支持API项目本身主要提供研究代码和脚本未封装成标准HTTP API服务。但生成的结果模型可用于后续集成。是否支持批量任务支持对多个视频分别进行处理但通常是串行任务需要手动或编写脚本组织。适合场景数字人内容创作、虚拟主播资产制作、影视特效预演、学术研究。不适合实时交互或消费级应用。2. 适用场景与使用边界在投入时间部署之前明确它能做什么、不能做什么至关重要。4DAnyone 适合谁数字内容创作者/团队需要为虚拟角色快速生成高质量、可定制的3D动态资产用于短视频、动画或游戏预演。技术研究者/学生希望学习或复现前沿的4D重建、神经渲染、数字人技术。虚拟主播运营者寻求从真人视频中提取数字人形象并进行换装等个性化编辑构建虚拟IP。它能解决什么问题降低4D数字人制作门槛无需专业3D扫描设备用普通视频即可创建。实现动态细节重建能捕捉衣物褶皱、头发飘动等细微动态。提供编辑能力在重建的4D模型基础上进行换装扩展了数字人的应用灵活性。它不适合什么场景实时直播/视频通话该技术管线以“预处理-训练-生成”为主延迟高非实时。低配置硬件环境对GPU显存要求苛刻消费级显卡如8G显存很难完成完整训练。复杂背景或多人视频算法针对单人、背景相对简单的视频优化复杂场景效果会大打折扣甚至失败。追求“一键生成”的用户部署和运行涉及大量命令行操作、环境配置和参数调试需要一定的技术耐心。重要合规与安全边界肖像权与授权必须确保输入视频中的人物已明确授权您使用其肖像进行数字人重建与编辑。用于商业用途时授权链条必须清晰、合法。版权素材用于换装的服装纹理等素材需确认其版权允许被用于此类合成与编辑。隐私保护处理任何个人视频数据时应在本地或安全的私有化环境中进行避免数据泄露。使用目的请将技术用于创作、研究等合法合规的领域严禁用于伪造、欺诈或任何侵犯他人合法权益的活动。3. 环境准备与前置条件4DAnyone 的环境配置是其第一道门槛。以下是基于其开源代码库整理的通用要求清单具体版本请以项目官方README为准。1. 操作系统推荐: Ubuntu 18.04/20.04/22.04 LTS。社区支持最好依赖问题最少。可选: Windows 10/11 with WSL2 (Ubuntu发行版)。但GPU穿透和某些原生库的编译可能遇到额外问题。不推荐: macOS (无CUDA支持无法利用GPU)。2. 硬件要求GPU:NVIDIA GPU显存建议24GB及以上如RTX 3090, RTX 4090。这是完成模型训练的基本保障。显存不足会导致进程被终止。CPU: 现代多核CPU如Intel i7/i9或AMD Ryzen 7/9系列。内存: 32GB RAM 或更高用于处理视频数据和中间缓存。存储: 至少50GB可用空间的SSD。用于存放代码、数据集、模型和输出结果。3. 软件与驱动NVIDIA驱动: 版本需与CUDA Toolkit匹配建议使用较新版本如525以上。CUDA Toolkit: 通常是11.3, 11.6 或 11.7。这是PyTorch等深度学习框架的基石版本必须精确匹配。cuDNN: 与CUDA版本对应的cuDNN库。Python:3.8 或 3.9。Python 3.10可能会遇到一些包兼容性问题。Conda 或 Python venv: 强烈建议使用虚拟环境隔离项目依赖。4. 关键依赖项通过Python包管理安装PyTorch: 版本需严格匹配CUDA版本如torch1.12.1cu113。torchvision: 对应版本的torchvision。其他科学计算库: numpy, scipy, opencv-python, imageio等。图形相关: kaolin, pytorch3d (可能需从源码编译)这些库用于3D数据处理和渲染。项目特定库: 4DAnyone 自身会依赖一些自定义或小众的库需要按照其requirements.txt安装。环境检查清单在开始安装前请在终端执行以下命令进行基础检查# 检查GPU和驱动 nvidia-smi # 检查CUDA版本如果已安装 nvcc --version # 检查Python版本 python --version # 检查pip版本 pip --version确保nvidia-smi能正确输出GPU信息且驱动版本足够新。4. 安装部署与启动方式4DAnyone 的部署是一个标准的“克隆代码-安装依赖-准备数据-运行脚本”的过程。这里给出通用流程具体路径和脚本名需根据项目实际代码调整。步骤1获取项目代码# 克隆仓库 git clone https://github.com/[organization]/4DAnyone.git cd 4DAnyone # 检查项目结构通常包含以下目录 ls -la # 预期看到: README.md, requirements.txt, scripts/, datasets/, models/ 等步骤2创建并激活虚拟环境# 使用 conda conda create -n 4danyone python3.9 -y conda activate 4danyone # 或使用 venv python -m venv venv_4danyone source venv_4danyone/bin/activate # Linux # venv_4danyone\Scripts\activate # Windows步骤3安装PyTorch与核心依赖这是最关键且最容易出错的一步。务必根据你的CUDA版本从 PyTorch官网 获取正确的安装命令。# 示例为 CUDA 11.3 安装 PyTorch 1.12.1 pip install torch1.12.1cu113 torchvision0.13.1cu113 torchaudio0.12.1 --extra-index-url https://download.pytorch.org/whl/cu113步骤4安装项目其余依赖# 安装 requirements.txt 中的包 pip install -r requirements.txt # 某些3D库可能需要单独从源码安装例如 # pip install githttps://github.com/facebookresearch/pytorch3d.git # 请严格遵循项目README中的指示。步骤5下载预训练模型与数据4DAnyone 通常依赖一些预训练的人体模型如SMPL-X和检查点checkpoint。# 通常项目会提供下载脚本 bash scripts/download_models.sh # 或 bash scripts/download_pretrained.sh # 如果没有脚本则需要手动从提供的链接如Google Drive下载并放入指定的 ./models 或 ./pretrained 目录。步骤6准备你的输入视频将你的单目视频如my_video.mp4放入项目指定的输入目录如./data/input/。 通常需要对视频进行预处理例如抽帧、裁剪、背景处理如果项目提供背景分割工具。# 示例使用项目提供的预处理脚本 python scripts/preprocess_video.py --path ./data/input/my_video.mp4 --output_dir ./data/processed/my_video步骤7启动训练与重建流程这是核心步骤命令会很长涉及大量参数。# 通用命令格式参数需要根据你的视频和需求调整 python train.py \ --config configs/default.yaml \ --data_root ./data/processed/my_video \ --exp_name my_first_4davatar \ --gpu_ids 0 \ --batch_size 1 \ --num_epochs 100--config: 指定配置文件控制模型结构、训练参数。--data_root: 预处理后的数据路径。--exp_name: 实验名称所有输出日志、模型、渲染结果会保存在./experiments/exp_name下。--gpu_ids: 指定使用的GPU ID单卡一般为0。--batch_size: 由于显存限制通常只能设为1。--num_epochs: 训练轮数可能需要数百轮才能收敛。训练开始后会在终端看到损失loss下降的日志。重点观察显存占用通过nvidia-smi命令确保没有爆显存。步骤8推理与渲染训练完成后使用推理脚本生成最终的可视化结果如新视角视频、换装结果。python inference.py \ --checkpoint ./experiments/my_first_4davatar/checkpoints/latest.pth \ --output_video ./results/my_avatar_output.mp4 \ --pose_source driving_video.mp4 # 如果是驱动任务 --garment_texture ./textures/new_shirt.png # 如果是换装任务5. 功能测试与效果验证部署成功后需要通过一系列测试来验证系统是否工作正常以及输出质量是否符合预期。我们按照从易到难的顺序进行。5.1 测试1环境与依赖验证目的确保所有关键库都能正确导入GPU可用。操作# 创建一个 test_env.py 文件 import torch import numpy as np import cv2 import imageio # 尝试导入项目核心模块名称可能为 model, network, renderer 等 try: from core import SomeKeyModule print([OK] 项目核心模块导入成功) except ImportError as e: print(f[FAIL] 核心模块导入失败: {e}) print(fPyTorch 版本: {torch.__version__}) print(fCUDA 是否可用: {torch.cuda.is_available()}) print(fGPU 设备: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else None}) print(fcuDNN 版本: {torch.backends.cudnn.version()})预期结果所有import成功CUDA可用并正确识别你的GPU。失败排查检查虚拟环境是否激活、PyTorch版本与CUDA是否匹配、项目路径是否在Python路径中。5.2 测试2数据预处理流程目的验证你的输入视频能被成功处理成模型需要的格式。操作运行预处理脚本见4.6节。检查输出目录如./data/processed/my_video是否生成以下文件images/: 视频抽帧后的图片序列如0000.png, 0001.png...。masks/: 如果支持人物分割掩码序列。cameras.npz或params.npz: 相机参数文件。smplx_params.npz: 如果用到SMPL-X人体姿态参数。判断成功目录结构完整图片清晰掩码准确。这是后续所有步骤的基础。5.3 测试3短序列快速训练测试目的用极短的训练时间和极低的参数验证整个训练管线能否跑通而非追求效果。操作准备一个非常短的视频3-5秒分辨率降至512x512左右。修改配置文件configs/default.yaml或通过命令行传入以下参数python train.py \ --data_root ./data/processed/short_video \ --exp_name test_run \ --num_epochs 10 \ # 仅训练10轮 --learning_rate 0.001 \ --save_interval 1 \ # 每轮都保存方便观察 --vis_interval 1 # 每轮都可视化观察./experiments/test_run目录下是否生成checkpoints和vis可视化文件夹。判断成功训练过程不报错vis文件夹内随着训练轮数增加生成的预览图从噪声逐渐变得有粗略的人形。失败排查检查数据路径、配置文件语法、GPU内存是否足够即使短序列初始内存占用也可能很高。5.4 测试4静态帧新视角合成目的测试训练好的模型是否学会了3D几何能否从不同视角渲染同一帧。操作使用训练好的模型如latest.pth。运行只渲染某一帧如第50帧但从不同相机角度观察的脚本。python render_novel_views.py \ --checkpoint ./experiments/test_run/checkpoints/latest.pth \ --frame_idx 50 \ --output_dir ./novel_views预期结果在./novel_views中生成一系列图片显示同一个人物姿势但从不同角度正面、侧面、背面、俯视等观看的效果。判断成功生成的多角度图片中人物主体一致没有严重的变形或撕裂。这是检验3D重建质量的核心。5.5 测试5时序动态重建质量目的测试模型重建的4D动态序列是否平滑、一致。操作使用推理脚本重建输入视频本身即输入pose为原始视频pose。bash python inference.py \ --checkpoint ./path/to/full_model.pth \ --pose_source original \ --output_video ./reconstructed.mp4判断成功观看reconstructed.mp4人物动作应与原视频基本一致。重点观察细节衣物褶皱、头发等非刚性部分是否自然运动有没有闪烁、抖动或突然变形。对比原视频和重建视频检查是否有颜色失真或细节丢失。5.6 测试6数字人换装功能目的验证项目的核心编辑能力——换装。操作准备一张服装纹理图如shirt_pattern.png确保其UV布局与项目要求匹配通常是一种标准的服装模板图。运行换装推理脚本。python inference_garment.py \ --checkpoint ./path/to/model.pth \ --garment_texture ./textures/shirt_pattern.png \ --garment_type upper # 指定服装类型如上衣 --output_video ./avatar_new_clothes.mp4判断成功生成的视频中数字人穿上了新纹理的服装。服装纹理应正确贴合身体并随着人物运动而自然变形如褶皱变化。没有出现纹理错位、穿透身体或严重拉伸失真的情况。6. 接口API与批量任务如前所述4DAnyone 主要是一个研究型代码库并未提供开箱即用的HTTP API服务。但其核心功能可以通过Python脚本进行调用这为集成和批量处理提供了可能。6.1 功能模块化调用你可以将项目的主要流程封装成函数供其他脚本调用。例如创建一个pipeline.py# pipeline.py - 示例封装 import sys sys.path.append(/path/to/4DAnyone) # 添加项目路径 from train import main as train_main from inference import main as infer_main import yaml import argparse def create_4d_avatar(video_path, output_dir, config_pathconfigs/default.yaml): 封装从视频到4D数字人的流程 # 1. 预处理 (假设有预处理模块) from scripts.preprocess_video import preprocess processed_data_dir preprocess(video_path) # 2. 训练 (这里需要模拟命令行参数) train_args argparse.Namespace( configconfig_path, data_rootprocessed_data_dir, exp_nameauto_exp, gpu_ids0, resumeFalse ) train_main(train_args) # 调用训练主函数 # 3. 获取最新模型路径 checkpoint_path f./experiments/auto_exp/checkpoints/latest.pth return checkpoint_path def drive_avatar(checkpoint_path, driving_pose_source, output_video_path): 封装数字人驱动流程 infer_args argparse.Namespace( checkpointcheckpoint_path, pose_sourcedriving_pose_source, output_videooutput_video_path, backgroundNone ) infer_main(infer_args) if __name__ __main__: # 使用示例 ckpt create_4d_avatar(input_video.mp4, ./my_output) drive_avatar(ckpt, driving_video.mp4, ./my_output/driven_result.mp4)6.2 批量任务处理对于需要处理多个视频的场景可以编写一个批处理脚本batch_process.py# batch_process.py import os import subprocess from concurrent.futures import ThreadPoolExecutor, as_completed VIDEO_DIR ./batch_input_videos OUTPUT_ROOT ./batch_output CONFIG configs/default.yaml MAX_WORKERS 1 # 由于GPU显存限制通常只能串行设为1。多卡可考虑并行。 def process_single_video(video_file): 处理单个视频的完整流程 video_name os.path.splitext(video_file)[0] input_path os.path.join(VIDEO_DIR, video_file) output_dir os.path.join(OUTPUT_ROOT, video_name) os.makedirs(output_dir, exist_okTrue) log_file os.path.join(output_dir, process.log) # 使用命令行调用将输出重定向到日志 # 这里简化了实际需要拆分成预处理、训练、推理等多个命令 cmd fpython train.py --config {CONFIG} --data_root {input_path}_processed --exp_name {video_name} --gpu_ids 0 {log_file} 21 print(f开始处理: {video_file}) try: subprocess.run(cmd, shellTrue, checkTrue) print(f处理完成: {video_file}) return (video_file, True, None) except subprocess.CalledProcessError as e: print(f处理失败: {video_file}, 错误: {e}) return (video_file, False, str(e)) if __name__ __main__: video_files [f for f in os.listdir(VIDEO_DIR) if f.endswith((.mp4, .mov, .avi))] # 由于GPU资源紧张建议串行处理 for vf in video_files: process_single_video(vf) # 如果未来优化或显存充足可考虑并行谨慎使用 # with ThreadPoolExecutor(max_workersMAX_WORKERS) as executor: # futures {executor.submit(process_single_video, vf): vf for vf in video_files} # for future in as_completed(futures): # result future.result() # print(result)批量任务关键点资源管理每个任务都消耗大量显存绝对不要同时启动多个训练进程除非你有多个独立GPU。日志记录每个任务必须有独立的日志文件便于失败后排查。检查点保存确保训练脚本定期保存检查点防止长时间运行意外中断。输出组织为每个输入视频创建独立的输出目录避免文件覆盖。7. 资源占用与性能观察理解和监控资源占用是稳定运行4DAnyone的关键。7.1 显存占用观察训练阶段是显存消耗的峰值期。通过以下命令实时监控# 每2秒刷新一次GPU状态 watch -n 2 nvidia-smi # 或使用更详细的工具如gpustat pip install gpustat gpustat -i 2典型观察结果训练初期加载模型、数据后显存会迅速上升至一个高位例如18-22GB。训练过程中显存占用会小幅波动但基本维持在高位。如果显存接近GPU容量会触发PyTorch的CUDA out of memory错误进程终止。推理阶段显存占用通常低于训练但具体取决于模型复杂度和渲染分辨率。降低显存占用的常见方法减小输入分辨率在预处理阶段将视频帧裁剪、缩放得更小如512x512。缩短输入视频使用更短的视频片段2-3秒。减小批量大小Batch Size在配置文件中将batch_size设为1通常已是最小值。使用梯度检查点Gradient Checkpointing如果代码支持可以以时间换空间。使用更低的模型精度尝试使用torch.cuda.amp进行混合精度训练需代码支持。7.2 CPU与内存占用CPU数据加载和预处理会占用一定CPU。如果发现训练时GPU利用率不高低于70%可能是CPU预处理成了瓶颈。可以考虑使用更快的存储NVMe SSD或调整数据加载的线程数num_workers。内存RAM长时间训练或处理高分辨率视频时内存占用可能达到10GB以上。确保系统有足够的空闲内存避免使用交换分区swap否则会极其缓慢。7.3 训练时间预估训练时间取决于视频长度、分辨率、模型复杂度和迭代轮数。短测试5秒视频10轮可能在30分钟到1小时内完成。完整训练10秒视频300轮可能需要12小时到数天。 使用vis_interval参数定期生成预览图可以在训练早期判断模型是否在向正确方向学习避免无效的长时训练。7.4 端口与进程管理本项目不直接提供Web服务但如果你封装了API可能会启动服务进程。检查端口占用lsof -i :PORT_NUMBER或netstat -tulpn | grep PORT_NUMBER。终止进程如果训练卡死或需要中断使用kill -9 PID。首先用nvidia-smi找到对应的进程PID。8. 常见问题与排查方法以下是部署和运行4DAnyone时可能遇到的典型问题及解决思路。问题现象可能原因排查方式解决方案ImportError或ModuleNotFoundError1. 虚拟环境未激活2. 依赖包未安装或版本不对3. Python路径问题1.conda activate 4danyone2.pip list | grep 包名3. 在Python中import sys; print(sys.path)1. 激活正确环境2. 根据错误信息安装或重装特定包3. 在代码开头添加sys.path.append(‘项目根路径’)CUDA error: out of memoryGPU显存不足运行nvidia-smi观察显存占用1. 减小输入视频分辨率/长度2. 确保batch_size13. 关闭其他占用GPU的程序4. 使用更小的模型配置训练过程中loss为NaN或爆炸1. 学习率LR过高2. 数据预处理有问题如数值范围异常3. 模型初始化问题1. 检查训练日志开头的学习率2. 检查预处理后的数据如图片像素值是否在[0,1]或[0,255]1. 将LR调低一个数量级如从1e-3调到1e-42. 规范化输入数据3. 尝试不同的随机种子预处理失败无法生成相机参数1. 视频格式不支持2. 依赖的SFM运动恢复结构工具失败3. 背景太复杂特征点匹配失败1. 检查预处理脚本日志2. 尝试将视频转为标准MP4/H.264格式3. 尝试背景更简单、人物动作幅度小的视频1. 使用ffmpeg转换视频格式2. 手动提供相机参数如果项目支持3. 更换输入视频生成的数字人模糊或抖动严重1. 训练轮数不足2. 视频本身模糊或抖动3. 重建的3D几何不稳定1. 检查训练轮数和loss曲线是否已收敛2. 检查原始视频质量3. 查看不同视角的渲染是否一致1. 增加训练轮数num_epochs2. 使用更清晰、稳定的源视频3. 尝试调整与时空平滑性相关的损失函数权重在配置文件中换装时纹理错位或穿透1. 服装纹理图的UV布局不匹配2. 人体模型SMPL与服装模型不兼容3. 重建的几何不够准确1. 对比生成的纹理贴图和项目要求的模板2. 检查换装代码中关于顶点对应关系的部分1. 使用项目提供的标准服装纹理模板2. 确保训练时使用了正确的人体模型参数3. 提升基础模型的重建质量推理渲染速度极慢1. 渲染分辨率过高2. 未使用GPU进行渲染3. 代码未优化1. 查看推理脚本中的resolution参数2. 检查torch.cuda.is_available()在推理时是否为True1. 降低输出视频的分辨率2. 确保模型和输入数据都在GPU上.cuda()3. 如果只是预览可以降低渲染的采样点数9. 最佳实践与使用建议为了更高效、稳定地使用4DAnyone遵循以下实践建议可以节省大量时间。从小开始迭代验证第一次运行务必使用短3-5秒、低分辨率512x512、背景干净、人物动作简单的视频进行测试。目标是让整个流程先跑通。验证管线确保从预处理、训练到推理的每一步都成功再尝试更复杂的视频。参数调整每次只调整一个关键参数如学习率、训练轮数并观察效果变化。建立可复现的环境冻结依赖在虚拟环境中使用pip freeze requirements_lock.txt保存所有包的确切版本。这是复现结果或与他人协作的基石。记录配置将每次成功实验的配置文件.yaml和启动命令一起保存。使用版本控制对代码的修改使用Git管理。系统化管理数据与实验your_project/ ├── data/ │ ├── raw_videos/ # 存放原始视频 │ ├── processed/ # 存放预处理后的数据按视频名分子目录 │ └── garments/ # 存放服装纹理图 ├── experiments/ │ ├── exp_video1_epoch500/ # 每次实验独立目录 │ ├── exp_video2_lowLR/ # 目录名包含关键信息 │ └── ... ├── outputs/ # 最终渲染结果 └── scripts/ # 自己的批处理和工具脚本训练过程监控与早期止损开启可视化设置vis_interval为较小的值如50轮定期查看生成的预览图。如果前几百轮后图像仍是无意义的噪声可能意味着训练失败应尽早停止检查。监控Loss曲线使用TensorBoard或简单的日志绘图工具观察损失值是否平稳下降。如果Loss震荡剧烈或上升需要调整学习率。合规与伦理先行授权文件存档对于任何用于创建数字人的真人视频保留好肖像使用授权书。结果审核在公开或使用生成的4D数字人前进行结果审核确保其符合公序良俗未被用于制造虚假信息。标注技术来源在学术或公开场合使用该技术生成的内容考虑注明使用了“4DAnyone”等相关技术。4DAnyone 代表了从单目视频创建可编辑4D数字人的前沿方向。它的价值在于将原本需要专业设备和技术的工作流部分地迁移到了普通视频和算法上。虽然目前对硬件要求高、流程复杂且距离“一键生成”还有距离但它为数字内容创作和研究打开了一扇新的大门。最值得尝试的点是它的换装能力这为数字人资产的个性化提供了新思路。最先应该验证的是短视频的重建管线确保你的环境能从数据到模型完整跑通。最容易踩的坑是环境配置和显存不足务必严格按照项目要求准备环境并从极小规模的测试开始。后续可以探索将生成的4D模型导出到主流3D引擎如Unity、Unreal Engine或研究如何与实时驱动方案结合向更低延迟的应用场景迈进。这个领域迭代迅速保持对项目官方仓库和社区讨论的关注是跟上进展的最好方式。
返回列表