径向基函数RBF三维网格变形

前言

之前写过径向基函数(RBF)神经网络做分类或者拟合。然后挖了个坑说在《Phase-Functioned Neural Networks for Character Control》里面提到了用于做地形编辑,所以这篇博客就是解析一下如何用RBF做网格编辑系统。

参考博客:

Noe’s tutorial on deforming 3D geometry using RBFs

基于参考博客的人脸网格编辑code

有很多网格变形算法的python包PyGem

《Real-Time Shape Editing using Radial Basis Functions》

简介

为了更好理解什么是网格变形,直接看第二个参考博客的效果:

在这里插入图片描述

图中实现的效果就是蓝色的点是黄色人脸模型的几个关键点,绿色的点是另一个没被显示的人脸模型的对应人脸3D关键点,使用RBF变形算法,基于蓝色到绿色的变换关系,将人脸变形到绿色关键点上。至于这个功能的用途请自行探索,后续有时间的话,搞不好我也会基于这个技术做个好玩的东东出来。

理论

其实理论基本就是参考博客的内容,不过那个博客里面对径向基函数的描述不是特别清晰。特别注意的是从PyGem工具包提供的RBF形变函数来看,RBF网格编辑会有个半径参数,可以控制形变影响区域,其实就是在计算径向基的时候,加了个额外的系数。

RBF插值

径向基函数插值的作用是能够在一些2D/3D离散点之间进行平滑插值。假设在3维空间中有M个离散点xix_ixi,RBF就能提供整个离散空间中的平滑插值函数。函数就是M个径向基函数g(ri)g(r_i)g(ri)的结果之和,其中rir_iri是估算点和原始点的距离:
F(x)=∑i=1Maig(∣∣x–xi∣∣)+c0+c1x+c2y+c3z,x=(x,y,z)⋯(1)F(\mathbf{x}) = \sum_{i=1}^M a_i g(||\mathbf{x} – \mathbf{x}_i||) + c_0 + c_1 x + c_2 y + c_3 z, \mathbf{x} = (x,y,z) \cdots \mathbf{(1)} F(x)=i=1Maig(xxi)+c0+c1x+c2y+c3z,x=(x,y,z)(1)
其中aia_iai是常量系数,后面四项c0c_0c0c3c_3c3是一次多项式系数,这些项的就是无法单独使用径向基函数完成的一个仿射变换。

根据已有的M个离散值,可以构建M个函数F(xi,yi,zi)=FiF(x_i,y_i,z_i)=F_iF(xi,yi,zi)=Fi,然后基于此,构建M+4个线性方程组GA=FGA=FGA=F,其中F=(F1,F2,…,FM,0,0,0,0)F=(F_1,F_2,\ldots,F_M,0,0,0,0)F=(F1,F2,,FM,0,0,0,0),A=(a1,a2,…,aM,c0,c1,c2,c3)A=(a_1,a_2,\ldots,a_M,c0,c1,c2,c3)A=(a1,a2,,aM,c0,c1,c2,c3),以及G是一个(M+4)×(M+4)(M+4)\times(M+4)(M+4)×(M+4)的矩阵
G=[g11g12∙∙∙g1M1x1y1z1g21g22∙∙∙g2M1x2y2z2∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙gM1gM2∙∙∙gMM1xMyMzM11∙∙∙10000x1x2∙∙∙xM0000y1y2∙∙∙yM0000z1z2∙∙∙zM0000]\mathbf{G} = \left[\begin{array}{cccccccccc}g_{11} & g_{12} & \bullet & \bullet & \bullet & g_{1M} & 1 & x_1 & y_1 & z_1 \\ g_{21} & g_{22} & \bullet & \bullet & \bullet & g_{2M} & 1 & x_2 & y_2 & z_2 \\ \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet \\ \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet \\ \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet & \bullet \\ g_{M1} & g_{M2} & \bullet & \bullet & \bullet & g_{MM} & 1 & x_M & y_M & z_M \\ 1 & 1 & \bullet & \bullet & \bullet & 1 & 0 & 0 & 0 & 0 \\ x_1 & x_2 & \bullet & \bullet & \bullet & x_M & 0 & 0 & 0 & 0 \\ y_1 & y_2 & \bullet & \bullet & \bullet & y_M & 0 & 0 & 0 & 0 \\ z_1 & z_2 & \bullet & \bullet & \bullet & z_M & 0 & 0 & 0 & 0\end{array}\right] G=g11g21gM11x1y1z1g12g22gM21x2y2z2g1Mg2MgMM1xMyMzM1110000x1x2xM0000y1y2yM0000z1z2zM0000
其中gij=g(∣∣xi−xj∣∣)g_{ij}=g(||x_i-x_j||)gij=g(xixj),g有很多不同的选择,同时也会导致不同的解。在参考博客中,使用了shift log function
g(t)=log⁡(t2+k2),k2≥1g(t)=\sqrt{\log{(t^2+k^2)}},k^2\geq1 g(t)=log(t2+k2),k21
可以直接设置k=1k=1k=1,然后根据式(1)方程组,得到系数向量A。

