四元素理解

旋转变换_四元数

2017年03月29日 11:59:38 csxiaoshui 阅读数:5686

1.简介

四元数是另一种描述三维旋转的方式,四元数使用4个分量来描述旋转,四元数的描述方式如下:

 

q=s+xi+yj+zk,(s,x,y,z∈ℝ)i2=j2=k2=ijk=−1

 

四元数的由来和复数很很大的关系,因此首先讨论一下关于复数的内容。

1.1 复数

复数可以定义如下:

z=a+bia,b∈ℝi2=−1


复数常用的基本运算如下:

 

Complex
复数中一个比较重要的概念是共轭复数,将复数的虚部取相反数,得到它的共轭复数:

z=a+biz∗=a−bi


复数的模,定义为:
complex2

 

复数还可以使用复平面来表示,复平面分为实轴和虚轴(类似于二维直角坐标系中的x轴和y轴),如下图所示:

Complex3
当我们使用i去乘以一个复数时,当我们把得到的结果绘制在复平面上时,发现得到的位置正好是绕原点旋转90度的效果。
complex4
于是可以猜测,复数的乘法和旋转之间应该有某些关系。
我们可以通过定义一个复数

q=cosθ+isinθ


使用它作为一个旋转的因子,当与复数相乘时,得到:
complex5
写成矩阵的形式是:

[a′b′]=[cosθsinθ−sinθcosθ]∗[ab]


这个公式正好是二维的旋转公式,当把新的到的(a′+b′i)绘制在复平面上时,得到的正好是原来的点(a+bi)旋转θ角之后的位置。

 

2. 四元数

既然使用复数的乘法可以描述二维的旋转,那么拓展一个维度是否能表示三维旋转呢,这个也正是四元数发明者William Hamilton最初的想法,也就是说使用

z=a+ib+jci2=j2=−1


但是很遗憾 “三维的复数”(这仅仅是我按概念杜撰的一个词,并不存在)的乘法并不是闭合的。也就是说有可能两个值相乘得到的结果并不是三维的复数。
William Hamilton经历了无数个日日夜夜,他绞尽脑汁也没想明白这个问题。终于有一天(1843年的一天),他意识到自己所需要的运算在三维空间中是不可能实现的,但在四维空间中是可以的,他是如此的兴奋,以至于把四元数的公式刻在了爱尔兰的一座桥上。

 

quaternions

四元数可以写成下面的方式:

q=[s,v]s∈Rv∈ℝ3


或者写成

q=[s,xi+yj+zk]s,x,y,z∈ℝ

 

