2023-2024 年,许多打算机视觉模型都专注于创建逼真的人类图像。
虽然存在许多用于姿势估计和分割等任务的模型,但 Meta 的 Sapiens 模型是专门为与人类干系的任务而设计的。

本博客阐明了 Meta 如何创建这个统一模型、优缺陷以及它与其他模型的比较。

NSDT工具推举: Three.js AI纹理开拓包 - YOLO合成数据天生器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - AI模型在线查看 - Three.js虚拟轴心开拓包 - 3D模型在线减面 - STL模型在线切割

1、Meta Sapiens 的三大支柱

Meta 声称,用于与人类干系的任务的模型该当知足以下三个关键品质:

Meta Sapiens 人体AI统一模型

泛化:这意味着该模型在许多不同情形下都能很好地事情。
例如,它可以处理不同的光照条件、相机角度,乃至各种类型的衣服。
广泛适用:该模型可以做很多事情。
它可以估计姿势、识别身体部位,乃至预测某物与相机的间隔,所有这些都不须要进行大的改变。
高保真度:它可以创建高质量、详细的结果。
例如,如果任务是天生一个人的 3D 模型,结果将看起来非常逼真,具有清晰的细节,如面部特色和身体形状。

Meta Sapiens 利用一些强大的技能来实现这些任务。
让我们大略地看一下个中的一些:

MAE(蒙版自动编码器):将其视为一种利用拼图进行有效学习的方法。
该模型查看短缺一些部分的图像(如短缺部分的拼图),并考试测验补充空缺。
这使模型更好地理解图像并节省演习韶光。
例如,如果模型在图像中看到一个人手臂的一部分缺失落,它可以通过理解图像的别的部分来预测手臂该当是什么样子。
利用关键点和分割:该模型识别人体上的 308 个点,包括手、脚、脸和躯干。
它还知道大约 28 个不同的身体部位,从头发到嘴唇再到四肢,非常详细。
为了演习模型,Meta 利用了真实的人体扫描和合成数据,这有助于它非常详细地理解人类。
2、2D 姿势估计 - 理解人体运动

这项任务就像给模型一张图片,并哀求它预测关键身体部位在哪里。
该模型会探求眼睛、肘部、膝盖等的位置。
例如,如果您上传某人跑步的照片,该模型可以准确识别他们的手臂、腿和头部在图像中的位置。

该过程通过创建“热图”来事情,这些热图显示了身体部位在特定位置的可能性。
该模型经由演习,通过调度直到其预测(热图)与身体部位的实际位置紧密匹配,以最大限度地减少缺点。

架构:

输入:图像(I ∈ R^H×W×3,个中 H 为高度,W 为宽度)。
步骤 1:重新缩放图像 — 输入图像被调度为固定高度 H 和宽度 W。
这样做是为了在所有图像中标准化输入大小。
步骤 2:姿势估计变换器 (P) — 变换器模型处理图像以预测关键点位置。
这涉及:a)边界框输入:在图像中的人周围供应一个边界框。
b)关键点热图:该模型天生 K 个热图,个中每个热图代表关键点位于某个位置的概率。
例如,一个热图代表右肘,另一个代表左膝,依此类推。
步骤 3:丢失函数(均方偏差) — 这里利用的丢失函数是均方偏差 (MSE)。
该模型将预测的热图 ŷ ∈ R^H×W×K 与地面真实关键点 y 进行比较,并利用 MSE 打算差异: L_pose = MSE(y, ŷ)步骤 4:编码器-解码器架构 - 姿势估计模型利用编码器-解码器设置。
编码器利用预演习的权重初始化,而解码器则随机初始化。
然后对全体系统进行微调以完成关键点预测任务。
关键点差异:与之前的模型(可能只能检测 68 个面部点)比较,Meta 的 Sapiens 模型可以检测多达 243 个面部关键点,捕捉眼睛、嘴唇、鼻子、耳朵等周围的更风雅的细节。
2.1 代码实现

