image.png

🏆 第二届3D视觉创新应用竞赛优秀作品 | 🎯 核心技术:人物抠像 · 动作捕捉 · 骨架识别与跟踪 | 📷 硬件平台:Astro Pro 相机

> 开源地址:[GitHub – Skeleton_3D-virtual-drag]

🧠 项目概述

网购衣服最痛的点是什么?“买家秀”与“卖家秀”的惨烈对比。因为无法试穿,近56%的消费者在网购衣物时踩过坑,导致退货率居高不下。传统的虚拟试衣系统要么建模成本高昂,要么就是贴图式的“2D换头术”,毫无真实感可言。

Skeleton_3D-virtual-drag 正是为了打破这一痛点而生。本项目立足于 Astro Pro 深度相机,结合 OpenPose 三维骨架追踪技术与 ADAM 人体模型,打造了一套“衣随人动、360度无死角”的 AR 虚拟试衣系统。不需要脱衣,往镜头前一站,你就是元宇宙里最靓的仔!

✨ 核心亮点:

– 真实体型拟合:基于特征识别与自动抠图,提取真实身材数据,告别“均码模型”;

– 毫秒级动捕:20个关键关节实时追踪,3D模型与人体动作神同步,照镜子即视感;

– 物理级布料渲染:布料解算加持,衣服不再是死板的贴图,而是有动态质感的“真衣服”。

🏗️ 系统架构

本系统采用经典的多线程流水线架构,基于 OpenPose 的 Worker 机制将繁重的计算任务拆解,确保实时性。整体数据流如下:

阶段 模块 职责 技术选型
1. 感知层 图像采集与标定 获取多视角图像,去畸变,提取内外参 Astro Pro, OpenCV calib3d
2. 识别层 2D骨架提取 检测人体20个关键点 OpenPose Pose 模块
3. 重建层 3D姿态三角化 多视角2D点融合为3D空间坐标 OpenPose PoseTriangulation
4. 拟合层 人体模型驱动 将3D关键点映射至 ADAM 人体模型 JointAngleEstimation, Eigen
5. 渲染层 抠图与布料解算 前景提取,布料物理仿真叠加 贝叶斯抠图,AR渲染引擎
image.png

🧩 核心模块说明

为了避免把大家淹没在源码的汪洋大海中,我们提炼了系统中最硬核的几个模块设计:

1. 多目相机标定与去畸变 (CameraParameterReader)

想算准3D骨架,相机的内外参必须拿捏得死死的。这个模块不仅负责读取 XML 格式的相机参数,还内置了去畸变功能。

– 设计取舍:采用 PIMPL(指向实现的指针)惯用法,将 OpenCV 的 Matrix 等重型依赖隐藏在 cpp 文件中,保持了头文件的轻量化,编译速度 ++。

class OP_API CameraParameterReader
    {
    public:
        explicit CameraParameterReader();

        virtual ~CameraParameterReader();

        // cameraExtrinsics is optional
        explicit CameraParameterReader(const std::string& serialNumber,
                                       const Matrix& cameraIntrinsics,
                                       const Matrix& cameraDistortion,
                                       const Matrix& cameraExtrinsics = Matrix(),
                                       const Matrix& cameraExtrinsicsInitial = Matrix());

        // serialNumbers is optional. If empty, it will load all the XML files available in the
        // cameraParameterPath folder
        void readParameters(const std::string& cameraParameterPath,
                            const std::vector<std::string>& serialNumbers = {});

        // It simply calls the previous readParameters with a single element
        void readParameters(const std::string& cameraParameterPath,
                            const std::string& serialNumber);

        void writeParameters(const std::string& cameraParameterPath) const;

        unsigned long long getNumberCameras() const;

        const std::vector<std::string>& getCameraSerialNumbers() const;

        const std::vector<Matrix>& getCameraMatrices() const;

        const std::vector<Matrix>& getCameraDistortions() const;

        const std::vector<Matrix>& getCameraIntrinsics() const;

        const std::vector<Matrix>& getCameraExtrinsics() const;

        const std::vector<Matrix>& getCameraExtrinsicsInitial() const;

        bool getUndistortImage() const;

        void setUndistortImage(const bool undistortImage);

        void undistort(Matrix& frame, const unsigned int cameraIndex = 0u);

    private:
        // PIMPL idiom
        // http://www.cppsamples.com/common-tasks/pimpl.html
        struct ImplCameraParameterReader;
        std::shared_ptr<ImplCameraParameterReader> spImpl;

        DELETE_COPY(CameraParameterReader);
    };

2. 3D骨架三角化 (PoseTriangulation)

单目相机只能猜深度,多目相机才能算真值。该模块接收多台相机的 2D 关键点向量,通过三角测量重建出真实的 3D 骨架。

– 关键参数:minViews3d。至少需要几台相机看到同一个点才进行重建?这是精度与鲁棒性的博弈,默认设置为 2,在遮挡严重时可适当调高。

class OP_API PoseTriangulation
    {
    public:
        PoseTriangulation(const int minViews3d);

        virtual ~PoseTriangulation();

        void initializationOnThread();

        Array<float> reconstructArray(
            const std::vector<Array<float>>& keypointsVector, const std::vector<Matrix>& cameraMatrices,
            const std::vector<Point<int>>& imageSizes) const;

        std::vector<Array<float>> reconstructArray(
            const std::vector<std::vector<Array<float>>>& keypointsVector, const std::vector<Matrix>& cameraMatrices,
            const std::vector<Point<int>>& imageSizes) const;

    private:
        const int mMinViews3d;
    };

3. 关节角度估算与模型驱动 (JointAngleEstimation)

这是从“骨架”走向“皮肉”的关键一步。3D关键点只是火柴人,怎么变成有血有肉的虚拟人?

