3DDFA-V3——基于人脸分割几何信息指导下的三维人脸重建

news2024/11/23 2:54:58

1. 研究背景

从二维图像中重建三维人脸是计算机视觉研究的一项关键任务。在虚拟现实、医疗美容、计算机生成图像等领域中,研究人员通常依赖三维可变形模型(3DMM)进行人脸重建,以定位面部特征和捕捉表情。然而,现有的方法往往难以准确重建出如闭眼、歪嘴、皱眉等极端表情。

为了增强3DMM对极端表情的捕捉能力,3DDFA-V3从训练策略和数据策略两个角度进行研究,以人脸分割为研究切入点,使用人脸部件分割的几何信息作为监督信号,设计损失函数,显著加强了对形状的约束,同时,3DDFA-V3设计了可靠的表情生成方法,能够大批量、可控地生成难以获取的极端表情人脸图像。

在这里插入图片描述

图1 3DDFA_V3 利用面部部件分割的几何指导进行人脸重建,提高了重建面部特征与原始图像的对齐精度,并在捕捉极端表情方面表现出色。
C++推理代码:https://download.csdn.net/download/matt45m/89934278

2.环境安装

项目地址 :https://github.com/wang-zidu/3DDFA-V3.git

# Clone the repo:
git clone https://github.com/wang-zidu/3DDFA-V3
cd 3DDFA-V3

conda create -n TDDFAV3 python=3.8
conda activate TDDFAV3

# The pytorch version is not strictly required.
pip install torch==1.12.1+cu102 torchvision==0.13.1+cu102 torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cu102
# or: conda install pytorch==1.12.1 torchvision==0.13.1 torchaudio==0.12.1 cudatoolkit=10.2 -c pytorch
# On Windows 10, it has been verified that version 1.10 works. You can install it with the following command: pip install torch==1.10.0+cu102 torchvision==0.11.0+cu102 torchaudio==0.10.0 -f https://download.pytorch.org/whl/torch_stable.html

pip install -r requirements.txt

# Some results in the paper are rendered by pytorch3d and nvdiffrast
# This repository only uses nvdiffrast for convenience.
git clone https://github.com/NVlabs/nvdiffrast.git
cd nvdiffrast
pip install .
cd ..

# In some scenarios, nvdiffrast may not be usable. Therefore, we additionally provide a fast CPU renderer based on face3d.
# The results produced by the two renderers may have slight differences, but we consider these differences to be negligible.
# Please note that we still highly recommend using nvdiffrast.
cd util/cython_renderer/
python setup.py build_ext -i
cd ..
cd ..

3. 研究内容

3.1 训练策略的研究

人脸部件分割能够以像素级的精度为每个面部特征提供准确的定位。相比常用的关键点信息,部件分割提供了覆盖整个图像的更密集的标签;相比纹理信息,部件分割不易受到光照等因素的干扰。现有利用分割信息来拟合三维人脸的尝试,依赖于可微渲染器生成预测的面部区域(如眼睛、眉毛、嘴唇等)轮廓,并使用IoU损失优化渲染轮廓与分割之间的差异。然而,由于可微渲染存在着局部最优、渲染误差传播和梯度不稳定性等缺陷,这些依赖可微渲染的IoU损失难以为人脸重建提供足够且稳定的几何引导信号。

3DDFA-V3的关键思想是将目标和预测的部件分割转化为语义点集,通过优化点集的分布来确保重建区域和目标具有相同的几何形态。具体来讲,3DDFA-V3提出了部件重投影距离损失(Part Re-projection Distance Loss, PRDL)。PRDL按照区域 left-eye, right-eye, left-eyebrow, right-eyebrow, up-lip, down-lip, nose, skin对人脸进行分块,针对二维部件分割的每个部分 ,PRDL首先在分割区域内采样点,得到目标点集 。然后,PRDL将三维人脸重建结果重新投影到图像平面上,并根据人脸模型的masks获得与目标区域语义一致的预测点集│,是人脸模型的系数。接着PRDL对图像平面的网格点进行采样,得到锚点集合,并计算任意一个锚点到点集的各种统计距离(如最近距离、最远距离、平均距离等)来建立几何描述子。最后,PRDL通过优化相同语义的预测点集的几何描述子和目标点集的几何描述子的差异,确保重建区域和目标具有相同的几何分布,从而提高目标和预测点集覆盖区域之间的重叠度,整个过程如图2所示。

在这里插入图片描述

图2 部件重投影距离损失(PRDL)概述。(a): 将面部部件分割转换为目标点集。(b): 将预测的三维人脸重投影到图像平面以获得预测点集│。©: 给定锚点和统计距离函数 ,PRDL的核心思想是最小化每个锚点到目标和预测点集的统计距离的差异。

概括来讲,对于,PRDL的优化目标为:

在实践过程中,PRDL可以使用最远点采样(Farthest Point Sampling)等技术可以减少、和中的点数量,从而降低计算成本。通过理论推导可知,在梯度下降条件下,PRDL的锚点对预测点有选择以及“推”和“拉”的作用,最终能使得锚点到预测点集和目标点集的统计距离尽可能相同,从而指导三维人脸的形变,如图3所示,当预测点被目标点包围时,锚点可以对预测点产生向外扩展的梯度下降方向。
在这里插入图片描述

图3 PRDL的梯度分析图,PRDL的锚点对预测点有选择以及“推”和“拉”的作用,最终的作用在预测点上的梯度是相关锚点作用的叠加,具有鲁棒性。

3.2 数据策略的研究

虚拟合成数据常用于训练三维人脸重建模型。现有的合成人脸数据要么侧重于背景、光照和身份的多样化,要么集中在姿态变化上,虽然在重建自然面部表情方面提供了有效的帮助,但在重建极端表情方面难以提供支持。为了克服这些局限并促进相关研究,3DDFA-V3采用了一种基于GAN的方法来大批量可控地合成难以搜集的人脸极端表情数据,包括闭眼、歪嘴和皱眉等表情。

在这里插入图片描述
图4 3DDFA-V3提出的大批量合成闭眼、歪嘴、皱眉等表情的方法。

如图4所示,我们首先使用人脸分割方法和关键点检测器分别获取原始图像的分割图和关键点,并预设一些人脸表情的局部变化模板。利用关键点的位置信息,对进行合适的仿射变换,将其应用到原始分割图上,得到。随后将输入到一个条件GAN网络中,生成新的面部表情图像。目前3DDFA-V3已经生成了超过50万张的闭眼、歪嘴、皱眉等表情的数据,并进行了开源,数据示例如图5所示。

图5 3DDFA-V3提供的虚拟合成表情数据示例。

4. 实验结果

4.1 定量对比实验

在这里插入图片描述
表1 3DDFA-V3在REALY benchmark上取得了SOTA的水平。

4.2 定性对比实验在这里插入图片描述

图6 与现有的SOTA方法相比,3DDFA-V3的重建结果可以非常准确地捕捉不对称和奇怪的面部变形。

在这里插入图片描述
在这里插入图片描述

5.模型推理

1. c++推理模型推理:

#include"face_reconstruction.h"
#include <opencv2/opencv.hpp>


using namespace std;
using namespace cv;
using namespace dnn;


