|

「AI哲学」让人类“为自身立命”与“为AI立心”。
通过本章的阅读,你可能已经对视觉计算产生了兴趣,并着手准备基于它来构建你的3D打印杀手级应用了。但应该怎么入门呢?还有更重要的是,如何尽快地动手实践,以便对视觉计算有一个直观的认识。这里给大家推荐OpenCV与OpenGL,分别是计算机视觉和计算机图形学的实战利器,下面一一进行介绍。
5.1.1 OpenCV与AdaBoost人脸检测OpenCV的全称是:Open Source Computer Vision Library,Intel公司支持的开源计算机视觉库,采用C/C++编写,可以运行在Linux/Windows/Mac等操作系统上。其目标是构建一个简单易用的计算机视觉框架,以帮助开发人员便捷地设计复杂的计算机视觉相关应用程序。OpenCV包含的函数有500多个,覆盖了如工厂产品检测、医学成像、信息安全、用户界面、摄像机标定、立体视觉和机器人等领域。
下面我们举个例子,展示如何使用OpenCV自动检测图像中的人脸。正如前面所提到的,我们要实现个性化的3D打印定制,比如要为1万名用户量身定制眼镜,我们就需要利用OpenCV自动地将这1万名用户的人脸分别从照片中检测出来,进而对每个人的眼距、眼眶大小进行自动测量。当然,你还可以让智能算法自动分析每个人的脸型(圆脸、方脸、瓜子脸等等),因为不同的眼镜形状(如方形、圆形眼镜)适于不同的脸型。这样,每个用户的眼镜都不是一模一样的,这些量身定制出来的眼镜可以把每个用户的脸庞都装饰得美观、时尚。可以看出,智能算法是实现低成本3D打印“大规模定制”的前提和基础,因为要为1万名用户手工定制眼镜将是一项昂贵、且几乎是不可能完成的任务(假设工期仅限1周或1个月的话)。
看了上面这段文字,你是不是有些跃跃欲试?好,我们就正式开讲如何检测图像中的人脸。首先,我们介绍如何用OpenCV的寥寥数行代码即可实现读取并显示图像,代码如下。
程序:从文件中读取一幅图像并在屏幕上显示
#include "highgui.h"
int main(int argc,char** argv)
{
if(argc<2)
exit(1);
//读入一张图片
IplImage* image = cvLoadImage(argv[1]);
if (NULL == image)//如果读入失败,退出程序
exit(1);
//创建一个窗口,标题为Example
cvNamedWindow("Example",CV_WINDOW_AUTOSIZE);
//在窗口Example中显示图片image
cvShowImage("Example",image);
//暂停程序,等待用户触发一个按键
cvWaitKey(0);
//释放图像所分配的内存
cvReleaseImage(&image);
//销毁窗口
cvDestroyWindow("Example");
return 0;
}
图像在屏幕上的显示效果如下图所示。
图5-1 使用OpenCV读入一幅图像文件并在屏幕上显示
接着我们看看如何使用OpenCV来进行人脸检测,主要是调用OpenCV中已训练好的Haar 分类器来对输入图像进行模式匹配。
人脸检测算法的流程非常简单,具体介绍如下:首先,构造一个含有成百上千张样本图像的样本库。网上现在有很多这样的图像库可供下载。接着,我们对每一张样本图像做标记,标记为正样本或负样本:正样本就是含有人脸的图像,负样本就是不含人脸的图像(如一张风景照片或一头猪的照片)。比如,我们这个样本库中含有正样本700张,负样本300张。然后,我们对每张图像提取Haar-like特征(请参考章节“5.17.1 个性特征的描述与检测”)。我们知道,Haar-like特征是非常原始、粗粒度的。所以,需要对这些提取到的特征用AdaBoost(Adaptive Boosting,自适应增强、自适应提升)学习算法进行特征选择和分类器训练[23]。具体来说,首先从大量的特征中选出少量的关键特征,构成弱分类器(指一个分类器对一组数据的分类正确率只比随机瞎猜好一点,大于50%即可)。然后,通过类似跨国公司“兼并收购”小公司的方式,把这些弱分类器(按照各自正确率的大小)加权线性组合成强分类器(指一个分类器的正确率很高)。
提示:弱学习和强学习等价定理表明:只要有足够的数据,弱分类器就能通过不断组合的方式增强(Boost)为任意高精度的强分类器,正所谓“三个臭皮匠顶一个诸葛亮”!
在把弱分类器组合成强分类器的过程中,AdaBoost方法的自适应(Adaptive)在于:前一个分类器分错的样本会被用来训练下一个分类器。AdaBoost方法就是这样一种反复迭代的算法,在每一轮中加入一个新的弱分类器,直至达到某个预定的足够小的错误率。每一个训练样本都被赋予一个权重,表明它被某个分类器选入训练集的概率(即可能性)。如果某个样本已经被准确地分类,那么在构造下一个训练集中,它被选中的概率就被降低;相反,如果某个样本没有被准确地分类,那么它的权重就得到提高。通过这样的方式,AdaBoost方法能“集中聚焦”学习那些难区分(更富信息)的样本,最终得到分类效果理想的强分类器。
扩展:AdaBoost为每个分类器都分配了一个权重值,这些值是基于每个弱分类器的错误率进行计算的,其中错误率的定义为:
为每个弱分类器计算出后,就可以对样本权重向量进行更新,以使得那些正确分类的样本的权重降低而错误分类的样本的权重升高。样本权重向量的计算方法如下:
如果某个样本被正确分类,则该样本的权重被调整为:
反之,如果某个样本被错误分类,则该样本的权重被调整为:
在现实的人脸检测中,只靠一个强分类器还是难以保证检测的正确率,所以我们通常训练有多个强分类器。这些强分类器虽然性能优越,但因为构造比较复杂,所以计算起来比较费时。如果同时计算它们的话,速度肯定不快。怎么办?可通过设置层层关卡的方式,一关一关地过,直到通关!也即,我们可通过筛选式级联(Cascaded)的方式来大大地提高速度。具体来说,级联结构的分类器是由一系列强分类器串联组成。对要识别的样本进行判别时,只有被前面一级的分类器判别为正的样本(“是人脸”)才被送入后面的分类器继续处理,反之则被认为是负样本(“非人脸”)而直接踢出局。最后,只有被所有分类器都判别为正的样本才会被输出。在设计级联结构时,前面几级的分类器都选用结构比较简单(计算快速)的,使用的特征数也较少,但检测速率很高,可以一下子就滤除那些与目标差异较大的负样本;后面级次的分类器则使用更多的特征和更复杂的结构(计算费时),这样可以细致地滤除那些与目标相似的负样本。这样做的一个重要好处是:可以将大量的不含人脸的子窗口图像“阻挡”在级联分类器的最前面几层,即只需简单计算就被直接滤除,而不进入后面的阶段,因而大大地减少了计算量。
如下图所示,举个最简单的决策树(Decision Tree)例子,实际级联结构就类似于这么一种退化的决策树。假设我们使用3个Haar-like特征f1、f2、f3来判断输入数据是否为人脸,可以建立如下决策树进行判别。可以看出,这种判别方式非常简单、快速,只需做if-then条件规则判断即可。
图5-2 筛选式级联的工作原理
提示:与前面章节“5.19 【3D智能十八篇之八】众里寻她千百度:海量3D模型的形状检索”中介绍的感知机和SVM相比,决策树(如分类回归树Classification And Regression Trees,CART)以及随机森林(Random Forest)这种对复杂数据不断进行子划分的方式很适合于非线性问题,因为当数据拥有众多特征且特征之间关系十分复杂时,要对所有的非线性数据构建一个统一的非线性划分函数将变得非常困难。但在决策树的生成过程中,往往需要对树进行剪枝简化,以避免构建出过于复杂和有噪声的决策树,导致出现过拟合现象(Over-fitting,即对已知的训练数据的分类过于准确,连细枝末节上的“坏分子”噪声都好坏不分地拟合进去了,导致对未知的测试数据的分类反而不准确了、降低了对未知数据的预测能力,即泛化能力Generalization Ability),正所谓“过犹不及”。
下面我们展示如何用OpenCV来编写人脸检测代码。注意:OpenCV已训练好了Haar分类器,所以我们无须进行上面的分类器训练过程,只需应用级联分类器进行判别即可。
程序:从文件中读取一幅图像并检测出其中的人脸
#include "cv.h"
#include "highgui.h"
//基于Haar特征的人脸检测要用到的级联分类器文件
string face_cascade_name = "haarcascade_frontalface_alt.xml";
//级联分类器类
CascadeClassifier face_cascade;
string window_name = "result";
void detectAndDisplay( Mat frame );
int main( int argc, char** argv ){
Mat image;
image = imread( argv[1]);
if( argc != 2 || !image.data ){
printf("[error] 没有图片\n");
return -1;
}
//加载人脸检测所用的分类器,并判断是否载入成功,如果不成功则提示
if( !face_cascade.load( face_cascade_name ) ){
printf("[error] 无法加载级联分类器文件!\n");
return -1;
}
detectAndDisplay(image);
waitKey(0);
}
void detectAndDisplay( Mat frame ){
//函数使用针对某目标物体训练的级联分类器,在图像中找到包含目标物体的矩形区域,并且将这些区域作为一序列的矩形框返回,最终检测结果保存在Rect变量中
std::vector<Rect> faces;
Mat frame_gray;
//由于大部分的脸部检测算法对光照、脸部大小、位置表情等敏感,所以一般需利用cvtColor函数将其转化为灰度图像,并利用equalizeHist函数进行直方图(Histogram)均衡化来增强图像的对比度。在本例中,将变量frame转换成灰度图,并输出到变量frame_gray
cvtColor( frame, frame_gray, CV_BGR2GRAY );
equalizeHist( frame_gray, frame_gray );
//对图片frame进行识别检测
face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) );
//人脸检测结果用红圈框出
for( int i = 0; i < faces.size(); i++ ){
Point center( faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5 );
ellipse( frame, center, Size( faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar( 255, 0, 255 ), 4, 8, 0 );
}
imshow( window_name, frame );
}
以上代码的人脸检测效果如下图所示,用一个红色圆框把人脸定位出来了。请仔细数一下,刨去那么多的注释代码,真正的代码是不是也只寥寥数行而已,但却可实现这么智能化的效果。
图5-3 OpenCV的人脸检测定位效果(图片为笔者本人)
附:OpenCV的主要功能
图像数据操作(内存分配与释放,图像复制、设定和转换)
图像/视频的输入/输出(支持文件或摄像头的输入,图像/视频文件的输出)
矩阵/向量数据操作及线性代数运算(矩阵乘积、矩阵方程求解、特征值、奇异值分解)
支持多种动态数据结构(链表、队列、数据集、树、图)
基本图像处理(去噪、边缘检测、角点检测、采样与插值、色彩变换、形态学处理、直方图、图像金字塔结构)
结构分析(连通域/分支、轮廓处理、距离转换、图像矩、模板匹配、霍夫变换、多项式逼近、曲线拟合、椭圆拟合、狄劳尼三角化)
摄像头定标(寻找和跟踪定标模式、参数定标、基本矩阵估计、单应矩阵估计、立体视觉匹配)
运动分析(光流、动作分割、目标跟踪)
目标识别(特征方法、HMM模型)
基本的GUI(显示图像/视频、键盘/鼠标操作、滑动条)
图像标注(直线、曲线、多边形、文本标注)
5.1.2 OpenGL与3D图形绘制上节介绍了计算机视觉领域的开源库OpenCV,本节接着介绍一下计算机图形学领域的开放库OpenGL。OpenGL的英文全称是“Open Graphics Library”,顾名思义,OpenGL是“开放的图形程序库”。虽然DirectX在家用市场全面领先,但在专业高端绘图领域,OpenGL是不能被取代的主角。OpenGL定义了一个跨平台的3D图形编程接口,具有很好的移植性,广泛应用于CAD设计、游戏开发、制造业及虚拟现实等行业领域中。
下面,我们介绍如何使用OpenGL显示一个3D球体,代码如下所示。
程序6-3:使用OpenGL显示一个3D球体
#include <GL/glut.h> //包含OpenGL实用库
#include <stdlib.h>
#include <math.h>
//声明一个二次曲面对象,如球体
GLUquadricObj *quadObj;
//设置光照参数,如环境光、漫射光以及光源位置
static float light_ambient[] = {0.1,0.1,0.1,1.0};
static float light_diffuse[] = {0.5,1.0,1.0,1.0};
static float light_position[] = {90.0,90.0,150.0,0.0};
void myInit(void)
{
glClearColor(0.0,1.0,0.0,0.0); //设置背景色为绿色
glEnable(GL_DEPTH_TEST); //启用深度测试
//设置光照
glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
glLightfv(GL_LIGHT0,GL_POSITION,light_position);
//激活光照
glEnable(GL_LIGHTING); //打开光照
glEnable(GL_LIGHT0); //打开光源0
glShadeModel(GL_SMOOTH); //启用阴影平滑
}
void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
//创建一个二次曲面物体,如球体
quadObj = gluNewQuadric();
//将这个球体绘制出来,半径为3,精细程度为20
glPushMatrix();
gluSphere(quadObj,3.0,20.0,20.0);
glPopMatrix();
//将二次曲面对象删除
gluDeleteQuadric(quadObj);
glFlush(); //强制刷新缓冲
}
void myReshape(int w,int h)
{
glViewport(0,0,(GLsizei)w,(GLsizei)h);//设置视区尺寸
glMatrixMode(GL_PROJECTION); //选择投影矩阵
glLoadIdentity(); //重置投影矩阵
gluPerspective(45.0,(GLfloat)w/(GLfloat)h,1.0,50.0); //指定透视投影的观察空间
提示:透视投影类似于人眼观察真实世界的视觉效果,即离视点近的物体大,离视点远的物体小,远到极点即为消失,成为灭点。比如我们看长长的铁轨(如下图左边所示),原本平行的左右两边似乎要在无穷远的地方相交。而如下图右边所示,透视投影可用一个视锥体模型来描述,OpenGL中函数gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)中的各参数意义分别为:fovy(视角)、aspect(宽/高比)、zNear(Z轴近平面距离)、zFar(Z轴远平面距离)。注意:只有当3D模型处于由Z轴近平面和Z轴远平面围成的视野范围内时,才会被OpenGL显示在二维屏幕上。
图5-4 透视投影的示意图(图片来源:xizanglvxing)
glMatrixMode(GL_MODELVIEW); //选择模型观察矩阵
glLoadIdentity(); //重置模型观察矩阵
glTranslatef(0.0,0.0,-15.0); //将图形沿z轴负向移动(移入屏幕15个单位),以便出现在可观察空间内(具体可参见图6-69)
}
//主函数
int main(int argc,char ** argv)
{
//初始化
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB|GLUT_DEPTH);
glutInitWindowSize(400,400);
//创建窗口
glutCreateWindow("绘制一个3D球体");
//绘制与显示
myInit();
glutReshapeFunc(myReshape);
glutDisplayFunc(myDisplay);
glutMainLoop();
return 0;
}
以上代码编译运行后的效果如下图所示。上面的代码实际上也没有几行,却实现了诸如二次曲面球体生成、光照、视角放置等诸多功能,是不是特别方便?
图5-5 OpenGL生成和显示的球体
附:OpenGL的主要功能
建模:OpenGL图形库除了提供基本的点、线、多边形的绘制函数外,还提供了复杂的三维物体(球、锥、多面体、茶壶等)以及复杂曲线和曲面绘制函数。
变换:OpenGL图形库的变换包括基本变换和投影变换。基本变换有平移、旋转、缩放、镜像4种变换,投影变换有平行投影(又称正射投影)和透视投影两种变换。其变换方法有利于减少算法的运行时间,提高三维图形的显示速度。
颜色模式设置:OpenGL颜色模式有两种,即RGBA模式和颜色索引(Color Index)。程序员还可以选择平面着色和光滑着色两种着色方式对整个三维景观进行着色。
光照和材质设置:用OpenGL绘制的三维模型必须加上光照才能与客观物体更加相似。OpenGL提供了管理4 种光(辐射光、环境光、镜面光和漫反射光)的方法,另外,还可以指定模型表面的反射特性。
纹理映射(Texture Mapping):OpenGL提供的一系列纹理映射函数使得开发者可以十分方便地把真实图像贴到景物的多边形上,从而在视窗内绘制逼真的三维景观。
位图显示和图像增强功能:除了基本的复制和像素读写外,还提供融合(Blending)、抗锯齿(也被称为:反走样,Antialiasing)和雾(Fog)的特殊图像效果处理。以上3条可使被仿真物更具真实感,增强图形显示的效果。
双缓存动画(Double Buffering)。为了获得平滑的动画效果,需要先在内存中生成下一幅图像,然后把已经生成的图像从内存复制到屏幕上,这就是OpenGL 的双缓存技术(Double Buffer)。
此外,利用OpenGL还能实现深度暗示(Depth Cue)、运动模糊(Motion Blur)等特殊效果,从而实现了消隐算法。
5.1.3 再论:向量/矩阵、欧拉角与四元数(Quaternions)在章节“5.18.1 摄像机定标”中,我们已介绍了空间、变换、向量、矩阵在数学和物理上的意义,并将这些概念应用到了计算机三维视觉领域的三维重建中。本节进一步将这些概念应用到计算机三维图形学领域,并补充了旋转矩阵、欧拉角以及四元数的概念,以加深大家对这些知识的全局理解。
在计算机三维图形学中,通常把三维模型的顶点坐标表示为向量,把变换表示为矩阵,通过矩阵和向量的乘积来得到变换后的顶点坐标。用矩阵表示变换可以把平移(Translation)、缩放(Scaling)、错切(Sheering)、旋转(Rotation)等变换都统一到一个4×4的矩阵里面,其中旋转变换(3×3的正交矩阵)是其中最复杂的一种变换了。除了用矩阵,三维图形学中也常用绕3个坐标轴的欧拉角来表示,但这种旋转表示方式易导致万向锁(Gimbal Lock)的问题;同时从一个旋转角度插值到另一个旋转角度,插值结果也是不平滑的。
本节将重点介绍四元数(Quaternions)。四元数最早是为了扩展复数应用而产生与发展起来的。然而,人们发现四元数也可以应用在计算机图形学中的旋转变换上:单位四元数可用来表现所有形式的旋转,且仅需要四个浮点数。单位四元数与其他两种表示方式:三维旋转矩阵(正交矩阵)和欧拉角表示方法是等价的,但避免了欧拉角表示法中的万向锁问题。同时,与不直观的三维旋转矩阵相比,四元数能更方便地表示旋转轴和旋转角。此外,四元数也非常适合插值,因为它是一个可以单位化的4维向量。
5.1.3.1 用四元数表示空间点和旋转首先,可以非常简单地用四元数表示三维空间中的一个点。给定三维空间中一个点的笛卡尔坐标,则四元数表示为,即实部为0的四元数。
其次,四元数描述一个旋转(旋转轴和旋转角)也非常简单。使用四元数的好处是:我们可以简单地取出旋转轴与旋转角度。给定旋转轴单位向量,其中,绕这个轴的旋转角度为,则描述该转动的单位四元数(Unit Quaternions,只有单位化的四元数才可用来描述旋转)可表示为:
扩展:单位四元数只有表示旋转的能力,而更复杂的单位双四元数(Dual Quaternions,也称为对偶四元数)则有表示旋转和平移的能力。每个双四元数用8个元素或者两个四元数来表示。
给定一个点,则其绕旋转轴旋转角度后的空间位置为:,是不是特别简单?其中,是四元数的逆(倒数),,为的共轭(Conjugate);如果是单位四元数,则。
此外,单位四元数可非常简单地表示两个旋转操作的复合,只需将对应的单位四元数相乘即可!设单位四元数表示第一次旋转,表示第一次旋转,则总的旋转。
使用四元数的原因就是在于它非常适合插值,最简单的插值算法就是线性插值:
但这个结果是需要单位化的,否则的单位长度会发生变化,所以需要进行归一化(规范化、单位化):
尽管线性插值很有效,如下图所示,但不能以恒定的速率描述到之间的圆形曲线轨迹,这也是其弊端,我们需要找到一种插值方法使得到之间的夹角是线性的,即,这样我们得到了球形线性插值(Spherical Linear Interpolation)函数:
图5-6 线性插值与球面线性插值
如果使用D3D,可以直接使用D3DXQuaternionSlerp函数就可以完成这个插值过程。
5.1.3.2 欧拉角与四元数的相互转换欧拉角旋转其实是我们最容易理解的一种旋转方式。以我们生活中为例,一个舞蹈老师告诉我们,完成某个舞蹈动作需要先向你的左边转30°,再向左侧弯腰60°,再起身向后弯腰90°(如果你能办到的话)。上面这样一个旋转的过程其实和我们在三维中进行欧拉旋转很类似,即我们是通过指明绕三个轴X/Y/Z旋转的角度来进行旋转的,分别称为Roll(横滚)、Pitch(俯仰)和Yaw(航向),旋转角度分别用、和表示。而这也意味着我们需要指明一个旋转顺序,这是因为,先绕X轴旋转90°、再绕Y轴30°,和先绕Y轴旋转90°、再绕X轴30°得到的是不同的结果。比如在Unity软件系统里,欧拉旋转的旋转顺序是Z、X、Y。
同时需要特别注意的是,欧拉角这样的三角度系统(Three-Angle System)是依次、顺序变换的,比如先绕X轴转角度、再绕Y轴转角度,最后绕Z轴转角度。也即:欧拉角旋转是针对世界坐标系的,旋转轴不会跟着转。比如,依次绕X、Y、Z轴旋转,当绕Y轴旋转了90°的时候,会导致Z轴指向原来的X轴(即重合了),这相当于丢失了Z轴的自由度,此时旋转Z轴可能没有任何效果,这就导致了万向锁(Gimbal Lock)的问题。也就是说在这种状态下我们无论怎么旋转(当然还是要原先的顺序)都不可能得到某些想要的旋转效果,除非我们打破原先的旋转顺序或者同时旋转3个坐标轴。因此,三角度系统无法表现任意轴的旋转。而四元数是绕着3个坐标轴同时旋转,一次性旋转到位,这样就避免了旋转90°所导致的万向锁问题。
欧拉角到单位四元数的转换公式:
四元数到欧拉角的转换公式:
由于arctan和arcsin的结果范围是,这并不能覆盖所有朝向(对于角的取值范围已经满足,但对和不满足),因此需要用atan2来代替arctan,其取值范围为:
注意在其他坐标系下,需根据坐标轴的定义调整一下以上公式。如在Direct3D中,笛卡尔坐标系的X轴变为Z轴,Y轴变为X轴,Z轴变为Y轴(无需考虑方向)。
5.1.3.3 旋转矩阵与四元数的相互转换单位四元数到旋转矩阵(Rotation Matrix)的转换就是根据单位四元数的4个分量来得到矩阵中的各个元素的:
我们可以根据旋转矩阵计算点旋转后的位置:
。
矩阵表示法非常不直观,且非常不适合于进行插值,直接插值将得到非常糟糕的结果。此外,3×3旋转矩阵多达9个自由度,而实际上表示一个旋转只需要3个自由度。
5.1.3.4 旋转矩阵与欧拉角的相互转换欧拉角也可转化成矩阵形式:
从数学实现上来看,绕X/Y/Z三个轴的欧拉角旋转实际上就是用三个基本旋转矩阵来实现的,即刚体旋转是由三个基本旋转矩阵复合而成的,依次单独分开作用,每个基本矩阵各自代表绕着其转动轴的旋转。
最后,我们将描述旋转的三种方式:旋转矩阵、欧拉角和四元数的特点总结一下。旋转矩阵支持传递性,使用起来很简单方便;缺点是不太直观,数据存储量大,特别是旋转的时候,会浪费很多空间。欧拉角非常直观、且储存量小,但欧拉角是不可传递的,旋转的顺序影响旋转的结果,正因为把一个旋转变成了一系列的依次旋转,这就可能会导致万向锁,并且插值也不够平滑。而四元数的数据量介于二者之间,一个四元组即表示一个完整的旋转,这样不会导致万向锁,且插值容易。
在骨骼动画中,可以在文件中存储欧拉角,加载后将旋转数据转换为四元数。最后动画计算时统一采用矩阵运算。
5.1.3.5 四元数的数学性质我们知道,复数()可以表示平面向量,在物理上有着广泛应用。于是人们很自然地想到,能不能仿照复数复数集找到“三维复数”,用以表示空间向量呢?爱尔兰的数学家哈密顿Hamilton首先发现,要想在实数基础上建立三维复数,使它具有实数和复数的各种运算性质,这是不可能的。他进而研究“四维超复数”,在1843年提出所谓四元数()。四元数是4维空间中的向量,缺点是不太容易想像。对于,,本身的几何意义可简单地理解为一种旋转。
四元数的加减法,和一般复数的加减法相同,也满足交换律和结合律。四元数的乘法满足结合律但并不满足交换律(),这是和实数、复数最显著的不同,也正因为如此,四元数集不能构成数域,人们称它为广域。四元素的研究,推动了向量代数的发展。著名的物理学家麦克斯韦是哈密顿的学生,他在掌握了四元数理论后,利用向量分析等工具建立起了著称于世的电磁理论。
可以算出,两个四元数做一次乘法需要16次乘法和加法,而3×3的旋转矩阵则需要27次运算,所以当有多次旋转操作时,使用四元数可以获得更高的计算效率。此外,当使用有限的精度对矩阵进行大量的操作,四舍五入就会不断累积到矩阵中并导致错误,所以要对矩阵进行规范化(Normalize)和正交化(Orthogonalize),这是很费时的操作。相比于矩阵的9个元素,四元数只有4个元素,它的累积误差就相对较小,而且归一化时间更少。
扩展:除了在三维图形学和机器人领域,即使到目前为止四元数的用途仍在争辩之中。一些哈密顿的支持者非常反对奥利弗·亥维赛(Oliver Heaviside)的向量代数学和约西亚·威拉德·吉布斯(Josiah Willard Gibbs)的向量微积分的发展,以维持四元数的超然地位。对于三维空间这可以讨论,但对于更高维四元数就失效了(虽然可进行扩展,如八元数和柯利弗德代数学)。而事实上,在二十世纪中叶的科学和工程界中,向量几乎已完全取代四元数的位置。某些早期的麦克斯韦方程组使用了四元数来表述,但与后来亥维赛使用四条以向量为基础的麦克斯韦方程组表述相比较,使用四元数的表述并没有流行起来。
【“AI哲学一吴怀宇”(中国科学院博士、北大博士后)作者主页:www.OpenDAI.org;邮件:huaiyuwu@sina.com 视频/公众号:AI哲学一吴怀宇中国科学院(人工智能哲学)】
Archiver|手机版|科学网 ( 京ICP备07017567号-12 )
GMT+8, 2026-7-6 18:12
Powered by ScienceNet.cn
Copyright © 2007- 中国科学报社