– 核心逻辑:利用 adamFastFit 函数,将 OpenPose 的 3D Keypoints 快速拟合到 ADAM 参数化人体模型上,计算出 ADAM 模型的姿态参数和位移向量,从而驱动整个网格变形。

class OP_API JointAngleEstimation
    {
    public:
        static const std::shared_ptr<const TotalModel> getTotalModel();

        JointAngleEstimation(const bool returnJacobian);

        virtual ~JointAngleEstimation();

        void initializationOnThread();

        void adamFastFit(Eigen::Matrix<double, 62, 3, Eigen::RowMajor>& adamPose,
                         Eigen::Vector3d& adamTranslation,
                         Eigen::Matrix<double, Eigen::Dynamic, 1>& vtVec,
                         Eigen::Matrix<double, Eigen::Dynamic, 1>& j0Vec,
                         Eigen::VectorXd& adamFacecoeffsExp,
                         const Array<float>& poseKeypoints3D,
                         const Array<float>& faceKeypoints3D,
                         const std::array<Array<float>, 2>& handKeypoints3D);

    private:
        // PIMPL idiom
        // http://www.cppsamples.com/common-tasks/pimpl.html
        struct ImplJointAngleEstimation;
        std::shared_ptr<ImplJointAngleEstimation> spImpl;

        // PIMP requires DELETE_COPY & destructor, or extra code
        // http://oliora.github.io/2015/12/29/pimpl-and-rule-of-zero.html
        DELETE_COPY(JointAngleEstimation);
    };

4. 异步流水线调度 (W* 系列 Worker)

仔细看代码,你会发现 WPoseTriangulation 和 WJointAngleEstimation 都继承了 Worker<TDatums>。这是 OpenPose 的多线程魔法,每个 Worker 跑在独立线程中,通过 tDatums(数据包)传递数据。

class WPoseTriangulation : public Worker<TDatums>
    {
    public:
        explicit WPoseTriangulation(const std::shared_ptr<PoseTriangulation>& poseTriangulation);

        virtual ~WPoseTriangulation();

        void initializationOnThread();

        void work(TDatums& tDatums);

    private:
        const std::shared_ptr<PoseTriangulation> spPoseTriangulation;

        DELETE_COPY(WPoseTriangulation);
    };
class WJointAngleEstimation : public Worker<TDatums>
    {
    public:
        explicit WJointAngleEstimation(const std::shared_ptr<JointAngleEstimation>& jointAngleEstimation);

        virtual ~WJointAngleEstimation();

        void initializationOnThread();

        void work(TDatums& tDatums);

    private:
        const std::shared_ptr<JointAngleEstimation> spJointAngleEstimation;

        DELETE_COPY(WJointAngleEstimation);
    };

– 优势:图像采集、2D推理、3D重建、渲染互不阻塞,极大提升了帧率。

📑 设计要点

系统在算法选型上做了一些非常务实的考量:

🔪 特征识别与自动抠图

虚拟试衣的第一步是把人从背景里“抠”出来。系统采用了经典的图像抠图融合方程:

$C = alpha F + (1 – alpha) B $

其中 $C$ 是原图,$F$ 是前景,$B$ 是背景,$alpha$ 是透明度。

– 算法选择:文档中重点提及了贝叶斯抠图。相比于深度学习的分割模型,贝叶斯抠图在边缘处理(如头发丝与衣服交界处)的 $\alpha$ 估计上更加细腻,通过最大化似然准则联合求解 $F, B, \alpha$,避免了边缘的“毛边”现象,这对试衣的视觉真实感至关重要。

👗 布料解算与运动同步

“衣随人动”不是简单的刚体绑定。当人体关节运动时,衣服需要基于物理引擎进行解算(如重力、风力、碰撞检测)。系统将 ADAM 模型的运动数据作为边界条件输入布料解算器,实现了衣服的动态褶皱和形变。

🚀 使用示例

想要跑起来这个项目?只需简单的几步配置。我们在 flags.hpp 中预留了丰富的命令行参数(基于 gflags):

# 1. 克隆项目
git clone https://github.com/3DCVdeveloper/Skeleton_3D-virtual-drag.git

# 2. 运行核心程序
./3d_virtual_drag 
  --camera_resolution 1280x720 
  --logging_level 3            # 0-255,3是默认值,只输出重要信息
  --disable_multi_thread false  # 保持多线程开启,帧率更高

⚙️ 参数调优小贴士:

  • 如果你的 GPU 不够顶,出现明显延迟,可以尝试将 --disable_multi_thread 设为 true,牺牲帧率换取低延迟;
  • --profile_speed 1000 可以开启性能分析,帮你找到流水线中的性能瓶颈。

🧭 后续规划

虽然当前系统已经实现了完整的闭环,但作为追求极致的具身智能工程师,我们还有更远的路要走:

1. 轻量化部署:目前依赖 OpenPose 完整框架,略显笨重。计划将 2D/3D 推理部分替换为 TensorRT 加速的轻量级网络,向边缘计算设备(如 Jetson 系列)迁移;

2. 无标定重建:当前多目系统依赖严格的相机标定(calibration 模块),未来希望引入 Self-calibration 算法,让用户随便摆两台相机就能用;

3. OpenCV 版本升级:项目底层依赖较老的 OpenCV 2.4.9(从附带的参考手册可以看出),后续将全面升级至 OpenCV 4.x,享受更现代的 DNN 和 Mat 操控体验;

4. 手指精细操作:目前动捕聚焦于身体 20 关节,未来计划引入 hand 模块,让用户能够用手“虚拟拖拽(virtual drag)”衣服的拉链和纽扣,实现真正的交互试穿!

> 💡 技术让试衣更便捷,代码让世界更有趣。欢迎 Star & PR!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注