Mat bilinear_interpolate(const Mat& img, const Mat& x, const Mat& y)
{
    img的形状是[1,3,h,w]
    const int num_pts = x.size[0];
    const int h = img.size[2];
    const int w = img.size[3];
    Mat out = Mat::zeros(num_pts, 3, CV_32FC1);
    for(int i=0;i<num_pts;i++)
    {
        int x0 = (int)floor(x.ptr<float>(i)[0]);
        int x1 = x0 + 1;
        int y0 = (int)floor(y.ptr<float>(i)[0]);
        int y1 = y0 + 1;

        x0 = std::max(std::min(x0, w-1), 0);
        x1 = std::max(std::min(x1, w-1), 0);
        y0 = std::max(std::min(y0, h-1), 0);
        y1 = std::max(std::min(y1, h-1), 0);

        float i_a[3] = {img.ptr<float>(0, 0, y0)[x0], img.ptr<float>(0, 1, y0)[x0], img.ptr<float>(0, 2, y0)[x0]};
        float i_b[3] = {img.ptr<float>(0, 0, y1)[x0], img.ptr<float>(0, 1, y1)[x0], img.ptr<float>(0, 2, y1)[x0]};
        float i_c[3] = {img.ptr<float>(0, 0, y0)[x1], img.ptr<float>(0, 1, y0)[x1], img.ptr<float>(0, 2, y0)[x1]};
        float i_d[3] = {img.ptr<float>(0, 0, y1)[x1], img.ptr<float>(0, 1, y1)[x1], img.ptr<float>(0, 2, y1)[x1]};

        float wa = (x1 - x.ptr<float>(i)[0]) * (y1 - y.ptr<float>(i)[0]);
        float wb = (x1 - x.ptr<float>(i)[0]) * (y.ptr<float>(i)[0] - y0);
        float wc = (x.ptr<float>(i)[0] - x0) * (y1 - y.ptr<float>(i)[0]);
        float wd = (x.ptr<float>(i)[0] - x0) * (y.ptr<float>(i)[0] - y0);

        out.ptr<float>(i)[0] = wa * i_a[0] + wb * i_b[0] + wc * i_c[0] + wd * i_d[0];
        out.ptr<float>(i)[1] = wa * i_a[1] + wb * i_b[1] + wc * i_c[1] + wd * i_d[1];
        out.ptr<float>(i)[2] = wa * i_a[2] + wb * i_b[2] + wc * i_c[2] + wd * i_d[2];
    }
    return out;
}

cv::Mat get_colors_from_uv(const cv::Mat& img, const cv::Mat& x, const cv::Mat& y)
{
    img的形状是[h,w,3]
    const int num_pts = x.size[0];
    const int h = img.size[0];
    const int w = img.size[1];
    Mat out = Mat::zeros(num_pts, 3, CV_32FC1);
    for(int i=0;i<num_pts;i++)
    {
        int x0 = (int)floor(x.ptr<float>(i)[0]);
        int x1 = x0 + 1;
        int y0 = (int)floor(y.ptr<float>(i)[0]);
        int y1 = y0 + 1;

        x0 = std::max(std::min(x0, w-1), 0);
        x1 = std::max(std::min(x1, w-1), 0);
        y0 = std::max(std::min(y0, h-1), 0);
        y1 = std::max(std::min(y1, h-1), 0);

        float i_a[3] = {img.ptr<float>(y0, x0)[0], img.ptr<float>(y0, x0)[1], img.ptr<float>(y0, x0)[2]};
        float i_b[3] = {img.ptr<float>(y1, x0)[0], img.ptr<float>(y1, x0)[1], img.ptr<float>(y1, x0)[2]};
        float i_c[3] = {img.ptr<float>(y0, x1)[0], img.ptr<float>(y0, x1)[1], img.ptr<float>(y0, x1)[2]};
        float i_d[3] = {img.ptr<float>(y1, x1)[0], img.ptr<float>(y1, x1)[1], img.ptr<float>(y1, x1)[2]};

        float wa = (x1 - x.ptr<float>(i)[0]) * (y1 - y.ptr<float>(i)[0]);
        float wb = (x1 - x.ptr<float>(i)[0]) * (y.ptr<float>(i)[0] - y0);
        float wc = (x.ptr<float>(i)[0] - x0) * (y1 - y.ptr<float>(i)[0]);
        float wd = (x.ptr<float>(i)[0] - x0) * (y.ptr<float>(i)[0] - y0);

        out.ptr<float>(i)[0] = wa * i_a[0] + wb * i_b[0] + wc * i_c[0] + wd * i_d[0];
        out.ptr<float>(i)[1] = wa * i_a[1] + wb * i_b[1] + wc * i_c[1] + wd * i_d[1];
        out.ptr<float>(i)[2] = wa * i_a[2] + wb * i_b[2] + wc * i_c[2] + wd * i_d[2];
    }
    return out;
}


Mat median_filtered_color_pca(const Mat& median_filtered_w, const Mat& uv_color_img, const Mat& uv_color_pca)
{
    /*median_filtered_w的形状是[1024,1024],数据类型是CV32FC3
    uv_color_img和uv_color_pca的形状都是[1024,1024,3], 数据类型是CV32FC1*/
    vector<int> shape = {uv_color_img.size[0], uv_color_img.size[1], 3};
    Mat out = Mat(shape, CV_32FC1);
    for(int h=0;h<uv_color_img.size[0];h++)
    {
        for(int w=0;w<uv_color_img.size[1];w++)
        {
            float x = uv_color_img.ptr<float>(h, w)[0];
            x = std::max(std::min(x, 1.f), 0.f);
            float y = uv_color_pca.ptr<float>(h, w)[0];
            y = std::max(std::min(y, 1.f), 0.f);
            out.ptr<float>(h, w)[0] = (1 - median_filtered_w.at<Vec3f>(h,w)[0]) * x + median_filtered_w.at<Vec3f>(h,w)[0] * y;

            x = uv_color_img.ptr<float>(h, w)[1];
            x = std::max(std::min(x, 1.f), 0.f);
            y = uv_color_pca.ptr<float>(h, w)[1];
            y = std::max(std::min(y, 1.f), 0.f);
            out.ptr<float>(h, w)[1] = (1 - median_filtered_w.at<Vec3f>(h,w)[1]) * x + median_filtered_w.at<Vec3f>(h,w)[1] * y;

            x = uv_color_img.ptr<float>(h, w)[2];
            x = std::max(std::min(x, 1.f), 0.f);
            y = uv_color_pca.ptr<float>(h, w)[2];
            y = std::max(std::min(y, 1.f), 0.f);
            out.ptr<float>(h, w)[2] = (1 - median_filtered_w.at<Vec3f>(h,w)[2]) * x + median_filtered_w.at<Vec3f>(h,w)[2] * y;
        }
    }
    return out;
}


