实习中项目要编写一个ARtoolkit实现交互的Demo给客户,在ARtoolkit中没有vuforia中集成的virtualbutton来直接实现,所以手动编写,查找ARtoolkit的原官方网站后,查到了一篇介绍交互的文章
来源: ARtoolkit学习一:Paddle交互代码理解和改写 – u011707827的专栏 – 博客频道 – CSDN.NET
实习中项目要编写一个ARtoolkit实现交互的Demo给客户,在ARtoolkit中没有vuforia中集成的virtualbutton来直接实现,所以手动编写,查找ARtoolkit的原官方网站后,查到了一篇介绍交互的文章
G. Gordan, M. Billinghurst, M. Bell, J. Woodfill, B. Kowalik, A. Erendi, J. Tilander. (2002)
The Use of Dense Stereo Range Data in Augmented Reality.
In Proceedings of the IEEE and ACM International Symposium on Mixed and Augmented Reality (ISMAR 2002) 30 Sept. – 1 Oct., 2002,
Darmstadt, Germany, IEEE Press, Los Alamitos, CA, pp. 14-23.
文章下载地址为:http://www.hitl.washington.edu/artoolkit/Papers/2002-ISMAR-DenseStereoData.pdf
具体可下载查看,其中交互的方法给出了两种,一种是基于paddle的,一种是基于freehand或pointer的。首先检测到交互实现的平面,跟基础的MarkerDetect一样,检测到Marker(标记物)来获得坐标系,在这里我们将这个平面叫做plane,并在这个plane上设置target的位置(实际就等于把一个button(按钮)位置定下来)。交互的工具paddle是另一个用一个Marker来表示,识别到这个Marker就找到了paddle,然后定位标记物的中心,并通过坐标转换投射到plane的坐标上,比较paddle的坐标和target的坐标,当距离非常近时,则触发交互的事件。
基于freehand或pointer的原理和前面的一样,只是检测pointer的方法改变了。由于手或者是笔这种物体不想Marker一样容易寻找,所以需要图像处理的内容,先将图像进行前景分割,然后找出手指或笔尖的位置,并通过坐标变换投射到plane上,后面的步骤后上述一样。单我对这个方法比较怀疑,因为每次都对整个帧图片进行分割等操作,开销一定不小。
文章读到这里后,去看ARtoolkit中有没有带这个接口,实际并没有写成接口,但是在ARToolKit-2.72.1版本中,examples中有paddleInteraction的一段样例代码,我上传到了这里,可以下载:http://download.csdn.NET/download/u011707827/9506183
在bin文件夹下,也有paddleInteractionTest.exe一段小程序,试着运行一下。这个程序中的定位plane的Marker是patterns中的pattMulti,打印出来后,将摄像头对准。屏幕显示出下图,可以看出根据Marker的位置定位出了plane平面。
然后做一个paddle,并检测出。
将这个paddle在平面上进行遮盖,触发了事件如下图,蓝色正方体变成了红色镂空正方体,不是很明显,但发生了改变。
可见这个代码的交互事件有点low,那就查看一下源码,并改写一下好了。开发环境VS2010+ARtoolkit2.72.1。代码中加载摄像机信息和一些基本变量设置不再赘述,一些用的到的会写出来。
直接看paddleInteraction.cpp中的主程序。
- int main(int argc, char **argv)
- {
- //initialize applications
- glutInit(&argc, argv); //glut初始化,不用管
- init();<span style=“white-space:pre”> </span> //初始化
- arVideoCapStart();
- //start the main event loop
- argMainLoop(NULL, keyEvent, mainLoop);//mainloop循环,是最重要的部分
- return 0;
- }
从init()函数看起,打开定义。前面的一大坨都是初始化摄像机设置,直接用,也不用看,从paddle那行开始看。
- static void init(void)//初始化
- {
- ARParam wparam;
- int i;
- /* 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 */
- 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);
- /* load the paddle marker file */
- //读取paddle的信息,并初始化paddle
- if ((paddleInfo = paddleInit(paddle_name)) == NULL) {
- printf(“paddleInit error!!\n”);
- exit(0);
- }
- printf(“Loaded Paddle File\n”);
- //读取MultiMarker的信息
- if ((config = arMultiReadConfigFile(config_name)) == NULL) {
- printf(“config data load error !!\n”);
- exit(0);
- }
- printf(“Loaded Multi Marker File\n”);
- /* initialize the targets */
- //初始化target的位置
- for (i = 0; i<TARGET_NUM; i++){
- myTarget[i].pos[0] = 50.0*i;
- myTarget[i].pos[1] = -50.0*i;
- myTarget[i].pos[2] = 50.0*i;
- myTarget[i].state = NOT_TOUCHED;
- }
和加载marker信息一样,写了paddleInit()来读入,具体可以查看paddle.h和paddle.c中的代码,但实际上就是和普通marker的操作是一致的,而且我们重点是实现交互的时间,不是paddle检测本身,所以不做深究。config是个很重要的变量,查看定义 ARMultiMarkerInfoT *config; 实际上config也是一种MarkerInfo的形式,其中最重要的成员是trans[3][4]这个数组,记录了从marker标定的plane坐标系到摄像头坐标系的转移矩阵,在后面会用到。
下面是一个循环体,发现是定义一个myTarget数组中的成员变量,myTarget是一个targetInfo数组。tragetInfo是一个struct体,代表了一个target的信息,target是我们交互中所定义的按键点,所以非常重要,查看targetInfo的定义部分。
- #define TOUCHED 1
- #define NOT_TOUCHED -1
- #define TARGET_NUM 5
- /* define a target struct for the objects that are to be touched */
- //定义出交互target信息
- typedef struct {
- int id; //target的ID
- int state; //target的状态,TOUCHED或者UN_TOUCHED,TOUCHED时触发状态
- float pos[3]; //target的在plane坐标系中的三维信息
- } targetInfo;
- targetInfo myTarget[TARGET_NUM]; //定义一组target
所以init()中的循环部分定义myTarget的初始位置,注意,这个位置是基于Marker坐标系的,即plane平面而言。
所以init()函数中做了这些事:
初始化摄像机参数,载入paddle文件,载入multiMarker文件,定义了target在multiMarker坐标系的位置。
再看mainloop()函数。比较长,分段读。
- ARUint8 *dataPtr; //每一帧的图像数据
- ARMarkerInfo *marker_info; //marker的信息
- int marker_num; //marker的数量
- float curPaddlePos[3]; //paddle在plane坐标系中的三维坐标
- int i;
- double err;
- /* 抓取一帧图像传入dataPtr中 */
- if ((dataPtr = (ARUint8 *)arVideoGetImage()) == NULL) {
- arUtilSleep(2);
- return;
- }
- if (count == 0) arUtilTimerReset();
- count++;
- /* detect the markers in the video frame */
- /* 检测图像中的Marker */
- if (arDetectMarkerLite(dataPtr, thresh, &marker_info, &marker_num) < 0) {
- cleanup();
- exit(0);
- }
curPaddlePos[3]是paddle的坐标,在后面要用到,其他的都是marker detect的功能,在最基础的simple程序中都有说明,不多说了。
下面一段程序跟显示有关,不需要理解。再往下看。是关于检测paddle和multiMarker分别得到paddle的转换矩阵以及multiMarker的转换矩阵。
- <span style=“white-space:pre”> </span>for (i = 0; i < marker_num; i++) marker_flag[i] = 0;
- /* 找到paddle坐标系和摄像机坐标系间转换矩阵 */
- paddleGetTrans(paddleInfo, marker_info, marker_flag,
- marker_num, &cparam);
- /* draw the 3D models */
- glClearDepth(1.0);
- glClear(GL_DEPTH_BUFFER_BIT);
- //如果检测到paddleInfo,绘制出paddle图形
- if (paddleInfo->active){
- draw_paddle(paddleInfo);//绘制函数
- }
- //有multimarker得到坐标系和转换矩阵
- if ((err = arMultiGetTransMat(marker_info, marker_num, config)) < 0) {
- argSwapBuffers();
- return;
- }
最后一部分。画出plane的平面,然后根据两个转换矩阵,换算出paddle在plane坐标系中的三维位置,然后通过checkCollision函数来检测是否Paddle的位置和target的位置有没碰撞,如果碰撞,则标记对应的myTarget的状态为TOUCHED,从而触发事件。这就是主程序代码的思路。
- <span style=“white-space:pre”> </span>//绘制出plane平面
- drawGroundGrid(config->trans, 20, 150.0f, 105.0f, 0.0f);
- //检测出paddle在plane坐标系中的3D坐标
- findPaddlePosition(curPaddlePos, paddleInfo->trans, config->trans);
- //检测paddle和targets的位置有没有碰撞
- for (i = 0; i<TARGET_NUM; i++){
- myTarget[i].state = NOT_TOUCHED;
- if (checkCollision(curPaddlePos, myTarget[i].pos, 20.0f))
- {
- myTarget[i].state = TOUCHED;
- fprintf(stderr, “touched !!\n”);
- }
- }
- /* draw the targets */
- for (i = 0; i<TARGET_NUM; i++){
- draw(myTarget[i], config->trans);
- }
- argSwapBuffers();
下篇将理解一些函数的代码,并改写它们实现另一些功能。