其实上面的ggg就是传说中的径向基函数了,根据scipy的文档发现常用的径向基函数有:

'multiquadric': sqrt((r/self.epsilon)**2 + 1)
'inverse': 1.0/sqrt((r/self.epsilon)**2 + 1)
'gaussian': exp(-(r/self.epsilon)**2)
'linear': r
'cubic': r**3
'quintic': r**5
'thin_plate': r**2 * log(r)

这些函数在PyGem里面有独立的实现,只不过PyGem里面加入了半径系数r控制插值影响区域,比如对gaussian的实现:

def gaussian_spline(X, r=1):result = np.exp(-(X * X) / (r * r))return result

详细可自行查看PyGem源码

偏移插值(Interpolating displacements)

上一节的插值是给了一系列的离散点,求解这些位于这些离散点间的值。而偏移插值的意思又是什么捏?假设M个3D坐标点对应的形变信息都知道了,也就是有另一个3D偏移向量uiu_iui,将对应索引的xix_ixi进行了偏移,此时就可以将xix_ixi视为控制点,这些点被移动到了xi+uix_i+u_ixi+uiRBF插值就是能够计算其余剩下的3D点对应的偏移量。

xi=(xi,yi,zi)\mathbf{x}_i = (x_i, y_i, z_i)xi=(xi,yi,zi)ui=(uix,uiy,uiz)\mathbf{u}_i = (u^x_i, u^y_i, u^z_i)ui=(uix,uiy,uiz),那么同样可以列出线性方程组:

GAx=(u1x,u2x,…,uMx,0,0,0,0)T\mathbf{G} \mathbf{A}_x = (u^x_1, u^x_2, \ldots, u^x_M, 0, 0, 0, 0)^TGAx=(u1x,u2x,,uMx,0,0,0,0)T

GAy=(u1y,u2y,…,uMy,0,0,0,0)T\mathbf{G} \mathbf{A}_y = (u^y_1, u^y_2, \ldots, u^y_M, 0, 0, 0, 0)^TGAy=(u1y,u2y,,uMy,0,0,0,0)T

GAz=(u1z,u2z,…,uMz,0,0,0,0)T\mathbf{G} \mathbf{A}_z = (u^z_1, u^z_2, \ldots, u^z_M, 0, 0, 0, 0)^TGAz=(u1z,u2z,,uMz,0,0,0,0)T

其中G\mathbf{G}GA\mathbf{A}A与上一节描述的差不多,只不过求解目标从FFF变成了偏移量uuu

求解完毕以后,需要对任意点x进行偏移量的计算,这一点文章没讲,但是从源码中分析发现就是FFF的计算公式:
F(x)=∑i=1Maig(∣∣x–xi∣∣)+c0+c1x+c2y+c3z,x=(x,y,z)⋯(1)F(\mathbf{x}) = \sum_{i=1}^M a_i g(||\mathbf{x} – \mathbf{x}_i||) + c_0 + c_1 x + c_2 y + c_3 z, \mathbf{x} = (x,y,z) \cdots \mathbf{(1)} F(x)=i=1Maig(xxi)+c0+c1x+c2y+c3z,x=(x,y,z)(1)
这也难怪,毕竟偏移插值也是RBF理论,所以在即使求解目标不同,但是形式相同的情况下,插值函数还是一样的。

核心代码解析

C++

求解阶段