FaceModel::FaceModel(std::map<string, bool> args_)
{
    string model_path = "models/net_recon.onnx";
    this->net_recon = readNet(model_path);
    this->outnames = this->net_recon.getUnconnectedOutLayersNames();
    this->args = args_;

    this->u = Mat(107127, 1, CV_32FC1);
	FILE* fp = fopen("models/u.bin", "rb");
	fread((float*)this->u.data, sizeof(float), this->u.total(), fp);//导入数据
	fclose(fp);//关闭文件

    this->id = Mat(107127, 80, CV_32FC1);
    fp = fopen("models/id.bin", "rb");
    fread((float*)this->id.data, sizeof(float), this->id.total(), fp);
    fclose(fp);

    this->exp_ = Mat(107127, 64, CV_32FC1);
    fp = fopen("models/exp.bin", "rb");
    fread((float*)this->exp_.data, sizeof(float), this->exp_.total(), fp);
    fclose(fp);

    this->u_alb = Mat(107127, 1, CV_32FC1);
    fp = fopen("models/u_alb.bin", "rb");
    fread((float*)this->u_alb.data, sizeof(float), this->u_alb.total(), fp);
    fclose(fp);

    this->alb = Mat(107127, 80, CV_32FC1);
    fp = fopen("models/alb.bin", "rb");
    fread((float*)this->alb.data, sizeof(float), this->alb.total(), fp);
    fclose(fp);

    int len = 35709*8;
    fp = fopen("models/point_buf.bin", "rb");
    fread(this->point_buf, sizeof(int), len, fp);
    fclose(fp);

    len = 70789*3;
    this->tri.resize(len);
    fp = fopen("models/tri.bin", "rb");
    fread(this->tri.data(), sizeof(int), len, fp);
    fclose(fp);

    len = 35709*2;
    this->uv_coords.resize(len);
    fp = fopen("models/uv_coords.bin", "rb");
    fread(this->uv_coords.data(), sizeof(float), len, fp);
    fclose(fp);
    process_uv
    const int uv_h = 1024;
    const int uv_w = 1024;
    this->uv_coords_numpy = Mat(35709, 3, CV_32FC1);
    this->uv_coords_torch = Mat(35709, 3, CV_32FC1);
    for(int i=0;i<35709;i++)
    {
        this->uv_coords_numpy.ptr<float>(i)[0] = this->uv_coords[i*2] * (uv_w - 1);
        this->uv_coords_numpy.ptr<float>(i)[1] = this->uv_coords[i*2+1] * (uv_h - 1);
        this->uv_coords_numpy.ptr<float>(i)[2] = 0;

        this->uv_coords_torch.ptr<float>(i)[0] = (this->uv_coords_numpy.ptr<float>(i)[0] / 1023.f - 0.5f)*2.f + 1e-6;
        this->uv_coords_torch.ptr<float>(i)[1] = (this->uv_coords_numpy.ptr<float>(i)[1] / 1023.f - 0.5f)*2.f + 1e-6;
        this->uv_coords_torch.ptr<float>(i)[2] = (this->uv_coords_numpy.ptr<float>(i)[2] / 1023.f - 0.5f)*2.f + 1e-6;

        this->uv_coords_numpy.ptr<float>(i)[1] = 1024 - this->uv_coords_numpy.ptr<float>(i)[1] - 1;
    }
    this->uv_renderer = std::make_shared<MeshRenderer_UV_cpu>(1024);

    len = 68;
    fp = fopen("models/ldm68.bin", "rb");
    fread(this->ldm68, sizeof(int), len, fp);
    fclose(fp);

    len = 106;
    fp = fopen("models/ldm106.bin", "rb");
    fread(this->ldm106, sizeof(int), len, fp);
    fclose(fp);

    len = 134;
    fp = fopen("models/ldm134.bin", "rb");
    fread(this->ldm134, sizeof(int), len, fp);
    fclose(fp);

    for(int i=0;i<this->annotation_shapes.size();i++)
    {
        len = this->annotation_shapes[i];
        vector<int> anno(len);
        string binpath = "models/annotation_"+to_string(i)+".bin";
        fp = fopen(binpath.c_str(), "rb");
        fread(anno.data(), sizeof(int), len, fp);
        fclose(fp);

        this->annotation.emplace_back(anno);
    }

    for(int i=0;i<this->annotation_tri_shapes.size();i++)
    {
        len = this->annotation_tri_shapes[i][0]*this->annotation_tri_shapes[i][1];
        vector<int> anno_tri(len);
        string binpath = "models/annotation_tri_"+to_string(i)+".bin";
        fp = fopen(binpath.c_str(), "rb");
        fread(anno_tri.data(), sizeof(int), len, fp);
        fclose(fp);

        this->annotation_tri.emplace_back(anno_tri);
    }

    for(int i=0;i<this->parallel_shapes.size();i++)
    {
        len = this->parallel_shapes[i];
        vector<int> para(len);
        string binpath = "models/parallel_"+to_string(i)+".bin";
        fp = fopen(binpath.c_str(), "rb");
        fread(para.data(), sizeof(int), len, fp);
        fclose(fp);

        this->parallel.emplace_back(para);
    }

    this->v_parallel.resize(35709, -1);
    for(int i=0;i<this->parallel.size();i++)
    {
        for(int j=0;j<this->parallel[i].size();j++)
        {
            this->v_parallel[this->parallel[i][j]] = i;
        }
    }

    const float rasterize_fov = 2 * atan(112. / 1015) * 180 / PI;
    this->renderer = std::make_shared<MeshRenderer_cpu>(rasterize_fov, 5.f, 15.f, 2*112);

    this->light_direction = this->lights.colRange(0,3);
    this->light_intensities = this->lights.colRange(3, this->lights.size[1]);
    this->light_direction_norm.resize(this->light_direction.size[0]);
    for(int i=0;i<this->light_direction.size[0];i++)
    {
        this->light_direction_norm[i] = cv::norm(this->light_direction.row(i));
    }
}

Mat FaceModel::compute_rotation(const Mat& angles)
{
    float x = angles.ptr<float>(0)[0];
    float y = angles.ptr<float>(0)[1];
    float z = angles.ptr<float>(0)[2];
    Mat rot_x = (Mat_<float>(3, 3) << 1, 0, 0, 0, cos(x), -sin(x), 0, sin(x), cos(x));
    Mat rot_y = (Mat_<float>(3, 3) << cos(y), 0, sin(y), 0, 1, 0, -sin(y), 0, cos(y));
    Mat rot_z = (Mat_<float>(3, 3) << cos(z), -sin(z), 0, sin(z), cos(z), 0, 0, 0, 1);
    
    Mat rot = rot_z * rot_y * rot_x;
    rot = rot.t();
    // vector<int> newshape = {1, 3, 3};   由于在c++的opencv里不支持3维Mat的矩阵乘法,此处不考虑batchsize维度
    // rot.reshape(0, newshape);
    return rot;
}

Mat FaceModel::transform(const Mat& face_shape, const Mat& rot, const Mat& trans)
{
    Mat out = face_shape * rot + cv::repeat(trans, face_shape.size[0], 1);   ///c++里没有numpy的广播机制,所以这里需要用repeat
    return out;
}

Mat FaceModel::to_camera(Mat& face_shape)
{
    face_shape.col(2) = this->camera_distance - face_shape.col(2);
    return face_shape;
}

Mat FaceModel::to_image(const Mat& face_shape)
{
    Mat face_proj = face_shape * this->persc_proj;
    face_proj.colRange(0, 2) = face_proj.colRange(0, 2) / cv::repeat(face_proj.col(2), 1, 2);
    return face_proj.colRange(0, 2);
}