下载姿势模型的检讨点并按照后续步骤操作:

TASK = 'pose'VERSION = 'sapiens_1b'model_path = get_model_path(TASK, VERSION)print(model_path)

利用“sapiens”模型函数定义姿势函数:

def get_pose(image, pose_estimator, input_shape=(3, 1024, 768), device="cuda"): # Preprocess the image img = preprocess_image(image, input_shape) # Run the model with torch.no_grad(): heatmap = pose_estimator(img.to(device)) # Post-process the output keypoints, keypoint_scores = udp_decode(heatmap[0].cpu().float().numpy(), input_shape[1:], (input_shape[1] // 4, input_shape[2] // 4)) # Scale keypoints to original image size scale_x = image.width / input_shape[2] scale_y = image.height / input_shape[1] keypoints[:, 0] = scale_x keypoints[:, 1] = scale_y # Visualize the keypoints on the original image pose_image = visualize_keypoints(image, keypoints, keypoint_scores) return pose_imagedef preprocess_image(image, input_shape): # Resize and normalize the image img = image.resize((input_shape[2], input_shape[1])) img = np.array(img).transpose(2, 0, 1) img = torch.from_numpy(img).float() img = img[[2, 1, 0], ...] # RGB to BGR mean = torch.tensor([123.675, 116.28, 103.53]).view(3, 1, 1) std = torch.tensor([58.395, 57.12, 57.375]).view(3, 1, 1) img = (img - mean) / std return img.unsqueeze(0)def udp_decode(heatmap, img_size, heatmap_size): # This is a simplified version. You might need to implement the full UDP decode logic h, w = heatmap_size keypoints = np.zeros((heatmap.shape[0], 2)) keypoint_scores = np.zeros(heatmap.shape[0]) for i in range(heatmap.shape[0]): hm = heatmap[i] idx = np.unravel_index(np.argmax(hm), hm.shape) keypoints[i] = [idx[1] img_size[1] / w, idx[0] img_size[0] / h] keypoint_scores[i] = hm[idx] return keypoints, keypoint_scoresdef visualize_keypoints(image, keypoints, keypoint_scores, threshold=0.3): draw = ImageDraw.Draw(image) for (x, y), score in zip(keypoints, keypoint_scores): if score > threshold: draw.ellipse([(x-2, y-2), (x+2, y+2)], fill='red', outline='red') return image

加载输入图像:

from utils.vis_utils import resize_imagepil_image = Image.open('path/to/input/image')if pil_image.mode == 'RGBA': pil_image = pil_image.convert('RGB')resized_pil_image = resize_image(pil_image, (640, 480))resized_pil_image

输出:

from PIL import Image, ImageDrawoutput_pose = get_pose(resized_pil_image, model)

3、身体部位分割——理解人体形状

在此任务中,模型对图像中的每个像素进行分类,将其分解为手臂、腿或脸等身体部位。
例如,如果你上传一张照片,模型可以将你的脸与头发分开,将你的手与手臂分开。
这有助于虚拟试穿系统或动画角色等任务。

Meta 的 Sapiens 模型利用大量词汇表(28 个身体部位)来供应详细的结果。
它不仅限于手臂和腿,还可以区分高下嘴唇、牙齿乃至手指。

架构:

输入:图像(I ∈ R^H×W×3),类似于姿势估计。
步骤 1:编码器-解码器架构——身体部位分割模型遵照与姿势估计相同的编码器-解码器设置。
编码器从输入图像中提取特色,解码器将这些特色转换为逐像素预测。
步骤 2:像素分类 — 该模型将图像的每个像素分类为 C 个身体部位种别之一(例如,头部、手臂、躯干等)。
例如,在标准分割中 C = 20,但 Meta 将其扩展为 C = 28,并供应了更详细的词汇,包括上/下嘴唇、牙齿和舌头等差异。
步骤 3:丢失函数(加权交叉熵) — 该模型利用加权交叉熵丢失进行微调,将预测的身体部位种别 p̂ 与地面原形 p 进行比较。
L_seg = WeightedCE(p, p̂)步骤 4:扩展词汇表和分辨率 — Sapiens 模型利用高分辨率图像(4K 分辨率)并手动注释了超过 100K 张带有这些详细身体部位标签的图像。
与之前的模型比较,分割词汇量要大得多,使其对人体部位有了更细致的理解。