第二个参考博客的代码就是第一个参考博客的c++实现,其中有两个核心与上述理论对应,代码基于RBF插值理论实现了开头的人脸变形;

所以细节一:计算控制点的偏移量

// move control points
for (unsigned int i = 0; i<controlPointPosX.size(); i++ )
{controlPointDisplacementX[i] = (destinationPointPosX[i]-controlPointPosX[i]);controlPointDisplacementY[i] = (destinationPointPosY[i]-controlPointPosY[i]);controlPointDisplacementZ[i] = (destinationPointPosZ[i]-controlPointPosZ[i]);
}

源码里面还有个系数(-cosf(morphtime)/2+0.5)不用管它,单纯为了显示变换过程设置的时间。

细节二计算形变系数,也就是公式1的代码如下:

RBFInterpolator(vector<real> x, vector<real> y, vector<real> z, vector<real> f)
{successfullyInitialized = false; // default value for if we end init prematurely.M = f.size();// all four input vectors must have the same length.if ( x.size() != M || y.size() != M || z.size() != M )return;	ColumnVector F = ColumnVector(M + 4);P = Matrix(M, 3);Matrix G(M + 4,M + 4);// copy function valuesfor (unsigned int i = 1; i <= M; i++)F(i) = f[i-1];F(M+1) = 0;  F(M+2) = 0;  F(M+3) = 0;  F(M+4) = 0;// fill xyz coordinates into P for (unsigned int i = 1; i <= M; i++){P(i,1) = x[i-1];P(i,2) = y[i-1];P(i,3) = z[i-1];}// the matrix below is symmetric, so I could save some calculations Hmmm. must be a todofor (unsigned int i = 1; i <= M; i++)for (unsigned int j = 1; j <= M; j++){real dx = x[i-1] - x[j-1];real dy = y[i-1] - y[j-1];real dz = z[i-1] - z[j-1];real distance_squared = dx*dx + dy*dy + dz*dz;G(i,j) = g(distance_squared);}//Set last 4 columns of Gfor (unsigned int i = 1; i <= M; i++){G( i, M+1 ) = 1;G( i, M+2 ) = x[i-1];G( i, M+3 ) = y[i-1];G( i, M+4 ) = z[i-1];}for (unsigned int i = M+1; i <= M+4; i++)for (unsigned int j = M+1; j <= M+4; j++)G( i, j ) = 0;//Set last 4 rows of Gfor (unsigned int j = 1; j <= M; j++){G( M+1, j ) = 1;G( M+2, j ) = x[j-1];G( M+3, j ) = y[j-1];G( M+4, j ) = z[j-1];}Try { Ginv = G.i(); A = Ginv*F;successfullyInitialized = true;}CatchAll { cout << BaseException::what() << endl; }
}

这里调用的时候,x,y,zx,y,zx,y,z就是控制点的原始坐标,fff就是偏移量,比如对三个坐标分别建立插值函数:

RBFX = RBFInterpolator(controlPointPosX, controlPointPosY, controlPointPosZ, controlPointDisplacementX );
RBFY = RBFInterpolator(controlPointPosX, controlPointPosY, controlPointPosZ, controlPointDisplacementY );
RBFZ = RBFInterpolator(controlPointPosX, controlPointPosY, controlPointPosZ, controlPointDisplacementZ );

整个过程就是先利用距离和径向基函数构建(M+4)×(M+4)(M+4)\times(M+4)(M+4)×(M+4)维度的GGG

for (unsigned int i = 1; i <= M; i++)for (unsigned int j = 1; j <= M; j++){real dx = x[i-1] - x[j-1];real dy = y[i-1] - y[j-1];real dz = z[i-1] - z[j-1];real distance_squared = dx*dx + dy*dy + dz*dz;G(i,j) = g(distance_squared);}

然后构建(M+4)(M+4)(M+4)维度的FFF,再去求解GA=FGA=FGA=F

推断阶段

当任意的顶点过来以后,需要调用如下代码:

interpolate(real x, real y, real z)
{if (!successfullyInitialized)return 0.0f;real sum = 0.0f;// RBF partfor (unsigned int i = 1; i <= M; i++){real dx = x - P(i,1);real dy = y - P(i,2);real dz = z - P(i,3);real distance_squared = dx*dx + dy*dy + dz*dz;sum += A(i) * g(distance_squared);}	//affine partsum += A(M+1) + A(M+2)*x + A(M+3)*y + A(M+4)*z;return sum;
}