Mat FaceModel::compute_shape(const Mat& alpha_id, const Mat& alpha_exp)
{
    Mat face_shape = alpha_id * this->id.t() + alpha_exp * this->exp_.t() + this->u.t();
    int len = face_shape.size[1] / 3;
    vector<int> newshape = {len, 3};   由于在c++的opencv里不支持3维Mat的矩阵乘法,此处不考虑batchsize维度
    face_shape = face_shape.reshape(0, newshape);
    return face_shape;
}

Mat FaceModel::compute_albedo(const Mat& alpha_alb, const bool normalize)
{
    Mat face_albedo = alpha_alb * this->alb.t() + this->u_alb.t();
    if(normalize)
    {
        face_albedo /= 255.f;
    }
    int len = face_albedo.size[1] / 3;
    vector<int> newshape = {len, 3};   由于在c++的opencv里不支持3维Mat的矩阵乘法,此处不考虑batchsize维度
    face_albedo = face_albedo.reshape(0, newshape);
    return face_albedo;
}

Mat FaceModel::compute_norm(const Mat& face_shape)
{
    Mat face_norm = Mat::zeros(this->tri_shape[0]+1, 3, CV_32FC1);
    for(int i=0;i<this->tri_shape[0];i++)
    {
        Mat v1 = face_shape.row(this->tri[i*3]);
        Mat v2 = face_shape.row(this->tri[i*3+1]);
        Mat v3 = face_shape.row(this->tri[i*3+2]);
        Mat e1 = v1 - v2;
        Mat e2 = v2 - v3;
        Mat n = e1.cross(e2);

        face_norm.row(i) = n / cv::norm(n);
    }
    
    Mat vertex_norm = Mat::zeros(this->point_buf_shape[0], 3, CV_32FC1);
    for(int i=0;i<this->point_buf_shape[0];i++)
    {
        Mat vs = Mat::zeros(this->point_buf_shape[1], 3, CV_32FC1);
        for(int j=0;j<this->point_buf_shape[1];j++)
        {
            face_norm.row(this->point_buf[i*this->point_buf_shape[1]+j]).copyTo(vs.row(j));
        }
        Mat vs_colsum;
        cv::reduce(vs, vs_colsum, 0, cv::REDUCE_SUM, CV_32FC1);   ///沿着列求和
        vertex_norm.row(i) = vs_colsum / cv::norm(vs_colsum);
    }
    return vertex_norm;
}

Mat FaceModel::compute_texture(const Mat& face_albedo, const Mat& face_norm, const Mat& alpha_sh)
{
    Mat alpha_sh_ = alpha_sh.reshape(0, {3, 9});   不考虑batchsize维度
    alpha_sh_ += cv::repeat(this->init_lit, 3, 1);
    alpha_sh_ = alpha_sh_.t();
    Mat Y = Mat::zeros(face_norm.size[0], 9, CV_32FC1);
    Y.col(0) = this->SH_a[0] * this->SH_c[0] * Mat::ones(face_norm.size[0], 1, CV_32FC1);
    Y.col(1) = -this->SH_a[1] * this->SH_c[1] * face_norm.col(1);
    Y.col(2) = this->SH_a[1] * this->SH_c[1] * face_norm.col(2);
    Y.col(3) = -this->SH_a[1] * this->SH_c[1] * face_norm.col(0);
    Y.col(4) = this->SH_a[2] * this->SH_c[2] * face_norm.col(0).mul(face_norm.col(1));  ///python里的*是对应元素相乘,@是矩阵乘法, 而c++里的*是矩阵乘法,mul是对应元素相乘
    Y.col(5) = -this->SH_a[2] * this->SH_c[2] * face_norm.col(1).mul(face_norm.col(2));
    Y.col(6) = 0.5 * this->SH_a[2] * this->SH_c[2] / sqrt(3.f) * (3 * face_norm.col(2).mul(face_norm.col(2)) - 1.f);
    Y.col(7) = -this->SH_a[2] * this->SH_c[2] * face_norm.col(0).mul(face_norm.col(2));
    Y.col(8) = 0.5 * this->SH_a[2] * this->SH_c[2] * (face_norm.col(2).mul(face_norm.col(2))  - face_norm.col(1).mul(face_norm.col(1)));
    
    Mat face_texture = Mat::zeros(face_albedo.size[0], 3, CV_32FC1);
    face_texture.col(0) = Y * alpha_sh_.col(0);
    face_texture.col(1) = Y * alpha_sh_.col(1);
    face_texture.col(2) = Y * alpha_sh_.col(2);
    face_texture = face_texture.mul(face_albedo);
    return face_texture;
}

Mat FaceModel::compute_gray_shading_with_directionlight(const Mat& face_texture, const Mat& normals)
{
    Mat texture = Mat(normals.size[0], 3, CV_32FC1);   ///不考虑batchsize维度
    for(int i=0;i<normals.size[0];i++)
    {
        float sum_[3] = {0.f, 0.f, 0.f};
        for(int j=0;j<this->lights.size[0];j++)
        {
            const float x = this->light_direction_norm[j];
            float y = cv::sum(normals.row(i).mul(this->light_direction.row(j) / x))[0];
            y = std::max(std::min(y, 1.f), 0.f);
            sum_[0] += (y*this->light_intensities.ptr<float>(j)[0]);
            sum_[1] += (y*this->light_intensities.ptr<float>(j)[1]);
            sum_[2] += (y*this->light_intensities.ptr<float>(j)[2]);
        }
        texture.ptr<float>(i)[0] = face_texture.ptr<float>(i)[0] * (sum_[0] / this->lights.size[0]);
        texture.ptr<float>(i)[1] = face_texture.ptr<float>(i)[1] * (sum_[1] / this->lights.size[0]);
        texture.ptr<float>(i)[2] = face_texture.ptr<float>(i)[2] * (sum_[2] / this->lights.size[0]);
    }
    return texture;
}

Mat FaceModel::get_landmarks_106_2d(const Mat& v2d, const Mat& face_shape, const Mat& angle, const Mat& trans)
{
    Mat temp_angle = angle.clone();
    temp_angle.ptr<float>(0)[2] = 0;
    Mat rotation_without_roll = this->compute_rotation(temp_angle);
    Mat temp = this->transform(face_shape, rotation_without_roll, trans);
    Mat v2d_without_roll = this->to_image(this->to_camera(temp));

    Mat visible_parallel = Mat(this->v_parallel.size(), 1, CV_32SC1, this->v_parallel.data());
    vector<int> ldm106_dynamic(this->ldm106, this->ldm106+106);
    for(int i=0;i<16;i++)
    {
        Mat temp = v2d_without_roll.col(0).clone();
        temp.setTo(1e5, visible_parallel!=i);
        ldm106_dynamic[i] = std::min_element(temp.begin<float>(), temp.end<float>()) - temp.begin<float>();
    }

    for(int i=17;i<33;i++)
    {
        Mat temp = v2d_without_roll.col(0).clone();
        temp.setTo(-1e5, visible_parallel!=i);
        ldm106_dynamic[i] = std::max_element(temp.begin<float>(), temp.end<float>()) - temp.begin<float>();
    }
    Mat out = Mat(106, 2, CV_32FC1);
    for(int i=0;i<106;i++)
    {
        out.ptr<float>(i)[0] = v2d.ptr<float>(ldm106_dynamic[i])[0];
        out.ptr<float>(i)[1] = v2d.ptr<float>(ldm106_dynamic[i])[1];
    }
    return out;
}

