这第一个ARToolkit的实例simpletext被用来熟系关于ARToolkit的相关知识。详细地示范怎么样开发一个 ARToolKit 的应用。
ARToolKit 应用程序主要可分为六个步骤
步骤1.应用程序初始化。init()函数实现初始化程序。
步骤2.抓取一帧输入视频 。由arVideoGetImage()函数实现
步骤3.探测标识卡。由arDetectMarker()函数实现
步骤4.计算摄像头的转移矩阵。由arGetTransMat()函数实现
步骤5.画上虚拟物体。由draw()函数实现
步骤6.关闭视频捕捉。由cleanup()函数实现
其中步骤2到步骤5,都位于mainloop函数内,步骤1单独位于init()函数。步骤6位于cleanup()函数
在这个simpletest程序中,最重要的函数是 main ,init , mainloop ,draw 和 cleanup。在本部分我们将详细地解释这些函数调用。 让大家熟系ARToolkit的程序编码
此程序只能被Hiro标识卡所识别,至于只能被Hiro识别,不能被别的标识卡识别,在我的另一篇文章,标识卡的制作中会讲到。
下面将程序代码贴出:
//此程序的限制:它只使用模板 Hiro
//程序 simpletest 使用模板匹配法来识别标识方框中的 Hiro 字样
#ifdef _WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#ifndef __APPLE__
#include <GL/gl.h>
#include <GL/glut.h>
#else
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#endif
#include <AR/gsub.h>
#include <AR/video.h>
#include <AR/param.h>
#include <AR/ar.h>
//需要初始化关键参数,这些参数可能是被用来进行模板模式匹配的模板信息,以及这些模板锁对应的虚拟物体
//初始化视频摄像机的相机特性参数
//
// Camera configuration.
//
#ifdef _WIN32
char *vconf = "Data\\WDM_camera_flipV.xml";
#else
char *vconf = "";//对于每一个平台,都定义了一个默认的字符串,这个字符串一般都打开你的应用程序结构中第一个可用的视频流。
#endif
int xsize, ysize;
int thresh = 100;
int count = 0;
char *cparam_name = "Data/camera_para.dat";
ARParam cparam;
char *patt_name = "Data/patt.hiro";
int patt_id;
double patt_width = 80.0;
double patt_center[2] = {0.0, 0.0};
double patt_trans[3][4];
static void init(void);
static void cleanup(void);
static void keyEvent( unsigned char key, int x, int y);
static void mainLoop(void);
static void draw( void );
int main(int argc, char **argv)
{
glutInit(&argc, argv);
init();//初始化
arVideoCapStart();//调用视频开始函数输入实时状态
argMainLoop( NULL, keyEvent, mainLoop );//启动主要的程序循环,键盘事件与函数keyEvent结合利用,通过主要的图像显示循环与mainLoop结合使用, argMainLoop的定义在gsub.c中
return (0);
}
static void keyEvent( unsigned char key, int x, int y)
{
/* quit if the ESC key is pressed */
if( key == 0x1b ) {
printf("*** %f (frame/sec)\n", (double)count/arUtilTimer());
cleanup();
exit(0);
}
}
/* main loop */
//ARTookit应用程序的大部分在这个例程里面完成,这个例程包含了开发原则的步骤二到步骤五(2抓取一帧输入视频,3探测标识卡,4计算摄像头的转移矩阵,5画上虚拟物体)
static void mainLoop(void)
{
ARUint8 *dataPtr;
ARMarkerInfo *marker_info;
int marker_num;
int j, k;
/* grab a vide frame */
//抓取一帧输入视频帧
/*该视频图像立即被输出到显示屏幕上。这个图像可以是一幅没有被扭曲的图像,也可以是根据摄像头失真信息被扭曲修正。扭曲以修正图像可以生成更加正常的图像,但是可能会导致视频帧的速率明显降低*/
if( (dataPtr = (ARUint8 *)arVideoGetImage()) == NULL ) {
arUtilSleep(2);
return;
}
if( count == 0 ) arUtilTimerReset();
count++;
//这例图像已经被扭曲
argDrawMode2D();
argDispImage( dataPtr, 0,0 );
/* detect the markers in the video frame */
//搜索整个图像来寻找含义正确的标识模板的方块
if( arDetectMarker(dataPtr, thresh, &marker_info, &marker_num) < 0 ) {//marker_num被用来存放标识卡的数量,marker_info是一个指向一列标识结构体的指针,这个结构体包含了坐标信息,识别可信度,以及每个标识对应的鉴定信息和物体。marker_info的详细信息在 API documentation中。
cleanup();
exit(0);
}
//此时,视频图像已经被显示和分析。所以我们不再需要使用它:我们可以在使用新的函数的同时使用帧捕捉器来启动一个新的帧捕捉操作,完成这些工作,我们只需要调用函数arVideoCapNext
arVideoCapNext();//使用帧捕捉器启动新的帧捕捉操作
//(备注:当我们调用这个函数的时候,使用上一个视频图像缓冲会导致坏的结果,确保你已经处理好了视频图像缓冲)
/* check for object visibility */
//所以的已经探测到的标识的可信度信息被加以比较,最终确定正确的标识鉴定信息为可信度最高的标识鉴定信息:
k = -1;
for( j = 0; j < marker_num; j++ ) {
if( patt_id == marker_info[j].id ) {
if( k == -1 ) k = j;
else if( marker_info[k].cf < marker_info[j].cf ) k = j;
}
}
//如果没有标识被找到(k==-1),应用程序会做一个简单的优化步骤,我们可以交换缓冲器而不需要调用函数draw,然后返回
if( k == -1 ) {
argSwapBuffers();
return;
}
/* get the transformation between the marker and the real camera */
//计算摄像头的转移矩阵,标识卡和摄像机之间的转移信息通过使用函数arGetTransMat来获取
arGetTransMat(&marker_info[k], patt_center, patt_width, patt_trans);//相对于标识物体 i 的真实的摄像机的位置和姿态包含在一个 3*4 的矩阵 patt_trans 中。
//使用画图函数,虚拟物体可以被叠加在标示卡上
draw();//画上虚拟物体
argSwapBuffers();
}
//初始化视频捕捉,读入ARTookit应用的初始参数信息
static void init( void )//应用程序初始化
{
//读取标识卡信息,摄像机参数信息,设置图像窗口
ARParam wparam;
/* open the video path */
//视频通道被打开
if( arVideoOpen( vconf ) < 0 ) exit(0);
/* find the size of the window */
//确定视频图像大小
if( arVideoInqSize(&xsize, &ysize) < 0 ) exit(0);
printf("Image size (x,y) = (%d,%d)\n", xsize, ysize);
//这些信息都是从文件里读取,这些文件的名字可以在命令行里被指定,或使用硬件编码的文件的默认 名称
/* set the initial camera parameters */
//摄像机的参数信息通过默认的摄像机参数文件名Data/camer_para.dat被读入
if( arParamLoad(cparam_name, 1, &wparam) < 0 ) {
printf("Camera parameter load error !!\n");
exit(0);
}
//这些参数根据现有的图像大小被转换,因为摄像机的参数根据图像的大小而改变,甚至是使用相同的 摄像机
arParamChangeSize( &wparam, xsize, ysize, &cparam );
//摄像机的参数被读入它的程序设置,摄像机的参数被输出显示到屏幕上
arInitCparam( &cparam );
printf("*** Camera Parameter ***\n");
arParamDisp( &cparam );
//其中patt_id是一个已经被识别的模板的鉴定信息(告诉我们是哪一个模板)
if( (patt_id=arLoadPatt(patt_name)) < 0 ) {
printf("pattern load error !!\n");
exit(0);
}
/* open the graphics window */
//打开窗口
argInit( &cparam, 1.0, 0, 0, 0, 0 );//argInit的第二个参数定义了一个缩放信息,适应视频图像格式的值设为1.0,值设为2.0时时双倍大小(比如说,输入 320*240 图像,输出为 VGA AR 格式)。
}
/* cleanup function called when program exits */
//关闭视频捕捉
//作用是停止视频处理以及关闭视频路径并释放它,使其他的应用可以使用
static void cleanup(void)
{
arVideoCapStop();
arVideoClose();
argCleanup();
}
//画上虚拟物体
static void draw( void )
{
//函数draw分为显示环境初始化,设置矩阵,显示物体几个部分,我们可以使用ARToolkit显示一个三维物体并设置最小的OpenGL状态来初始化一个3d显示:
double gl_para[16];
GLfloat mat_ambient[] = {0.0, 0.0, 1.0, 1.0};
GLfloat mat_flash[] = {0.0, 0.0, 1.0, 1.0};
GLfloat mat_flash_shiny[] = {50.0};
GLfloat light_position[] = {100.0,-200.0,200.0,0.0};
GLfloat ambi[] = {0.1, 0.1, 0.1, 0.1};
GLfloat lightZeroColor[] = {0.9, 0.9, 0.9, 0.1};
argDrawMode3D();
argDraw3dCamera( 0, 0 );
glClearDepth( 1.0 );
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
/* load the camera transformation matrix */
argConvGlpara(patt_trans, gl_para);//我们需要把这个转移矩阵(3*4 的矩阵)转化成 OpenGL 适用的格式(16 个值 的向量),可用函数 argConvGlpara 来完成此功能。
/*这十六个值是真实世界的摄像机的位置 和姿态信息,因此利用这些信息可以设置虚拟世界摄像机的位置,因此任何的图形物体都 可以被准确地放置在相应的真实标识卡上*/
glMatrixMode(GL_MODELVIEW);
glLoadMatrixd( gl_para );//虚拟世界的摄像机的位置是用函数 glLoadMatrixd(gl_para)来设置的。代码的最后是三维物体的显示
//显示白色光束下的一个蓝色立方体
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambi);
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightZeroColor);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_flash);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_flash_shiny);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMatrixMode(GL_MODELVIEW);
glTranslatef( 0.0, 0.0, 25.0 );
glutSolidCube(50.0);
//重置某些OpenGL的参数为默认值:
glDisable( GL_LIGHTING );
glDisable( GL_DEPTH_TEST );
//上述步骤出现并贯穿了主要显示函数的始终,当这个程序在运行时,鼠标事件被鼠标事件函数控制,键盘事件被键盘函数控制
}
其中有一些重要函数或者变量这里需要被提到,比如:
1.在开头定义的vconf参数,这个参数的作用是打开应用程序结构的第一个可用的视频流,对于每一个平台,都定义了一个默认的字符串。
2.ARToolkit里面也要初始化参数信息,不过这些参数都是视频摄像机和模板信息以及出现的虚拟物体
3. patt_id 是一个已经被识别的模板的鉴定信息(告诉我们是哪一个模板,相当于人 类的身份证)
4.函数 arginit 的第二个参数定义了一个缩放函数,适应视频图像格式时的值设为 1.0, 值设为 2.0 时是双倍大小
5.其中,在函数 arVideoGetImage 捕捉输入视频帧时,被捕捉到的图像可以是一幅没有被扭曲的图像,也可 以是一幅根据摄像头的失真信息被扭曲修正。扭曲以修正图像可以生成更加正常的图像, 但是可能会导致视频帧的速率明显降低,其中,扭曲并且修正可以生成更好的图像