大致意思就是,需要将被估算顶点与M个已知形变顶点求基于距离的径向基函数的加和,然后使用系数乘一下,就得到了新的坐标,调用方法如下

void deformObject(TriangleMesh* res, TriangleMesh* initialObject, RBFInterpolator rbfX, RBFInterpolator rbfY, RBFInterpolator rbfZ)
{for (unsigned int i = 0; i < res->getParticles().size(); i++){Vector3 oldpos = initialObject->getParticles()[i].getPos();Vector3 newpos;newpos[0] = oldpos[0] + rbfX.interpolate(oldpos[0], oldpos[1], oldpos[2]);newpos[1] = oldpos[1] + rbfY.interpolate(oldpos[0], oldpos[1], oldpos[2]);newpos[2] = oldpos[2] + rbfZ.interpolate(oldpos[0], oldpos[1], oldpos[2]);res->getParticles()[i].setPos(newpos);}
}

python

求解阶段

PyGem代码中有实现,由于python对矩阵的操作更加简洁,所以看起来很清晰

def _get_weights(self, X, Y):npts, dim = X.shapeH = np.zeros((npts + 3 + 1, npts + 3 + 1))H[:npts, :npts] = self.basis(cdist(X, X), self.radius)#, **self.extra)H[npts, :npts] = 1.0H[:npts, npts] = 1.0H[:npts, -3:] = XH[-3:, :npts] = X.Trhs = np.zeros((npts + 3 + 1, dim))rhs[:npts, :] = Yweights = np.linalg.solve(H, rhs)return weights

其中X,Y就是对应控制点形变前后的坐标位置,可以发现这里并没有按照常理出牌,正常情况应该就是对形变前后的差值做求解,不过事实证明,并不影响最终结果。

其中self.basis对应的是几种径向基函数的一个:

__bases = {'gaussian_spline': RBFFactory.gaussian_spline,'multi_quadratic_biharmonic_spline': RBFFactory.multi_quadratic_biharmonic_spline,'inv_multi_quadratic_biharmonic_spline': RBFFactory.inv_multi_quadratic_biharmonic_spline,'thin_plate_spline': RBFFactory.thin_plate_spline,'beckert_wendland_c2_basis': RBFFactory.beckert_wendland_c2_basis,'polyharmonic_spline': RBFFactory.polyharmonic_spline
}

求解方式也是没有用G−1FG^{-1}FG1F,而是直接用np.linalg.solve求解了。

推断阶段

def __call__(self, src_pts):self.compute_weights()H = np.zeros((src_pts.shape[0], self.n_control_points + 3 + 1))H[:, :self.n_control_points] = self.basis(cdist(src_pts, self.original_control_points), self.radius)#**self.extra)H[:, self.n_control_points] = 1.0H[:, -3:] = src_ptsreturn np.asarray(np.dot(H, self.weights))

与求解阶段一样,先计算待预测坐标点与控制点的距离,然后输入到径向基函数中,最后利用系数乘一下,就得到了待预测坐标点的形变后位置。

实验

简单网格形变

蓝色的标记为规规整整的原始网格,黑色圆点为选取的形变点,红色三角为目标形变点,最终整个网格的形变结果就是红色三角形了。

在这里插入图片描述

人头形变

c++两个人头拿过来,将控制点进行着色后,使用meshlab可视化看看

在这里插入图片描述

python中将PyGem的代码抠出来,测试一下半径影响:

当形变半径影响为0.5时候

在这里插入图片描述

当形变半径影响为1.0时候,就是标准的RBF插值了

在这里插入图片描述

将0.5和1.0的放一起对比一下:

在这里插入图片描述
可以发现半径的设置的确对变形结果有一定影响。半径较小的时候,鼻子都被捏塌了,半径大点就好点,实际人脸变形的过程中,我们是不会使用这么简单的几个点的,肯定会加入更多的关键点。

后记

好像理论非常的简单,就是一个方程组的求解,只不过方程组的建立与径向基函数有关系。这玩意好像可以用来做捏脸什么的,以后有机会试试。