vector<Mat> FaceModel::segmentation(const Mat& v3d)
{
    // const int sz[3] = {224,224,8};
    // Mat seg = Mat::zeros(3, sz, CV_32FC1);
    vector<Mat> seg(8);
    for(int i=0;i<8;i++)
    {
        std::tuple<Mat, Mat, Mat, vector<int>> render_outs = this->renderer->forward(v3d, this->annotation_tri[i].data(), this->annotation_tri_shapes[i]);
        seg[i] = std::get<0>(render_outs);
    }
    return seg;
}

vector<Mat> FaceModel::segmentation_visible(const Mat& v3d, const vector<int>& visible_idx)
{
    vector<Mat> seg(8);
    for(int i=0;i<8;i++)
    {
        Mat temp = Mat::zeros(v3d.size[0], v3d.size[1], CV_32FC1);
        for(int j=0;j<this->annotation[i].size();j++)
        {
            temp.row(this->annotation[i][j]) = 1;
        }
        for(int j=0;j<visible_idx.size();j++)
        {
            if(visible_idx[j] == 0)
            {
                temp.row(j) = 0;
            }
        }
        std::tuple<Mat, Mat, Mat, vector<int>> render_outs = this->renderer->forward(v3d, this->tri.data(), this->tri_shape, temp);
        Mat temp_image = std::get<2>(render_outs);
        Mat temp_mask = Mat(temp_image.size[0], temp_image.size[1], CV_32FC1);
        for(int h=0;h<temp_image.size[0];h++)
        {
            for(int w=0;w<temp_image.size[1];w++)
            {
                float sum = 0.f;
                for(int c=0;c<temp_image.size[2];c++)
                {
                    sum += temp_image.ptr<float>(h,w)[c];  
                }
                temp_mask.ptr<float>(h)[w] = sum / (float)temp_image.size[2];
            }
        }
        Mat mask = Mat::zeros(temp_mask.size[0], temp_mask.size[1], CV_32FC1);
        mask.setTo(1, temp_mask >= 0.5);
        mask.copyTo(seg[i]);
    }
    return seg;
}

DataDict FaceModel::forward(const Mat& im)
{
    Mat input_img;
    im.convertTo(input_img, CV_32FC3, 1/255.f);
    Mat blob = blobFromImage(input_img);
	this->net_recon.setInput(blob);
	std::vector<Mat> outs;
	this->net_recon.forward(outs, this->outnames);
    Mat alpha = outs[0];

    split_alpha
    std::map<string, Mat> alpha_dict = {{"id", alpha.colRange(0, 80)}, {"exp", alpha.colRange(80, 144)}, 
                                        {"alb", alpha.colRange(144, 224)}, {"angle", alpha.colRange(224, 227)},
                                        {"sh", alpha.colRange(227, 254)}, {"trans", alpha.colRange(254, alpha.size[1])}};
    compute_shape
    Mat face_shape = this->compute_shape(alpha_dict["id"], alpha_dict["exp"]);
    Mat rotation = this->compute_rotation(alpha_dict["angle"]);
    Mat v3d = this->transform(face_shape, rotation, alpha_dict["trans"]);

    v3d = this->to_camera(v3d);

    Mat v2d =this->to_image(v3d);

    Mat face_albedo = this->compute_albedo(alpha_dict["alb"]);
    Mat face_norm = this->compute_norm(face_shape);
    Mat face_norm_roted = face_norm * rotation;
    Mat face_texture = this->compute_texture(face_albedo, face_norm_roted, alpha_dict["sh"]);

    face_texture.setTo(0, face_texture < 0);
    face_texture.setTo(1, face_texture > 1);

    std::tuple<Mat, Mat, Mat, vector<int>> render_outs = this->renderer->forward(v3d, this->tri.data(), this->tri_shape, face_texture, true);
    Mat pred_image = std::get<2>(render_outs);

    vector<int> visible_idx_renderer = std::get<3>(render_outs);

    Mat gray_shading = this->compute_gray_shading_with_directionlight(Mat::ones(face_albedo.rows, face_albedo.cols, CV_32FC1)*0.78, face_norm_roted);
    std::tuple<Mat, Mat, Mat, vector<int>> render_outs2 = this->renderer->forward(v3d, this->tri.data(), this->tri_shape, gray_shading);
    Mat mask = std::get<0>(render_outs2);
    Mat pred_image_shape = std::get<2>(render_outs2);

    DataDict result_dict;   ///也可以使用结构体
    result_dict = {{"v3d", v3d}, {"v2d", v2d}, 
                    {"face_texture", face_texture}, {"tri", this->tri}, 
                    {"uv_coords", this->uv_coords}, {"render_shape", convert_hwc2bgr(pred_image_shape)}, 
                    {"render_face", convert_hwc2bgr(pred_image)}, {"render_mask", mask}};

    vector<int> visible_idx(35709, 0);
    if(this->args["seg_visible"] || this->args["extractTex"])
    {
        for(int i=0;i<visible_idx_renderer.size();i++)
        {
            visible_idx[visible_idx_renderer[i]] = 1;
        }
        for(int i=0;i<face_norm_roted.size[0];i++)
        {
            if(face_norm_roted.ptr<float>(i)[2] < 0)
            {
                visible_idx[i] = 0;
            }
        }
    }

    if(this->args["ldm68"])
    {
        /get_landmarks_68
        Mat v2d_68 = Mat(68, 2, CV_32FC1);
        for(int i=0;i<68;i++)
        {
            v2d_68.ptr<float>(i)[0] = v2d.ptr<float>(this->ldm68[i])[0];
            v2d_68.ptr<float>(i)[1] = v2d.ptr<float>(this->ldm68[i])[1];
        }
        result_dict["ldm68"] = v2d_68;
    }

    if(this->args["ldm106"])
    {
        /get_landmarks_106
        Mat v2d_106 = Mat(106, 2, CV_32FC1);
        for(int i=0;i<106;i++)
        {
            v2d_106.ptr<float>(i)[0] = v2d.ptr<float>(this->ldm106[i])[0];
            v2d_106.ptr<float>(i)[1] = v2d.ptr<float>(this->ldm106[i])[1];
        }
        result_dict["ldm106"] = v2d_106;
    }

    if(this->args["ldm106_2d"])
    {
        Mat v2d_106_2d = this->get_landmarks_106_2d(v2d, face_shape, alpha_dict["angle"], alpha_dict["trans"]);
        result_dict["ldm106_2d"] = v2d_106_2d;
    }

    if(this->args["ldm134"])
    {
        /get_landmarks_134
        Mat v2d_134 = Mat(134, 2, CV_32FC1);
        for(int i=0;i<134;i++)
        {
            v2d_134.ptr<float>(i)[0] = v2d.ptr<float>(this->ldm134[i])[0];
            v2d_134.ptr<float>(i)[1] = v2d.ptr<float>(this->ldm134[i])[1];
        }
        result_dict["ldm134"] = v2d_134;
    }

    if(this->args["seg"])
    {
        vector<Mat> seg = this->segmentation(v3d);
        result_dict["seg"] = seg;
    }

    if(this->args["seg_visible"])
    {
        vector<Mat> seg_visible = this->segmentation_visible(v3d, visible_idx);
        result_dict["seg_visible"] = seg_visible;
    }

    if(this->args["extractTex"])
    {
        std::tuple<Mat, Mat, Mat, vector<int>> uv_renderer_outs = this->uv_renderer->forward(this->uv_coords_torch, this->tri.data(), this->tri_shape, face_texture);
        Mat uv_color_pca = std::get<2>(uv_renderer_outs);
        Mat img_colors = bilinear_interpolate(blob, v2d.col(0), v2d.col(1));
        const vector<int> newshape = {1, img_colors.size[0], img_colors.size[1]};
        std::tuple<Mat, Mat, Mat, vector<int>> uv_renderer_outs2 = this->uv_renderer->forward(this->uv_coords_torch, this->tri.data(), this->tri_shape, img_colors.reshape(0, newshape));
        Mat uv_color_img = std::get<2>(uv_renderer_outs2);
        Mat visible_idx_mat = cv::repeat(Mat(visible_idx.size(), 1, CV_32SC1, visible_idx.data()), 1, 3);
        visible_idx_mat.convertTo(visible_idx_mat, CV_32FC1);
        std::tuple<Mat, Mat, Mat, vector<int>> uv_renderer_outs3 = this->uv_renderer->forward(this->uv_coords_torch, this->tri.data(), this->tri_shape, 1-visible_idx_mat);
        Mat uv_weight = std::get<2>(uv_renderer_outs3);
        
        Mat median_filtered_w;
        cv::medianBlur(converthwc2bgr(uv_weight), median_filtered_w, 31);
        median_filtered_w.convertTo(median_filtered_w, CV_32FC3, 1/255.f);

        Mat res_colors = median_filtered_color_pca(median_filtered_w, uv_color_img, uv_color_pca);
        Mat v_colors = get_colors_from_uv(res_colors, this->uv_coords_numpy.col(0), this->uv_coords_numpy.col(1));
        result_dict["extractTex"] = v_colors;
    }
    cout<<"forward done"<<endl;
    return result_dict;
}