把稳:只管 Meta Sapiens 在身体部位分割方面取得了进展,但它仍旧没有达到与 SAM 或 SAM2 等基于掩模的分割模型相同的精度水平。
这些模型供应了更准确、更详细的掩模,特殊是对付细粒度的工具边界。

3.1 代码实现

加载分割权重并按照以下步骤操作:

def get_model_path(task, version): try: model_path = SAPIENS_LITE_MODELS_PATH[task][version] if not os.path.exists(model_path): print(f"Warning: The model file does not exist at {model_path}") return model_path except KeyError as e: print(f"Error: Invalid task or version. {e}") return None# Example usageTASK = 'seg' VERSION = 'sapiens_0.3b'model_path = get_model_path(TASK, VERSION)print(model_path)

从“sapiens”模型实现分割函数:

def segment(image): input_tensor = transform_fn(image).unsqueeze(0).to("cuda") preds = run_model(input_tensor, height=image.height, width=image.width) mask = preds.squeeze(0).cpu().numpy() mask_image = Image.fromarray(mask.astype("uint8")) blended_image = visualize_mask_with_overlay(image, mask_image, LABELS_TO_IDS, alpha=0.5) return blended_image

加载输入图像:

pil_image = Image.open('sapiens2.jpg')if pil_image.mode == 'RGBA': pil_image = pil_image.convert('RGB')resized_pil_image = resize_image(pil_image, (640, 480))resized_pil_image

元分割的输出结果并不令人满意,它没有清晰地显示人体分割;相反,Meta 的 SAM[分割任何模型] 可以更好地分割图像。

4、深度估计——有多远?

深度估计有助于模型理解图像不同部分的间隔。
这就像让模型能够分辨照片中哪些东西近,哪些东西远。
例如,在一个人站在汽车阁下的照片中,模型可以估计这个人离汽车有多远,这对付增强现实等任务很主要。

架构:

输入:图像( I ∈ R^H×W×3)。
步骤 1:编码器-解码器架构——与身体部位分割类似,编码器从图像中提取特色,解码器预测每个像素的深度。
步骤 2:单通道深度图——深度估计的关键差异在于输出通道设置为 1,从而天生深度图。
此深度图( d̂ ∈ R^H×W)预测图像中每个点与相机的间隔。
步骤 3:丢失函数(回归)——深度估计任务被视为回归问题。
该模型将其预测的深度值 (d̂) 与地面实况 (d) 进行比较,并利用回归丢失最小化差异: L_depth = ||d − d̂||1步骤 4:在合成数据上进行演习——为了改进其深度预测,Meta Sapiens 利用合成人体数据,包括来自 RenderPeople 的 600 个高分辨率人体 3D 扫描。
这使得模型纵然在困难的情形下也能天生详细而逼真的深度估计。
4.1 代码实现

加载深度权重:

TASK = 'depth'VERSION = 'sapiens_0.3b'model_path = get_model_path(TASK, VERSION)print(model_path)

利用“sapiens”模型编写深度函数:

def get_depth(image, depth_model, input_shape=(3, 1024, 768), device="cuda"): # Preprocess the image img = preprocess_image(image, input_shape) # Run the model with torch.no_grad(): result = depth_model(img.to(device)) # Post-process the output depth_map = post_process_depth(result, (image.shape[0], image.shape[1])) # Visualize the depth map depth_image = visualize_depth(depth_map) return depth_image, depth_mapdef preprocess_image(image, input_shape): img = cv2.resize(image, (input_shape[2], input_shape[1]), interpolation=cv2.INTER_LINEAR).transpose(2, 0, 1) img = torch.from_numpy(img) img = img[[2, 1, 0], ...].float() mean = torch.tensor([123.5, 116.5, 103.5]).view(-1, 1, 1) std = torch.tensor([58.5, 57.0, 57.5]).view(-1, 1, 1) img = (img - mean) / std return img.unsqueeze(0)def post_process_depth(result, original_shape): # Check the dimensionality of the result if result.dim() == 3: result = result.unsqueeze(0) elif result.dim() == 4: pass else: raise ValueError(f"Unexpected result dimension: {result.dim()}") # Ensure we're interpolating to the correct dimensions seg_logits = F.interpolate(result, size=original_shape, mode="bilinear", align_corners=False).squeeze(0) depth_map = seg_logits.data.float().cpu().numpy() # If depth_map has an extra dimension, squeeze it if depth_map.ndim == 3 and depth_map.shape[0] == 1: depth_map = depth_map.squeeze(0) return depth_mapdef visualize_depth(depth_map): # Normalize the depth map min_val, max_val = np.nanmin(depth_map), np.nanmax(depth_map) depth_normalized = 1 - ((depth_map - min_val) / (max_val - min_val)) # Convert to uint8 depth_normalized = (depth_normalized 255).astype(np.uint8) # Apply colormap depth_colored = cv2.applyColorMap(depth_normalized, cv2.COLORMAP_INFERNO) return depth_colored# You can add the surface normal calculation if neededdef calculate_surface_normal(depth_map): kernel_size = 7 grad_x = cv2.Sobel(depth_map.astype(np.float32), cv2.CV_32F, 1, 0, ksize=kernel_size) grad_y = cv2.Sobel(depth_map.astype(np.float32), cv2.CV_32F, 0, 1, ksize=kernel_size) z = np.full(grad_x.shape, -1) normals = np.dstack((-grad_x, -grad_y, z)) normals_mag = np.linalg.norm(normals, axis=2, keepdims=True) with np.errstate(divide="ignore", invalid="ignore"): normals_normalized = normals / (normals_mag + 1e-5) normals_normalized = np.nan_to_num(normals_normalized, nan=-1, posinf=-1, neginf=-1) normal_from_depth = ((normals_normalized + 1) / 2 255).astype(np.uint8) normal_from_depth = normal_from_depth[:, :, ::-1] # RGB to BGR for cv2 return normal_from_depth

加载输入图像:

from utils.vis_utils import resize_imagepil_image = Image.open('/home/user/app/assets/image.webp')# Load and process an imageimage = cv2.imread('/home/user/app/assets/frame.png')depth_image, depth_map = get_depth(image, model)

输出:

surface_normal = calculate_surface_normal(depth_map)cv2.imwrite("output_surface_normal.jpg", surface_normal)# Save the resultsoutput_im = cv2.imwrite("output_depth_image2.jpg", depth_image)

5、表面法线估计——理解表面

此任务让模型找出人体的 3D 表面细节,例如每个点的表面角度或方向。
例如,它可以理解一个人脸部的曲线或手臂和腿的角度。

架构:

输入:图像( I ∈ R^H×W×3)。
步骤 1:编码器-解码器架构——与前面的任务一样,法线估计模型利用编码器-解码器框架。
编码器从图像中提取特色,解码器针对法线预测进行调度。
步骤 2:表面法线的三通道输出——对付法线估计,解码器输出通道设置为 3,对应于法线矢量的 xyz 分量。
每个像素都会得到一个 xyz 值,表示该点的表面朝向的方向。
步骤 3:丢失函数(余弦相似度)——该模型利用 L1 丢失和余弦相似度的组合来比较预测的法线向量 (n̂) 与地面真实法线 (n)。
丢失打算如下: L_normal = ||n − n̂||1 + (1 − n · n̂)步骤 4:从合成数据进行监督——与深度估计一样,法线估计依赖于合成人体数据进行监督。
这使得模型能够准确预测表面方向,纵然在波折的身体部位或极度姿势等繁芜情形下也是如此。
5.1 代码实现