2.1 四元数的运算

  • 两四元数相加:
    A(a+bi+cj+dk) + B(e + fi + gj + hk) = C【 (a+e) + (b+f)i + (c+g)j + (d+h)k 】,实现代码:
      // Quat的成员_v[4]代表四元数的(x,y,z,w)inline Quat operator + (const Quat& rhs) const{return Quat(_v[0] + rhs._v[0],_v[1] + rhs._v[1],_v[2] + rhs._v[2],_v[3] + rhs._v[3]);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 两个四元数相减
    (sa,va) - (sb,vb) = (sa-sb,va-vb)
        inline Quat operator - (const Quat& rhs) const{return Quat(_v[0] - rhs._v[0],_v[1] - rhs._v[1],_v[2] - rhs._v[2],_v[3] - rhs._v[3]);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 两个四元数相乘
两个四元数相乘的规则和多项式乘法一样,
(a + i b + j c + k d)*(e + i f + j g + k h)
当有i,j,k参与时,规则如下:
i*i = j*j = k*k = -1
i*j = k,
j*i = -k
j*k = i,
k*j = -i
k*i = j,
i*k = -j
使用多项式乘法展开,可以得到:
a*e - b*f - c*g - d*h
+ i (b*e + a*f + c*h- d*g)
+ j (a*g - b*h+ c*e + d*f)
+ k (a*h + b*g - c*f + d*e)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

实现代码:

        inline const Quat operator*(const Quat& rhs) const{return Quat( rhs._v[3]*_v[0] + rhs._v[0]*_v[3] + rhs._v[1]*_v[2] - rhs._v[2]*_v[1],rhs._v[3]*_v[1] - rhs._v[0]*_v[2] + rhs._v[1]*_v[3] + rhs._v[2]*_v[0],rhs._v[3]*_v[2] + rhs._v[0]*_v[1] - rhs._v[1]*_v[0] + rhs._v[2]*_v[3],rhs._v[3]*_v[3] - rhs._v[0]*_v[0] - rhs._v[1]*_v[1] - rhs._v[2]*_v[2] );}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 两四元数相除
    四元数一般来说不定义除法,因为四元数的乘法运算并不满足交换律。一般有四元数的类定义除法是,其实定义的是q1∗q2−1

,其中
q−1=conj(q)/|q2|,为什么定义这么奇怪的表达式呢,其实是为了让q∗q−1=1

  • ,这个结论很容易推导出来。conj(q)称为q的共轭表达式,
    con(q) = w - xi - yj -zk,只需要四元数向量部分取负即可
    实现如下:
        inline const Quat operator/(const Quat& denom) const{return ( (*this) * denom.inverse() );}/// Conjugateinline Quat conj () const{return Quat( -_v[0], -_v[1], -_v[2], _v[3] );}/// Multiplicative inverse method: q^(-1) = q^*/(q.q^*)inline const Quat inverse () const{return conj() / length2();}value_type length2() const{return _v[0]*_v[0] + _v[1]*_v[1] + _v[2]*_v[2] + _v[3]*_v[3];}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.2 四元数的旋转

四元数在三维图形学领域的一个重要应用是用它来描述三维旋转,四元数从某种意义上来说是四维空间的旋转,难以想象,了解它的结论和使用场景更加重要。欧拉定理告诉我们任意三维旋转都可以使用一个旋转向量和旋转角度来描述。因此四元数往往是使用旋转轴和旋转角来构造的,构造它的方法如下:


2.2.1 绕向量u旋转角度θ

构造四元数

可以用下面的四元数来表示:

u⃗ =(ux,uy,uz)=uxi+uyj+uzk

 

q=eθ2(uxi+uyj+uzk)=cosθ2+(uxi+uyj+uzk)sinθ2


实现代码如下:

 

   void makeRotate(value_type angle, value_type x, value_type y, value_type z){const value_type epsilon = 1e-7;value_type length = sqrt(x*x + y*y + z*z);if (length < epsilon){*this = Quat();return;}value_type  inversenorm = 1.0 / length;value_type  coshalfangle = cos(0.5*angle);value_type  sinhalfangle = sin(0.5*angle);_v[0] = x * sinhalfangle * inversenorm;_v[1] = y * sinhalfangle * inversenorm;_v[2] = z * sinhalfangle * inversenorm;_v[3] = coshalfangle;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2.2.2 从一个向量旋转到另一个向量构造四元数

按照最原始的想法,从一个向量旋转到另一个向量,那么旋转轴可以通过两个向量的叉乘得到,旋转角度可以通过两个向量间的夹角得到。(向量间的夹角的余弦可以通过两向量点乘去除以它们的模,再通过反余弦函数计算),得到旋转轴和旋转角度之后就转换成2.2.1中的情形了。
也就是说最初的代码如下:

void makeRotate(Vec3<value_type>& u,  Vec3<value_type>& v){u.normalize();v.normalize();double costheta = u*v;double angle = acos(costheta);Vec3<value_type> w = u^v;w.normalize();makeRotate(angle, w.x(), w.y(), w.z());}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

有一种特殊情况需要考虑:两向量共线(包括方向相同和方向相反,也就是夹角是0度和180度的情形)

void makeRotate(const Vec3<value_type>& from, const Vec3<value_type>& to){const value_type epsilon = 1e-7;value_type length1 = from.length();value_type length2 = to.length();value_type cosangle = from*to / (length1*length2);if (fabs(cosangle - 1) < epsilon){makeRotate(0.0, 0.0, 0.0, 1.0);}else if (fabs(cosangle + 1.0) < epsilon){Vec3<value_type> tmp;if ((fabs(from.x())) < fabs(from.y())){if (fabs(from.x()) < fabs(from.z())){tmp.set(1.0, 0.0, 0.0);}else{tmp.set(0.0, 0.0, 1.0);}}else if (fabs(from.y()) < fabs(from.z())){tmp.set(0.0, 1.0, 0.0);}else{tmp.set(0.0, 0.0, 1.0);}Vec3<value_type> fromd(from.x(), from.y(), from.z());Vec3<value_type> axis(fromd^tmp);axis.normalize();_v[0] = axis[0];_v[1] = axis[1];_v[2] = axis[2];_v[3] = 0.0;}else{Vec3<value_type> axis(from^to);value_type angle = acos(cosangle);makeRotate(angle, axis.x(), axis.y(), axis.z());}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

上述的代码改进了之前代码,但是在计算过程中使用了反三角函数(相对比较耗时),可以通过三角函数公式,简化,不需要调用反三角函数:

sinθ2cosθ2=1−cosθ2‾‾‾‾‾‾‾‾‾√=1+cosθ2‾‾‾‾‾‾‾‾‾√


代码可以修改为:

 

//省略部分相同的代码else{Vec3<value_type> axis(from^to);//替换成下面几行//value_type angle = acos(cosangle);//makeRotate(angle, axis.x(), axis.y(), axis.z());axis.normalize();value_type half_cos = sqrt(0.5*(1+cosangle));value_type half_sin = sqrt(0.5*(1-cosangle));_v[0] = axis[0] * half_sin;_v[1] = axis[1] * half_sin;_v[2] = axis[2] * half_sin;_v[3] = half_cos;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这样修改之后,去掉了算法中复杂的三角函数运算,事实上还可以进一步改进计算过程,考虑到代码中多次的归一化(normalize)的操作,需要进行多次开方运算,为了简化,可以考虑:

||u×v||=|u|.|v|.|sinθ|

 

 

sinθ=2sinθ2cosθ2

 

同时有:

sqrt(a)sqrt(b)=sqrt(ab)

 

于是代码可以修改为:

            else{value_type normFromAndTo = sqrt(from.length2()*to.length2());value_type cos_theta = from * to / normFromAndTo;value_type half_cos = sqrt(0.5 * (1+cos_theta));value_type half_sin = sqrt(0.5 * (1-cos_theta));Vec3<value_type> axis = from^to / (normFromAndTo*2*half_sin * half_cos);_v[0] = axis[0]*half_sin;_v[1] = axis[1]*half_sin;_v[2] = axis[2]*half_sin;_v[3] = half_cos;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

注意到_v[0]到_v[3]中乘以half_sin,之前axis计算的分母中就有half_sin,也就是说这一项可以被化简掉,于是代码简化成:

        else{value_type normFromAndTo = sqrt(from.length2()*to.length2());value_type cos_theta = from * to / normFromAndTo;value_type half_cos = sqrt(0.5 * (1+cos_theta));Vec3<value_type> axis = from^to / (normFromAndTo*2 * half_cos);_v[0] = axis[0];_v[1] = axis[1];_v[2] = axis[2];_v[3] = half_cos;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.2.3 从四元数获取旋转矩阵和旋转角

这个过程是上面的反过程,根据之前描述的公式反算就可以,得到的公式是:

 //设四元数是 xi+yj+zk+w,那么旋转角度和旋转轴(a,b,c)是:
angle = 2 * acos(w)
a = x / sqrt(1-w*w)
b = y / sqrt(1-w*w)
c = z / sqrt(1-w*w)
  • 1
  • 2
  • 3
  • 4
  • 5

推导过程如下:
有之前的公式可以知道: w=cos(θ/2)

可以得到 角度 θ=2∗acos(w)

 

x=a∗sin(θ/2)y=b∗sin(θ/2)z=c∗sin(θ/2)


先分析x这个等式,带入求出的θ角,得到:

a=x/sin(acos(w))

,参考下图:
sin(acosw)
得到 sin(acosw) = sqrt(1-w*w),同理可以推出其他的结论。
但是还需要考虑其他两个特殊情况:也就是共线的情形(角度θ是0度或者180度)

 

  • 0度的情况:
    当时0度的时候,得到w=1,会导致计算公式中分母是0,除以0出现无穷大,因此需要单独讨论

  • 180度的情况
    当180度是 w=0,可以通过计算得到
    a = x, b=y,c=z
    计算过程是正确的,因此这种情况不需要特殊的去分析。
    综合上面整体的描述,代码如下:

        void getRotate(value_type& angle, value_type& x, value_type& y, value_type& z) const {Quat q1 = *this;if (_v[3] > 1)q1.normalize();angle = 2 * acos(q1.w());value_type s = sqrt(1 - q1.w()*q1.w());if (s < 1e-6){x = q1.x();y = q1.y();z = q1.z();}else{x = q1.x() / s;y = q1.y() / s;z = q1.z() / s;}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2.3 向量使用四元数进行旋转

这么辛苦写了四元数的类,为的就是使用它对顶点和向量进行旋转的操作,也就是说我们需要完成下面的函数实现:

        //Rotate a vector by this quaternionVec3<value_type> operator* (const Vec3<value_type>& v);
  • 1
  • 2

四元数变换向量的算法如下:
1. 创建一个以v为虚部的纯虚的向量,(v.x + v.y + v.z + 0)
2. 左乘四元数 q 接着右乘四元数q的共轭四元数 q*
3. 计算得到的结果也是一个纯的四元数,它的虚部就是变换之后的向量v’
尽管这样做可以得到变换后的向量,如果计算过程完全按照四元数乘法法则去展开计算,计算量略大 ,可以使用下面的方式优化一下:

        Vec3<value_type> operator* (const Vec3<value_type>& v){// nVidia SDK implementationVec3<value_type> uv, uuv;Vec3<value_type> qvec(_v[0], _v[1], _v[2]);uv = qvec ^ v;uuv = qvec ^ uv;uv *= ( 2.0f * _v[3] );uuv *= 2.0f;return v + uv + uuv;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.4 四元数的插值

使用四元数来表示旋转,在插值时非常的方便和平滑,如果使用欧拉角来进行插值运算,除了会出现万向节死锁外,插值的效果显得十分的生硬。四元数的球面插值使用下面的公式:

这里写图片描述

其中:
- qm:插值的四元数
- qa: 插值四元数的第一个值(起点)
- qb:插值四元数的第二个值(终点)
- t: (0.0,1.0)之间的一个数
- θ

: qa和qb夹角的一半

实现如下:

void dslerp( value_type t, const Quat& from, const Quat& to )
{const double epsilon = 0.00001;double omega, cosomega, sinomega, scale_from, scale_to ;osg::Quat quatTo(to);// this is a dot productcosomega = from.asVec4() * to.asVec4();if ( cosomega <0.0 ){cosomega = -cosomega;quatTo = -to;}if( (1.0 - cosomega) > epsilon ){omega= acos(cosomega) ;  // 0 <= omega <= Pi (see man acos)sinomega = sin(omega) ;  // this sinomega should always be +ve so// could try sinomega=sqrt(1-cosomega*cosomega) to avoid a sin()?scale_from = sin((1.0-t)*omega)/sinomega ;scale_to = sin(t*omega)/sinomega ;}else{/* --------------------------------------------------The ends of the vectors are very closewe can use simple linear interpolation - no needto worry about the "spherical" interpolation-------------------------------------------------- */scale_from = 1.0 - t ;scale_to = t ;}*this = (from*scale_from) + (quatTo*scale_to);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

参考文献:

  1. Understanding Quaternions
  2. 如何形象地理解四元数?知乎Yang Eninala的解答
  3. Quaternion
  4. Quaternion
  5. Maths - Quaternions
  6. Beautiful maths simplification: quaternion from two vectors
  7. Rotating vector3 by a quaternion
  8. quaternion vector product四元数变换向量算法的原理

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

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

相关文章

31、SAM文件中flag含义解释工具--转载

转载&#xff1a;http://www.cnblogs.com/nkwy2012/p/6362996.html SAM是Sequence Alignment/Map 的缩写。像bwa等软件序列比对结果都会输出这样的文件。samtools网站上有专门的文档介绍SAM文件。具体地址&#xff1a;http://samtools.sourceforge.net/SAM1.pdf很多人困惑SAM文…

《Head First设计模式》批注系列(一)——观察者设计模式

最近在读《Head First设计模式》一书&#xff0c;此系列会引用源书内容&#xff0c;但文章内容会更加直接&#xff0c;以及加入一些自己的理解。 观察者模式&#xff08;有时又被称为模型-视图&#xff08;View&#xff09;模式、源-收听者(Listener)模式或从属者模式&#xff…

PYPL 4 月排行:Python 最流行,Java 还行不行?

开发四年只会写业务代码&#xff0c;分布式高并发都不会还做程序员&#xff1f; PYPL 发布了 4 月份的编程语言排行榜。 前五的分别是&#xff1a;Python、Java、Javascript、C# 和 PHP。可以看到&#xff0c;榜单没有什么大变化&#xff0c;但是相比去年 4 月份&#xff0c;…

两个向量的旋转矩阵与四元素

两向量的夹角 2017年06月20日 17:38:11 csxiaoshui 阅读数&#xff1a;36764 怎么计算两个向量间的夹角呢&#xff1f; 这里主要分两种情况&#xff0c;对于二维向量和三维向量来分别讨论。 1. 二维向量 二维向量的情况相对简单&#xff0c;根据向量间的点乘关系 v1⋅v2|…

顺序表

一、数据是如何在内存中存储的&#xff1f; 32位系统中char&#xff0c;int型数据在内存中的存储方式&#xff1a; char占1byte&#xff08;8bit&#xff09;int占4byte&#xff08;32bit&#xff09;假设我们有一个int类型的值&#xff0c;它从0x01开始&#xff0c;一个int占据…

Establishing SSL connection without server's identity verification is not recommended.

完全描述:Establishing SSL connection without servers identity verification is not recommended. According to MySQL 5.5.45, 5.6.26 and 5.7.6 requirements SSL connection must be established by default if explicit option isnt set. For compliance with existing …

四元素的真面目..........简单粗暴

作者&#xff1a;Yang Eninala 链接&#xff1a;https://www.zhihu.com/question/23005815/answer/33971127 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 根据我的理解&#xff0c;大多数人用汉密尔顿四元数就只…

2.自定义变量调节器

① 使用registerPlugin()方法来扩充变量调节器 该方法接收3个参数 1. 字符串modifier 2. 插件函数的名字 3. PHP回调函数 示例&#xff1a;自定义一个变量调节器&#xff0c;可以改变文字的颜色和大小 第一步&#xff1a;调用smarty对象的registerPlugin&#xff08;&#x…

SpringBoot2构建基于RBAC权限模型的驾校代理小程序后端

本项目是使用SpringBoot2构建的一套基于RBAC权限模型的后台管理系统&#xff0c;前端是微信小程序。 项目地址: github.com/fuyunwang/D… 项目的缘由 最近接了个外包,主要是针对于驾校开发一个代理小程序。目的是为了方便驾校的管理来招揽学员,同时方便维护学员和代理信息。 项…

while read line的问题

循环中的重定向或许你应该在其他脚本中见过下面的这种写法&#xff1a;while read linedo…done < file刚开始看到这种结构时&#xff0c;很难理解< file是如何与循环配合在一起工作的。因为循环内有很多条命令&#xff0c;而我们之前接触的重定向都是为一条命令工作的。…

Linemod;理解

Linemod 代码笔记 2019年03月11日 16:18:30 haithink 阅读数&#xff1a;197 最近了解到 Linemod 这个模板匹配算法&#xff0c;印象不错 准备仔细学习一下&#xff0c;先做点代码笔记&#xff0c;免得后面不好回顾 目前的笔记基本上把 核心流程都分析得比较清楚了&#xff0…

Swift3中数组创建方法

转载自&#xff1a;http://blog.csdn.net/bwf_erg/article/details/70858865 数组是由一组类型相同的元素构成的有序数据集合。数组中的集合元素是有 序的&#xff0c;而且可以重复出现。 1 数组创建 在Swift语言中&#xff0c;数组的类型格式为&#xff1a; Array<ElementT…

BZOJ 5249: [2018多省省队联测]IIIDX(贪心 + 线段树)

题意 这一天&#xff0c;\(\mathrm{Konano}\) 接到了一个任务&#xff0c;他需要给正在制作中的游戏 \(\mathrm{《IIIDX》}\) 安排曲目 的解锁顺序。游戏内共有\(n\) 首曲目&#xff0c;每首曲目都会有一个难度 \(d\) &#xff0c;游戏内第 \(i\) 首曲目会在玩家 Pass 第 \(\lf…

手眼标定

Eye-in-hand和Eye-to-hand问题求解和实验 2018年12月07日 00:00:40 百川木易 阅读数 3018 2018/12/5 By Yang Yang&#xff08;yangyangipp.ac.cn&#xff09; 本文所有源码和仿真场景文件全部公开&#xff0c;点击Gitee仓库链接。 文章目录 问题描述Eye-in-hand问题求解公式…

RNN总结

RNN既可以表述为循环神 经网络&#xff08;recurrent neural network&#xff09;&#xff0c;也可以表述为递归神经网络&#xff08;recursive neural network&#xff09;&#xff0c;前者一般用于处理以时间序列为输入的问题&#xff08;比如把一个句子看成词组成的序列&…

Problem 2. number题解

number&#xff1a;数学二分图匹配 首先&#xff0c;如果S<N,那么S1&#xff0c;S2...N这些数直接放在S1,S2...N的位置上(如果其他数x放在这些位置上面&#xff0c;这些数不放在对应位置&#xff0c;那么x一定能放在这些数放的位置&#xff0c;所以直接交换即可)所以可以直接…

SSD列子

一、介绍 本博文主要介绍实现通过SSD物体检测方式实现工件裂纹检测。裂纹图像如下所示&#xff1a; 二、关于SSD算法 具体算法不再阐述&#xff0c;详细请参考&#xff1a; https://blog.csdn.net/u013989576/article/details/73439202 https://blog.csdn.net/xiaohu2022/arti…

linux硬链接与软链接

Linux 系统中有软链接和硬链接两种特殊的“文件”。 软链接可以看作是Windows中的快捷方式&#xff0c;可以让你快速链接到目标档案或目录。 硬链接则透过文件系统的inode来产生新档名&#xff0c;而不是产生新档案。 创建方法都很简单&#xff1a; 软链接&#xff08;符号链接…

int转时间

int转时间 public static string FormatDuration(int duration) { if (duration 0) return "00:00:00"; int hours duration / 3600; int minutes duration % 3600 / 60; int seconds duration % 3600 % 60; string _hours hours.ToString("00") &qu…

企业级区块链现状研究报告:小企业的投资总额是大企业的28倍

根据企业级区块链现状研究报告表明&#xff0c;当前企业采用区块链技术的势头正在逐步增强。参与该报告的企业表示&#xff0c;区块链投资今年共增长了 62% &#xff0c;预计到 2025 年区块链将成为主流技术。其中&#xff0c;有 28% 的企业正在积极开展区块链发展计划。现在看…