main.cpp

#include "det_face_landmarks.h"
#include "face_reconstruction.h"
#include <opencv2/opencv.hpp>

#pragma comment(linker, "/STACK:10000000")

using namespace std;
using namespace cv;


int main()
{
	std::map<std::string, bool> args = {{"iscrop", true}, 
		{"ldm68", true}, {"ldm106", true},
		{"ldm106_2d", true}, {"ldm134", true}, 
		{"seg", true}, {"seg_visible", true}, 
		{"useTex", true}, {"extractTex", true}};

	RetinaFace facebox_detector;
	FaceModel recon_model(args);
	Visualizer my_visualize;
    
	Mat srcimg = imread("data/3.png");

    float trans_params[5];
	Mat im = facebox_detector.detect(srcimg, trans_params);
	DataDict results = recon_model.forward(im);


	cv::Mat cv_dst;
	vector<Mat> visualize_list = my_visualize.visualize_and_output(results, args, trans_params, srcimg,cv_dst);
	最重要的3个结果图保存起来

	cv::namedWindow("dst", 0);
	cv::namedWindow("render_face", 0);
	cv::namedWindow("seg_face", 0);
	cv::imshow("dst", cv_dst);
	cv::imshow("render_face", visualize_list[2]);
	cv::imshow("seg_face", visualize_list[7]);

	cv::waitKey();
	return 0;
}

在这里插入图片描述
在这里插入图片描述

5.2 python推理代码

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import numpy as np
from time import time

def isPointInTri(point, tri_points):

    tp = tri_points

    # vectors
    v0 = tp[2,:] - tp[0,:]
    v1 = tp[1,:] - tp[0,:]
    v2 = point - tp[0,:]

    # dot products
    dot00 = np.dot(v0.T, v0)
    dot01 = np.dot(v0.T, v1)
    dot02 = np.dot(v0.T, v2)
    dot11 = np.dot(v1.T, v1)
    dot12 = np.dot(v1.T, v2)

    # barycentric coordinates
    if dot00*dot11 - dot01*dot01 == 0:
        inverDeno = 0
    else:
        inverDeno = 1/(dot00*dot11 - dot01*dot01)

    u = (dot11*dot02 - dot01*dot12)*inverDeno
    v = (dot00*dot12 - dot01*dot02)*inverDeno

    # check if point in triangle
    return (u >= 0) & (v >= 0) & (u + v < 1)

def get_point_weight(point, tri_points):

    tp = tri_points
    # vectors
    v0 = tp[2,:] - tp[0,:]
    v1 = tp[1,:] - tp[0,:]
    v2 = point - tp[0,:]

    # dot products
    dot00 = np.dot(v0.T, v0)
    dot01 = np.dot(v0.T, v1)
    dot02 = np.dot(v0.T, v2)
    dot11 = np.dot(v1.T, v1)
    dot12 = np.dot(v1.T, v2)

    # barycentric coordinates
    if dot00*dot11 - dot01*dot01 == 0:
        inverDeno = 0
    else:
        inverDeno = 1/(dot00*dot11 - dot01*dot01)

    u = (dot11*dot02 - dot01*dot12)*inverDeno
    v = (dot00*dot12 - dot01*dot02)*inverDeno

    w0 = 1 - u - v
    w1 = v
    w2 = u

    return w0, w1, w2

def rasterize_triangles(vertices, triangles, h, w):

    # initial 
    depth_buffer = np.zeros([h, w]) - 999999. #+ np.min(vertices[2,:]) - 999999. # set the initial z to the farest position
    triangle_buffer = np.zeros([h, w], dtype = np.int32) - 1  # if tri id = -1, the pixel has no triangle correspondance
    barycentric_weight = np.zeros([h, w, 3], dtype = np.float32)  # 
    
    for i in range(triangles.shape[0]):
        tri = triangles[i, :] # 3 vertex indices

        # the inner bounding box
        umin = max(int(np.ceil(np.min(vertices[tri, 0]))), 0)
        umax = min(int(np.floor(np.max(vertices[tri, 0]))), w-1)

        vmin = max(int(np.ceil(np.min(vertices[tri, 1]))), 0)
        vmax = min(int(np.floor(np.max(vertices[tri, 1]))), h-1)

        if umax<umin or vmax<vmin:
            continue

        for u in range(umin, umax+1):
            for v in range(vmin, vmax+1):
                if not isPointInTri([u,v], vertices[tri, :2]): 
                    continue
                w0, w1, w2 = get_point_weight([u, v], vertices[tri, :2]) # barycentric weight
                point_depth = w0*vertices[tri[0], 2] + w1*vertices[tri[1], 2] + w2*vertices[tri[2], 2]
                if point_depth > depth_buffer[v, u]:
                    depth_buffer[v, u] = point_depth
                    triangle_buffer[v, u] = i
                    barycentric_weight[v, u, :] = np.array([w0, w1, w2])

    return depth_buffer, triangle_buffer, barycentric_weight