python版本的代码可以去PyGem上找到,或者本博客的代码在我的github上能找到,关注微信公众号查看公众号简介有,或者CSDN左侧栏有github网址。

在这里插入图片描述

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

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

相关文章

OBJ可视化——UV还原(修正)

前言 前面写过一篇obj格式解析的博客&#xff0c;但是这篇文章中可视化的工作是参考PRNet的源码进行的&#xff0c;后来细细思考了一下&#xff0c;有点问题&#xff0c;具体看下面。 问题来源 在PRNet源码的render.py中有个函数render_texture&#xff0c;是作者用于将uv展…

Unity中BVH骨骼动画驱动的可视化理论与实现

前言 找了很久使用BVH到unity中驱动骨骼动画的代码&#xff0c;但是都不是特别好用&#xff0c;自己以前写过&#xff0c;原理很简单&#xff0c;这里记录一下。 理论 初始姿态 在BVH或者其它骨骼动画中&#xff0c;一般涉及到三种姿势&#xff1a;A-pose&#xff0c;T-pos…

卡通驱动项目ThreeDPoseTracker——模型驱动解析

前言 之前解析过ThreeDPoseTracker这个项目中的深度学习模型&#xff0c;公众号有兄弟私信一些问题&#xff0c;我刚好对这个项目实现有兴趣&#xff0c;就分析一波源码&#xff0c;顺便把问题解答一下。 这个源码其实包括很多内容&#xff1a;3D姿态估计&#xff0c;坐标平滑…

卡通驱动项目ThreeDPoseTracker——关键点平滑方案解析

前言 之前对ThreeDPoseTracker的深度学习模型和unity中的驱动方法进行过解析&#xff0c;还有一个比较重要的就是从深度学习模型出来的3D关键点数据会有抖动&#xff0c;在ThreeDPoseTracker源码中有做两次平滑&#xff0c;一部分是卡尔曼滤波&#xff0c;还有一部分是低通滤波…

卡通角色表情驱动系列一

前言 分析完ThreeDPoseTracker来做卡通角色的身体驱动&#xff0c;接下来在卡通驱动领域还有一个是表情驱动。对这个真的是一窍不通啊&#xff0c;只能慢慢看论文了。 国际惯例&#xff0c;参考博客/论文&#xff1a; 《Landmark-guided deformation transfer of template f…

opencv相机标定和人头姿态估计案例

前言 头部驱动除了之前关注的表情驱动外&#xff0c;还有眼球驱动和头部方向驱动。本博客基于opencv官方文档和部分开源代码来研究如何基于人脸关键点获取头部的朝向。 国际惯例&#xff0c;参考博客&#xff1a; opencv:Camera Calibration and 3D Reconstruction opencv:…

卡通角色表情驱动系列二

前言 之前介绍了使用传统算法求解BS系数的表情驱动方法&#xff0c;其中提到过的三种方法之一是基于网格形变迁移做的&#xff0c;那么这篇文章就是对《Deformation Transfer for Triangle Meshes》做表情驱动的解析。 国际惯例&#xff0c;参考博客&#xff1a; 论文原文《…

UE自带重定向原理

UE自带重定向方法验证 核心源码在VS的解决方案中的位置&#xff1a; UE4\Source\Developer\AssetTools\Private\AssetTypeActions\AnimSequence.cpp中第3237行RemapTracksToNewSkeleton函数 跳转方法 AssetTypeActions_AnimationAsset.cpp的RetargetNonSkeletonAnimationHa…

【caffe-Windows】caffe+VS2013+Windows无GPU快速配置教程

前言 首先来一波地址&#xff1a; happynear大神的第三方caffe&#xff1a;http://blog.csdn.net/happynear/article/details/45372231 Neil Z大神的第三方caffe&#xff1a;https://initialneil.wordpress.com/2015/01/11/build-caffe-in-windows-with-visual-studio-2013-…

【caffe-Windows】caffe+VS2013+Windows+GPU配置+cifar使用

前言 国际惯例&#xff0c;先来波地址&#xff1a; CUDA WIN7&#xff1a;链接&#xff1a;http://pan.baidu.com/s/1nvyA3Qp 密码&#xff1a;h0f3 官方网址&#xff1a;https://developer.nvidia.com/cuda-toolkit CUDA WIN10:链接&#xff1a;http://pan.baidu.com/s/1…