加载法线权重:

TASK = 'normal'VERSION = 'sapiens_0.3b'model_path = get_model_path(TASK, VERSION)print(model_path)

从“智人”模型定义法线函数:

import torchimport torch.nn.functional as Fimport numpy as npimport cv2def get_normal(image, normal_model, input_shape=(3, 1024, 768), device="cuda"): # Preprocess the image img = preprocess_image(image, input_shape) # Run the model with torch.no_grad(): result = normal_model(img.to(device)) # Post-process the output normal_map = post_process_normal(result, (image.shape[0], image.shape[1])) # Visualize the normal map normal_image = visualize_normal(normal_map) return normal_image, normal_mapdef preprocess_image(image, input_shape): img = cv2.resize(image, (input_shape[2], input_shape[1]), interpolation=cv2.INTER_LINEAR).transpose(2, 0, 1) img = torch.from_numpy(img) img = img[[2, 1, 0], ...].float() mean = torch.tensor([123.5, 116.5, 103.5]).view(-1, 1, 1) std = torch.tensor([58.5, 57.0, 57.5]).view(-1, 1, 1) img = (img - mean) / std return img.unsqueeze(0)def post_process_normal(result, original_shape): # Check the dimensionality of the result if result.dim() == 3: result = result.unsqueeze(0) elif result.dim() == 4: pass else: raise ValueError(f"Unexpected result dimension: {result.dim()}") # Ensure we're interpolating to the correct dimensions seg_logits = F.interpolate(result, size=original_shape, mode="bilinear", align_corners=False).squeeze(0) normal_map = seg_logits.float().cpu().numpy().transpose(1, 2, 0) # H x W x 3 return normal_mapdef visualize_normal(normal_map): normal_map_norm = np.linalg.norm(normal_map, axis=-1, keepdims=True) normal_map_normalized = normal_map / (normal_map_norm + 1e-5) # Add a small epsilon to avoid division by zero # Convert to 0-255 range and BGR format for visualization normal_map_vis = ((normal_map_normalized + 1) / 2 255).astype(np.uint8) normal_map_vis = normal_map_vis[:, :, ::-1] # RGB to BGR return normal_map_visdef load_normal_model(checkpoint, use_torchscript=False): if use_torchscript: return torch.jit.load(checkpoint) else: model = torch.export.load(checkpoint).module() model = model.to("cuda") model = torch.compile(model, mode="max-autotune", fullgraph=True) return model

输入图像:

import cv2import numpy as np# Load the modelnormal_model = load_normal_model(model_path, use_torchscript='_torchscript')# Load the imageimage = cv2.imread("/home/user/app/assets/image.webp")

输出图像:

6、结束语

只管 Meta Sapiens 在理解与人类干系的任务方面表现出色,但它在更繁芜的场景中也面临寻衅。
例如,当多个人站得很近(拥挤)或个人摆出不屈常或罕见的姿势时,模型很难准确估计姿势并分割身体部位。
此外,严重的遮挡——当身体的某些部位被遮挡时——进一步增加了模型供应精确结果的能力。

Meta Sapiens 代表了以人为本的人工智能向前迈出的主要一步,在姿势估计、分割、深度预测和表面法线估计方面供应了强大的功能。
然而,与许多模型一样,它仍旧存在局限性,特殊是在拥挤或高度繁芜的场景中。
随着人工智能的不断发展,像 Sapiens 这样的模型的未来迭代有望办理这些寻衅,使我们更靠近更准确、更可靠的以人为本的运用程序。

原文链接:Meta Sapiens 人体AI - BimAnt