def render_colors_ras(vertices, triangles, colors, h, w, c = 3):

    assert vertices.shape[0] == colors.shape[0]

    depth_buffer, triangle_buffer, barycentric_weight = rasterize_triangles(vertices, triangles, h, w)

    triangle_buffer_flat = np.reshape(triangle_buffer, [-1]) # [h*w]
    barycentric_weight_flat = np.reshape(barycentric_weight, [-1, c]) #[h*w, c]
    weight = barycentric_weight_flat[:, :, np.newaxis] # [h*w, 3(ver in tri), 1]

    colors_flat = colors[triangles[triangle_buffer_flat, :], :] # [h*w(tri id in pixel), 3(ver in tri), c(color in ver)]
    colors_flat = weight*colors_flat # [h*w, 3, 3]
    colors_flat = np.sum(colors_flat, 1) #[h*w, 3]. add tri.

    image = np.reshape(colors_flat, [h, w, c])
    # mask = (triangle_buffer[:,:] > -1).astype(np.float32)
    # image = image*mask[:,:,np.newaxis]
    return image


def render_colors(vertices, triangles, colors, h, w, c = 3):

    assert vertices.shape[0] == colors.shape[0]
    
    # initial 
    image = np.zeros((h, w, c))
    depth_buffer = np.zeros([h, w]) - 999999.

    for i in range(triangles.shape[0]):
        tri = triangles[i, :] # 3 vertex indices

        # the inner bounding box
        umin = max(int(np.ceil(np.min(vertices[tri, 0]))), 0)
        umax = min(int(np.floor(np.max(vertices[tri, 0]))), w-1)

        vmin = max(int(np.ceil(np.min(vertices[tri, 1]))), 0)
        vmax = min(int(np.floor(np.max(vertices[tri, 1]))), h-1)

        if umax<umin or vmax<vmin:
            continue

        for u in range(umin, umax+1):
            for v in range(vmin, vmax+1):
                if not isPointInTri([u,v], vertices[tri, :2]): 
                    continue
                w0, w1, w2 = get_point_weight([u, v], vertices[tri, :2])
                point_depth = w0*vertices[tri[0], 2] + w1*vertices[tri[1], 2] + w2*vertices[tri[2], 2]

                if point_depth > depth_buffer[v, u]:
                    depth_buffer[v, u] = point_depth
                    image[v, u, :] = w0*colors[tri[0], :] + w1*colors[tri[1], :] + w2*colors[tri[2], :]
    return image


def render_texture(vertices, triangles, texture, tex_coords, tex_triangles, h, w, c = 3, mapping_type = 'nearest'):

    assert triangles.shape[0] == tex_triangles.shape[0]
    tex_h, tex_w, _ = texture.shape

    # initial 
    image = np.zeros((h, w, c))
    depth_buffer = np.zeros([h, w]) - 999999.

    for i in range(triangles.shape[0]):
        tri = triangles[i, :] # 3 vertex indices
        tex_tri = tex_triangles[i, :] # 3 tex indice

        # the inner bounding box
        umin = max(int(np.ceil(np.min(vertices[tri, 0]))), 0)
        umax = min(int(np.floor(np.max(vertices[tri, 0]))), w-1)

        vmin = max(int(np.ceil(np.min(vertices[tri, 1]))), 0)
        vmax = min(int(np.floor(np.max(vertices[tri, 1]))), h-1)

        if umax<umin or vmax<vmin:
            continue

        for u in range(umin, umax+1):
            for v in range(vmin, vmax+1):
                if not isPointInTri([u,v], vertices[tri, :2]): 
                    continue
                w0, w1, w2 = get_point_weight([u, v], vertices[tri, :2])
                point_depth = w0*vertices[tri[0], 2] + w1*vertices[tri[1], 2] + w2*vertices[tri[2], 2]
                if point_depth > depth_buffer[v, u]:
                    # update depth
                    depth_buffer[v, u] = point_depth    
                    
                    # tex coord
                    tex_xy = w0*tex_coords[tex_tri[0], :] + w1*tex_coords[tex_tri[1], :] + w2*tex_coords[tex_tri[2], :]
                    tex_xy[0] = max(min(tex_xy[0], float(tex_w - 1)), 0.0); 
                    tex_xy[1] = max(min(tex_xy[1], float(tex_h - 1)), 0.0); 

                    # nearest
                    if mapping_type == 'nearest':
                        tex_xy = np.round(tex_xy).astype(np.int32)
                        tex_value = texture[tex_xy[1], tex_xy[0], :] 

                    # bilinear
                    elif mapping_type == 'bilinear':
                        # next 4 pixels
                        ul = texture[int(np.floor(tex_xy[1])), int(np.floor(tex_xy[0])), :]
                        ur = texture[int(np.floor(tex_xy[1])), int(np.ceil(tex_xy[0])), :]
                        dl = texture[int(np.ceil(tex_xy[1])), int(np.floor(tex_xy[0])), :]
                        dr = texture[int(np.ceil(tex_xy[1])), int(np.ceil(tex_xy[0])), :]

                        yd = tex_xy[1] - np.floor(tex_xy[1])
                        xd = tex_xy[0] - np.floor(tex_xy[0])
                        tex_value = ul*(1-xd)*(1-yd) + ur*xd*(1-yd) + dl*(1-xd)*yd + dr*xd*yd

                    image[v, u, :] = tex_value
    return image


def MeshRenderer_cpu_core(vertices, triangles, colors, h, w, c = 3):

    assert vertices.shape[0] == colors.shape[0]
    
    # initial 
    image = np.zeros((h, w, c))
    depth_buffer = np.zeros([h, w]) - 999999.
    triangle_buffer = np.zeros([h, w], dtype = np.int32) - 1  # if tri id = -1, the pixel has no triangle correspondance
    barycentric_weight = np.zeros([h, w, 3], dtype = np.float32)  # 

    for i in range(triangles.shape[0]):
        tri = triangles[i, :] # 3 vertex indices

        # the inner bounding box
        umin = max(int(np.ceil(np.min(vertices[tri, 0]))), 0)
        umax = min(int(np.floor(np.max(vertices[tri, 0]))), w-1)

        vmin = max(int(np.ceil(np.min(vertices[tri, 1]))), 0)
        vmax = min(int(np.floor(np.max(vertices[tri, 1]))), h-1)

        if umax<umin or vmax<vmin:
            continue

        for u in range(umin, umax+1):
            for v in range(vmin, vmax+1):
                if not isPointInTri([u,v], vertices[tri, :2]): 
                    continue
                w0, w1, w2 = get_point_weight([u, v], vertices[tri, :2])
                point_depth = w0*vertices[tri[0], 2] + w1*vertices[tri[1], 2] + w2*vertices[tri[2], 2]

                if point_depth > depth_buffer[v, u]:
                    depth_buffer[v, u] = point_depth
                    image[v, u, :] = w0*colors[tri[0], :] + w1*colors[tri[1], :] + w2*colors[tri[2], :]
                    triangle_buffer[v, u] = i
                    barycentric_weight[v, u, :] = np.array([w0, w1, w2])
    return image, depth_buffer, triangle_buffer, barycentric_weight

推理结果:
在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2230965.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu系统如何实现键盘按键映射到其他按键(以 Ctrl+c 映射到 F3,Ctrl+v 映射到 F4 为例)

文章目录 写在前面1. 功能描述2. 实现步骤2.1 安装AutoKey2.2 软件设置2.2.1 软件设置 2.3 测试是否安装成功 参考链接 写在前面 自己的测试环境&#xff1a; Ubuntu20.04 1. 功能描述 Ubuntu系统使用Ctrlc 、Ctrlv 进行复制粘贴操作的时候&#xff0c;时间长了就会出现小拇指…

【Clickhouse】客户端连接工具配置