【一些网站的收集】包含机器学习深度学习大牛主页等

数学概念部分 旋转矩阵、欧拉角、四元数的比较 欧拉角和四元数的表示 四元数与旋转 B样条曲线 非常好的概率统计学习的主页 误差方差偏差 编程语言学习 C#编程视频 OpenGL编程NeHe OpenGL官网 OpenGL“我叫MT“纯手工3D动画制作之1——基础介绍 【强大】非常好的Op…

Eureka源码分析

Eureka源码分析 Eureka server入口: Spring.factories PS: 意味着如果加载EurekaServerAutoConfiguration成功,需要 ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)需加载成功. 通过Bean注入了很多类 本质上, eureka-server包含很多事件: EurekaInstanceC…

matlab程序中,如何解决矢量长度必须相同的问题

主要原因就是画图的x和y长度不一样&#xff0c;我用一个例子说明。 问题代码&#xff1a; clear all;close all;clc;x 0 : 1: 9;y sin(x);n 2*length(x);yi interpft(y, n);xi 0 : 0.5 : 10;hold on ;plot(x, y ,ro);plot(xi, yi, b.-);plot(x, sin(x),m--);legend(原始…

matlab 功率谱分析函数psd用法

psd简介 PSD(power spectrum analysis)功率谱分析&#xff0c;PSD在给定频带上的积分计算信号在该频带上的平均功率。与均值-平方谱相反&#xff0c;这个光谱中的峰值并没有反映出给定频率的能量。 单边PSD包含了信号的总功率在频率间隔从DC到一半的奈奎斯特速率。双侧PSD包含…

linux tar (打包、压缩、解压)命令

打包程序&#xff1a;tar c: 创建文档t&#xff1a; 列出存档内容x&#xff1a;提取存档f&#xff1a; filename 要操作的文档名v&#xff1a;详细信息 一&#xff1a;打包 打包&#xff1a;是指把文件整合在一起&#xff0c;不压缩 1.将文件打包&#xff1a;tar cf a.tar…

虚拟机添加硬盘扩容

1.设置→添加→硬盘 2.选择磁盘类型 3.开启虚拟机 4.用ls 命令查看&#xff1a;ls /dev/sd* 5.最后就可以对sdb进行分区操作 这里好麻烦&#xff0c;等我有空&#xff0c;在补上&#xff01; . . .

利用matlab将三维数据画成三维立体图

首先先分析对象。将数据利用matlab画出图&#xff0c;最开始是导入数据&#xff0c;然后处理数据&#xff0c;最后将处理的数据画出来。 所以我将它分为三个步骤。 第一步&#xff1a;导入数据 如果是mat数据。可以直接load如果是txt数据。可以用txtread如果是excel数据。可…

世界坐标、相机坐标、图像坐标、像素坐标的原理、关系,并用matlab仿真

世界坐标、相机坐标、图像坐标、像素坐标的原理、关系&#xff0c;并用matlab仿真 照相机是日常生活中最常见的。它能把三维的空间图片等比例缩小投影在照片上&#xff0c;称为一个二维图像。 以下我们就讲一讲原理&#xff0c;并相应的进行matlab仿真。 在学之前&#xff0…

matlab 三维高程根据图片颜色给对应点赋予颜色

目录 1. 问题分析 2. 技术分析 3. 程序代码 4. 代码运行结果 1. 问题分析 日常工作尤其是测绘、地质、遥感行业&#xff0c;需要画DEM模型&#xff0c;并在这个模型的基础上&#xff0c;进行着色、渲染。比如&#xff0c;地质分析地面三维地表形变之时&#xff0c;需要根据D…

matlab 计算N天前(后)的日期

注意时间的格式&#xff1a;是字符串、数字还是日期&#xff1f; 下面是计算明天、今天、昨天的日期。 day1 datetime(datestr(now,yyyy-mm-dd))caldays(1)%tomorrowday0 datetime(datestr(now,yyyy-mm-dd))%todayday_1 datetime(datestr(now,yyyy-mm-dd))-caldays(1)%yest…