ClickHouse 是什么 ClickHouse 是一个分布式实时分析型列式存储数据库。具备高性能&#xff0c;支撑PB级数据&#xff0c;提供实时分析&#xff0c;稳定可扩展等特性。适用于数据仓库、BI报表、监控系统、互联网用户行为分析、广告投放业务以及工业、物联网等分析和时序应用场…

postman的脚本设置接口关联

pm常用的对象 变量基础知识 postman获取响应结果的脚本的编写 下面是购物场景存在接口信息的关联 登录进入---搜索商品---进入商品详情---加入购物车 资源在附件中&#xff0c;可以私聊单独发送 postman的SHA256加密 var CryptoJS require(crypto-js);// 需要加密的字符串 …

Qt 文件目录操作

Qt 文件目录操作 QDir 类提供访问系统目录结构 QDir 类提供对目录结构及其内容的访问。QDir 用于操作路径名、访问有关路径和文件的信息以及操作底层文件系统。它还可以用于访问 Qt 的资源系统。 Qt 使用“/”作为通用目录分隔符&#xff0c;与“/”在 URL 中用作路径分隔符…

qt QCheckBox详解

QCheckBox 是 Qt 框架中的一个控件&#xff0c;用于创建复选框&#xff0c;允许用户进行选择和取消选择。它通常用于表单、设置界面和任何需要用户选择的场景。 QCheckBox继承自QAbstractButton类&#xff0c;因此继承了按钮的特性。它表示一个复选框&#xff0c;用户可以通过…

读数据工程之道:设计和构建健壮的数据系统26数据建模

1. 数据建模 1.1. 良好的数据架构必须反映出使用这些数据的组织的业务目标和业务逻辑 1.2. 数据湖1.0、NoSQL和大数据系统的兴起&#xff0c;使工程师们有时是为了合理的性能提升去忽略传统的数据建模 1.3. 数据在企业中的地位急剧上升&#xff0c;人们越来越认识到&#xf…

2025生物发酵展(济南)为生物制造产业注入新活力共谱行业新篇章

2025第十四届国际生物发酵展将于3月3-5日济南盛大举办&#xff01;产业链逐步完整&#xff0c;展会面积再创历史新高&#xff0c;展览面积较上届增涨至60000平方米&#xff0c;专业观众40000&#xff0c;品牌展商800&#xff0c;同期活动会议增加至50场&#xff0c;展会同期将举…

Windows版 nginx安装,启动,目录解析,常用命令

Windows版 nginx安装&#xff0c;启动&#xff0c;目录解析&#xff0c;常用命令 一级目录二级目录三级目录 1. 下载2. 启动方式一&#xff1a;方式二&#xff1a; 3. 验证是否启动4. 安装目录解析5. 常用命令 一级目录 二级目录 三级目录 1. 下载 官网下载&#xff1a;ngi…

【maven】idea执行了maven的install命令给本地安装项目依赖包 安装后删除

目录 事件起因环境和工具操作过程解决办法1、找到对应的目录下的文件&#xff0c;手动去删除&#xff0c;比如我的依赖库的路径是D:\qc_code\apache-maven-3.8.2\repository 我只需要找到这个目录下对应的依赖包进行手动删除即可&#xff08;不推荐&#xff0c;强行删除文件夹文…

技术选型不当对项目的影响与补救措施

在项目管理中&#xff0c;初期技术选型与项目需求不匹配的情况并不罕见&#xff0c;这可能导致项目延误、成本增加和最终成果的不理想。补救的关键措施包括&#xff1a;重新评估技术选型、加强团队沟通、实施有效的需求管理以及建立持续的反馈机制。其中&#xff0c;重新评估技…

基于SSM+VUE宠物医院后台管理系统JAVA|VUE|Springboot计算机毕业设计源代码+数据库+LW文档+开题报告+答辩稿+部署教+代码讲解

源代码数据库LW文档&#xff08;1万字以上&#xff09;开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统&#xff1a;Window操作系统 2、开发工具&#xff1a;IntelliJ IDEA或者Eclipse 3、数据库存储&#xff1a…

MySQL 8.0在windows环境安装及配置

文章目录 一、下载二、安装三、配置环境变量 一、下载 1、先彻底卸载之前的MySQL&#xff0c;并清理其 残留文件 。 2、登录网址https://www.mysql.com/ 3、点击网址左下角“中文”按钮&#xff0c;切换到中文界面 4、点击网页上方的“下载”按钮&#xff0c;然后点击网页…

【传知代码】用于图像识别的判别图正则化技术

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;传知代码 欢迎大家点赞收藏评论&#x1f60a; 目录 论文概述图正则化技术及其优点参考文献&#xff1a; 算法流程在标准BLS中嵌入判别图正则化的方法 模型整体架构代码复现图拉普拉斯矩阵的构建—…

第二十五章 Vue父子通信之sync修饰符

目录 一、概述 二、完整代码 2.1. main.js 2.2. App.vue 2.3. BaseDialog.vue 三、运行效果 一、概述 前面的章节我们讲到&#xff0c;通过v-model我们可以实现父子组件间的通信&#xff0c;但是使用v-model的时候&#xff0c;子组件接收的prop属性名必须固定为valu…

头歌——机器学习(逻辑回归)

文章目录 逻辑回归简述代码 sklearn逻辑回归 - 手写数字识别代码 逻辑回归算法详解似然与概率的区别逻辑回归算法的代码实现代码 逻辑回归案例 - 癌细胞精准识别代码 逻辑回归简述 什么是逻辑回归 当一看到“回归”这两个字&#xff0c;可能会认为逻辑回归是一种解决回归问题的…

【高等数学】3-2多元函数积分学

1. 二重积分 可以想象你有一块不规则的平面薄板,它在一个平面区域上。二重积分就是用来求这个薄板的质量(假设薄板的面密度函数是)。 把区域划分成许多非常小的小方块(类似于把一块地划分成很多小格子),在每个小方块上,密度近似看成是一个常数,然后把每个小方块的质量加…

需求管理流程与工具:国内外10款综合评测

本文中&#xff0c;分享了10款需求管理工具&#xff1a;1.PingCode&#xff1b;2.Worktile&#xff1b;3.纷享销客&#xff1b;4.Teambition&#xff1b;5.Jira&#xff1b;6.Trello&#xff1b;7.Figma&#xff1b;8.万维需求管理&#xff1b;9.ClickUp&#xff1b;10.项目管理…

Java项目实战II基于Spring Boot的个人云盘管理系统设计与实现(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 基于Spring Boot的个人云盘管理系统设计…

嵌入式之C语言(基础篇)

首先&#xff0c;我们要知道什么是程序。程序&#xff1a;为了让计算机执行某操作或解决某个问题而编写的一系列有序指令的集合。 一、计算机语言简史 第一代是机器语言&#xff1a;时间实在1946年&#xff0c;第一台计算机ENIAC诞生&#xff0c;用的是穿孔卡片做的&#xff0c…

搜索引擎算法更新对网站优化的影响与应对策略

内容概要 随着互联网的不断发展&#xff0c;搜索引擎算法也在不断地进行更新和优化。了解这些算法更新的背景与意义&#xff0c;对于网站管理者和优化人员而言&#xff0c;具有重要的指导意义。不仅因为算法更新可能影响到网站的排名&#xff0c;还因为这些变化也可能为网站带…