00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include "StereoEngine.h"
00020 #include <iostream>
00021 #include <stdlib.h>
00022 #include <osgViewer/Viewer>
00023 #include <osg/Geode>
00024 #include <osg/ShapeDrawable>
00025 #include <osg/Geometry>
00026 #include <osg/PointSprite>
00027 #include <osg/Point>
00028 #include <osgGA/TrackballManipulator>
00029 #include <math.h>
00030 #include <uvsim/utilities/utilities.h>
00031
00032 using namespace boost::numeric::ublas;
00033
00034 namespace uvsim
00035 {
00036
00037
00038 StereoEngine::StereoEngine(IplImage *frameL, IplImage *frameR):
00039 frameL(frameL), frameR(frameR)
00040 {
00041 imageSize = cvGetSize(frameL);
00042
00043 rightImage = cvCreateImage(imageSize, IPL_DEPTH_8U, 3);
00044 rightImage->origin = frameR->origin;
00045 leftImage = cvCreateImage(imageSize, IPL_DEPTH_8U, 3);
00046 leftImage->origin = frameL->origin;
00047 leftImageR = cvCreateImage(imageSize, IPL_DEPTH_8U, 3);
00048 leftImageR->origin = frameL->origin;
00049
00050 rightGreyR = cvCreateImage(imageSize, IPL_DEPTH_8U, 1);
00051 leftGreyR = cvCreateImage(imageSize, IPL_DEPTH_8U, 1);
00052 img1r = cvCreateMat( imageSize.height, imageSize.width, CV_8U );
00053 img2r = cvCreateMat( imageSize.height, imageSize.width, CV_8U );
00054
00055
00056 rightGrey = cvCreateImage(imageSize, IPL_DEPTH_8U, 1);
00057 leftGrey = cvCreateImage(imageSize, IPL_DEPTH_8U, 1);
00058 points[0] = (CvPoint2D32f*)cvAlloc(maxCount*sizeof(points[0][0]));
00059 points[1] = (CvPoint2D32f*)cvAlloc(maxCount*sizeof(points[0][0]));
00060 eig = cvCreateImage( imageSize, IPL_DEPTH_32F, 1 );
00061 temp = cvCreateImage( imageSize, IPL_DEPTH_32F, 1 );
00062
00063 mx1 = cvCreateMat( imageSize.height, imageSize.width, CV_32F );
00064 my1 = cvCreateMat( imageSize.height, imageSize.width, CV_32F );
00065 mx2 = cvCreateMat( imageSize.height, imageSize.width, CV_32F );
00066 my2 = cvCreateMat( imageSize.height, imageSize.width, CV_32F );
00067 R1 = cvCreateMat(3, 3, CV_64F);
00068 R2 = cvCreateMat(3, 3, CV_64F);
00069 P1 = cvCreateMat(3, 4, CV_64F);
00070 P2 = cvCreateMat(3, 4, CV_64F);
00071
00072 Q = cvCreateMat( 4, 4, CV_32F);
00073
00074 disp = cvCreateMat( imageSize.height, imageSize.width, CV_16S );
00075 vdisp = cvCreateMat( imageSize.height, imageSize.width, CV_8U );
00076 realDisp = cvCreateMat( imageSize.height, imageSize.width, CV_32FC1);
00077 pair = cvCreateMat( imageSize.height, imageSize.width*2, CV_8UC3 );
00078 threeD = cvCreateImage(imageSize,IPL_DEPTH_32F,3);
00079
00080 BMState = cvCreateStereoBMState();
00081
00082
00083 }
00084
00085 StereoEngine::~StereoEngine()
00086 {
00087
00088 if (leftImage) cvReleaseImage(&leftImage);
00089 if (leftImageR) cvReleaseImage(&leftImageR);
00090 if (rightImage) cvReleaseImage(&rightImage);
00091 if (leftGrey) cvReleaseImage(&leftGrey);
00092 if (rightGrey) cvReleaseImage(&rightGrey);
00093 if (leftGreyR) cvReleaseImage(&leftGreyR);
00094 if (rightGreyR) cvReleaseImage(&rightGreyR);
00095 if (eig) cvReleaseImage(&eig);
00096 if (temp) cvReleaseImage(&temp);
00097
00098
00099
00100 if (points[0]) cvFree(&points[0]);
00101 if (points[1]) cvFree(&points[1]);
00102
00103
00104 if (F) cvReleaseMat(&F);
00105 if (Q) cvReleaseMat(&Q);
00106 if (E) cvReleaseMat(&E);
00107 if (T) cvReleaseMat(&T);
00108 if (R) cvReleaseMat(&R);
00109 if (M1) cvReleaseMat(&M1);
00110 if (M2) cvReleaseMat(&M2);
00111 if (D1) cvReleaseMat(&D1);
00112 if (D2) cvReleaseMat(&D2);
00113 if (R1) cvReleaseMat(&R1);
00114 if (R2) cvReleaseMat(&R2);
00115 if (P1) cvReleaseMat(&P1);
00116 if (P2) cvReleaseMat(&P2);
00117
00118 if (mx1) cvReleaseMat(&mx1);
00119 if (my1) cvReleaseMat(&my1);
00120 if (mx2) cvReleaseMat(&mx2);
00121 if (my2) cvReleaseMat(&my2);
00122 if (pair) cvReleaseMat(&pair);
00123 if (img1r) cvReleaseMat(&img1r);
00124 if (img2r) cvReleaseMat(&img2r);
00125
00126 if (disp) cvReleaseMat(&disp);
00127 if (vdisp) cvReleaseMat(&vdisp);
00128 if (realDisp) cvReleaseMat(&realDisp);
00129
00130 }
00131
00132 void StereoEngine::captureCalibrationImages(int number)
00133 {
00134 char left[50];
00135 char right[50];
00136
00137 sprintf(left,"data/%.02dL.jpg",number);
00138 sprintf(right,"data/%.02dR.jpg",number);
00139
00140 capture();
00141
00142 cvSaveImage(left, leftGrey);
00143 cvSaveImage(right, rightGrey);
00144 }
00145
00146 void StereoEngine::stereoCalibrate(std::string imageList, int nx, int ny, int useUncalibrated)
00147
00148
00149
00150 {
00151 int displayCorners = 1;
00152 int showUndistorted = 1;
00153 bool isVerticalStereo = false;
00154
00155 const int maxScale = 1;
00156 const float squareSize = .03f;
00157 FILE* f = fopen(imageList.c_str(), "rt");
00158 int i, j, lr, nframes, n = nx*ny, N = 0;
00159
00160 std::vector<CvPoint3D32f> objectPoints;
00161 std::vector<CvPoint2D32f> points[2];
00162 std::vector<int> npoints;
00163 std::vector<uchar> active[2];
00164 std::vector<CvPoint2D32f> temp(n);
00165 CvSize imageSize = {0,0};
00166
00167 double M1[3][3], M2[3][3], D1[5], D2[5];
00168 double R[3][3], T[3], E[3][3], F[3][3];
00169 CvMat _M1 = cvMat(3, 3, CV_64F, M1 );
00170 CvMat _M2 = cvMat(3, 3, CV_64F, M2 );
00171 CvMat _D1 = cvMat(1, 5, CV_64F, D1 );
00172 CvMat _D2 = cvMat(1, 5, CV_64F, D2 );
00173 CvMat _R = cvMat(3, 3, CV_64F, R );
00174 CvMat _T = cvMat(3, 1, CV_64F, T );
00175 CvMat _E = cvMat(3, 3, CV_64F, E );
00176 CvMat _F = cvMat(3, 3, CV_64F, F );
00177 if ( displayCorners )
00178 cvNamedWindow( "corners", 1 );
00179
00180 if ( !f )
00181 {
00182 fprintf(stderr, "can not open file %s\n",imageList.c_str());
00183 return;
00184 }
00185 for (i=0;;i++)
00186 {
00187 char buf[1024];
00188 int count = 0, result=0;
00189 lr = i % 2;
00190 std::vector<CvPoint2D32f>& pts = points[lr];
00191 if ( !fgets( buf, sizeof(buf)-3, f ))
00192 break;
00193 size_t len = strlen(buf);
00194 while ( len > 0 && isspace(buf[len-1]))
00195 buf[--len] = '\0';
00196 if ( buf[0] == '#')
00197 continue;
00198 std::string imageFile = std::string("data/")+=std::string(buf);
00199 IplImage* img = cvLoadImage( imageFile.c_str(), 0 );
00200 if ( !img )
00201 break;
00202 imageSize = cvGetSize(img);
00203
00204
00205
00206 for ( int s = 1; s <= maxScale; s++ )
00207 {
00208 IplImage* timg = img;
00209 if ( s > 1 )
00210 {
00211 timg = cvCreateImage(cvSize(img->width*s,img->height*s),
00212 img->depth, img->nChannels );
00213 cvResize( img, timg, CV_INTER_CUBIC );
00214 }
00215 result = cvFindChessboardCorners( timg, cvSize(nx, ny),
00216 &temp[0], &count,
00217 CV_CALIB_CB_ADAPTIVE_THRESH |
00218 CV_CALIB_CB_NORMALIZE_IMAGE);
00219 if ( timg != img )
00220 cvReleaseImage( &timg );
00221 if ( result || s == maxScale )
00222 for ( j = 0; j < count; j++ )
00223 {
00224 temp[j].x /= s;
00225 temp[j].y /= s;
00226 }
00227 if ( result )
00228 break;
00229 }
00230 if ( displayCorners )
00231 {
00232 printf("%s\n", imageFile.c_str());
00233 IplImage* cimg = cvCreateImage(imageSize, 8, 3 );
00234
00235 cvCvtColor( img, cimg, CV_GRAY2BGR );
00236 cvDrawChessboardCorners( cimg, cvSize(nx, ny), &temp[0],
00237 count, result );
00238 cvShowImage( "corners", cimg );
00239 cvReleaseImage( &cimg );
00240 int c = cvWaitKey(1000);
00241 if ( c == 27 || c == 'q' || c == 'Q' )
00242 exit(-1);
00243 }
00244 else
00245 putchar('.');
00246 N = pts.size();
00247 pts.resize(N + n, cvPoint2D32f(0,0));
00248 active[lr].push_back((uchar)result);
00249
00250 if ( result )
00251 {
00252
00253 cvFindCornerSubPix( img, &temp[0], count,
00254 cvSize(11, 11), cvSize(-1,-1),
00255 cvTermCriteria(CV_TERMCRIT_ITER+CV_TERMCRIT_EPS,
00256 30, 0.01) );
00257 copy( temp.begin(), temp.end(), pts.begin() + N );
00258 }
00259 cvReleaseImage( &img );
00260 }
00261 fclose(f);
00262 printf("\n");
00263
00264 nframes = active[0].size();
00265 objectPoints.resize(nframes*n);
00266 for ( i = 0; i < ny; i++ )
00267 for ( j = 0; j < nx; j++ )
00268 objectPoints[i*nx + j] =
00269 cvPoint3D32f(i*squareSize, j*squareSize, 0);
00270 for ( i = 1; i < nframes; i++ )
00271 copy( objectPoints.begin(), objectPoints.begin() + n,
00272 objectPoints.begin() + i*n );
00273 npoints.resize(nframes,n);
00274 N = nframes*n;
00275 CvMat _objectPoints = cvMat(1, N, CV_32FC3, &objectPoints[0] );
00276 CvMat _imagePoints1 = cvMat(1, N, CV_32FC2, &points[0][0] );
00277 CvMat _imagePoints2 = cvMat(1, N, CV_32FC2, &points[1][0] );
00278 CvMat _npoints = cvMat(1, npoints.size(), CV_32S, &npoints[0] );
00279 cvSetIdentity(&_M1);
00280 cvSetIdentity(&_M2);
00281 cvZero(&_D1);
00282 cvZero(&_D2);
00283
00284
00285 printf("Running stereo calibration ...");
00286 fflush(stdout);
00287 cvStereoCalibrate( &_objectPoints, &_imagePoints1,
00288 &_imagePoints2, &_npoints,
00289 &_M1, &_D1, &_M2, &_D2,
00290 imageSize, &_R, &_T, &_E, &_F,
00291 cvTermCriteria(CV_TERMCRIT_ITER+
00292 CV_TERMCRIT_EPS, 100, 1e-5),
00293 CV_CALIB_FIX_ASPECT_RATIO +
00294 CV_CALIB_ZERO_TANGENT_DIST +
00295 CV_CALIB_SAME_FOCAL_LENGTH );
00296 printf(" done\n");
00297
00298
00299
00300
00301
00302 std::vector<CvPoint3D32f> line[2];
00303 points[0].resize(N);
00304 points[1].resize(N);
00305 _imagePoints1 = cvMat(1, N, CV_32FC2, &points[0][0] );
00306 _imagePoints2 = cvMat(1, N, CV_32FC2, &points[1][0] );
00307 line[0].resize(N);
00308 line[1].resize(N);
00309 CvMat _L1 = cvMat(1, N, CV_32FC3, &line[0][0]);
00310 CvMat _L2 = cvMat(1, N, CV_32FC3, &line[1][0]);
00311
00312 cvUndistortPoints( &_imagePoints1, &_imagePoints1,
00313 &_M1, &_D1, 0, &_M1 );
00314 cvUndistortPoints( &_imagePoints2, &_imagePoints2,
00315 &_M2, &_D2, 0, &_M2 );
00316 cvComputeCorrespondEpilines( &_imagePoints1, 1, &_F, &_L1 );
00317 cvComputeCorrespondEpilines( &_imagePoints2, 2, &_F, &_L2 );
00318 double avgErr = 0;
00319 for ( i = 0; i < N; i++ )
00320 {
00321 double err = fabs(points[0][i].x*line[1][i].x +
00322 points[0][i].y*line[1][i].y + line[1][i].z)
00323 + fabs(points[1][i].x*line[0][i].x +
00324 points[1][i].y*line[0][i].y + line[0][i].z);
00325 avgErr += err;
00326 }
00327 printf( "avg err = %g\n", avgErr/(nframes*n) );
00328
00329
00330 cvSave("M1.xml",&_M1);
00331 cvSave("M2.xml",&_M2);
00332
00333 cvSave("D1.xml",&_D1);
00334 cvSave("D2.xml",&_D2);
00335
00336 cvSave("R.xml",&_R);
00337 cvSave("T.xml",&_T);
00338
00339 cvSave("E.xml",&_E);
00340 cvSave("F.xml",&_F);
00341
00342 }
00343
00344 void StereoEngine::findDisparity()
00345 {
00346
00347 capture();
00348 loadMatrices();
00349
00350
00351
00352
00353
00354
00355 cvStereoRectify( M1, M2, D1, D2, imageSize,
00356 R, T,
00357 R1, R2, P1, P2, Q,
00358 CV_CALIB_ZERO_DISPARITY);
00359 int isVerticalStereo = fabs(CV_MAT_ELEM(*P1,double,1,3)) > fabs(CV_MAT_ELEM(*P2,double,0,3));
00360
00361 cvInitUndistortRectifyMap(M1,D1,R1,P1,mx1,my1);
00362 cvInitUndistortRectifyMap(M2,D2,R2,P2,mx2,my2);
00363
00364 cvSave("Q.xml",Q);
00365
00366
00367 cvNamedWindow( "rectified", 1 );
00368
00369 pair = cvCreateMat( imageSize.height, imageSize.width*2, CV_8UC3 );
00370
00371 assert(BMState != 0);
00372 BMState->preFilterSize=41;
00373 BMState->preFilterCap=31;
00374 BMState->SADWindowSize=41;
00375 BMState->minDisparity=0;
00376 BMState->numberOfDisparities=128;
00377 BMState->textureThreshold=1;
00378 BMState->uniquenessRatio=15;
00379
00380
00381
00382 if ( leftGrey && rightGrey )
00383 {
00384 cvRemap( leftGrey, leftGreyR, mx1, my1 );
00385 cvRemap( rightGrey, rightGreyR, mx2, my2 );
00386
00387
00388 cvRemap( leftImage, leftImageR, mx1, my1);
00389
00390
00391 cvFindStereoCorrespondenceBM( leftGreyR, rightGreyR, disp,
00392 BMState);
00393
00394 cvConvertScale( disp, realDisp, 1.0f/16.0f, 0 );
00395 cvNormalize( disp, vdisp, 0, 256, CV_MINMAX );
00396 cvSave("disp.xml",disp);
00397 cvSave("vdisp.xml",vdisp);
00398 cvSave("realDisp.xml",realDisp);
00399 }
00400
00401
00402
00403
00406
00407
00408
00409
00410
00411
00412
00413
00414
00415
00416
00422
00423
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454
00455
00456
00457
00458
00459
00460
00462 }
00463
00464 void StereoEngine::displayDisparity()
00465 {
00466 CvMat part;
00467 cvGetCols( pair, &part, 0, imageSize.width );
00468 cvCvtColor( leftGreyR, &part, CV_GRAY2BGR );
00469 cvGetCols( pair, &part, imageSize.width,
00470 imageSize.width*2 );
00471 cvCvtColor( rightGreyR, &part, CV_GRAY2BGR );
00472
00473 for (int j = 0; j < imageSize.height; j += 16 )
00474 cvLine( pair, cvPoint(0,j),
00475 cvPoint(imageSize.width*2,j),
00476 CV_RGB(0,255,0));
00477 cvNamedWindow( "disparity" );
00478 cvShowImage( "disparity", vdisp );
00479
00480
00481 cvShowImage( "rectified", pair );
00482 }
00483
00484
00485
00486
00487 void *runViewerThread(void *ptr)
00488 {
00489 osgViewer::Viewer * viewer;
00490 viewer = (osgViewer::Viewer *)ptr;
00491 while (1) viewer->frame();
00492 }
00493
00494 IplImage* StereoEngine::reprojectTo3d()
00495 {
00496 cvReprojectImageTo3D(realDisp, threeD, Q);
00497 cvSave("threeD.xml",threeD);
00498 return threeD;
00499 }
00500
00501 void StereoEngine::drawPointCloud()
00502 {
00503
00504
00505 viewer.setCameraManipulator(new osgGA::TrackballManipulator);
00506 osg::Geode *geode = new osg::Geode();
00507 osg::Geometry *pointCloud = new osg::Geometry();
00508 osg::Vec3Array *points = new osg::Vec3Array();
00509 geode->setStateSet(makeStateSet(1.0f));
00510
00511 for ( int y=0; y<threeD->height; y++)
00512 {
00513 float* ptr = (float*) (threeD->imageData + y* threeD->widthStep);
00514
00515 for (int x=0; x<threeD->width; x++)
00516 {
00517
00518 if ((CV_MAT_ELEM(*disp, signed short,y,x) != (BMState->minDisparity - 1) * 16) && (abs(ptr[3*x+2])< 20))
00519 {
00520
00521 points->push_back(osg::Vec3(-ptr[3*x+2], -ptr[3*x+0], -ptr[3*x+1] ));
00522 }
00523
00524
00525
00526 }
00527 }
00528 pointCloud->setVertexArray(points);
00529 pointCloud->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
00530 pointCloud->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, points->size()));
00531
00532 geode->addDrawable(pointCloud);
00533
00534
00535 osg::Geometry* linesGeom = new osg::Geometry();
00536
00537
00538
00539
00540 osg::Vec3Array* vertices = new osg::Vec3Array(8);
00541 (*vertices)[0].set(0, 0, 0);
00542 (*vertices)[1].set(5, 0, 0);
00543 (*vertices)[2].set(0, 0, 0);
00544 (*vertices)[3].set(0, 3, 0);
00545 (*vertices)[4].set(0, 0, 0);
00546 (*vertices)[5].set(0, 0, 1);
00547
00548
00549
00550
00551 linesGeom->setVertexArray(vertices);
00552
00553
00554 osg::Vec4Array* colors = new osg::Vec4Array;
00555 colors->push_back(osg::Vec4(20.0f,20.0f,20.0f,20.0f));
00556 linesGeom->setColorArray(colors);
00557 linesGeom->setColorBinding(osg::Geometry::BIND_OVERALL);
00558
00559
00560
00561 osg::Vec3Array* normals = new osg::Vec3Array;
00562 normals->push_back(osg::Vec3(0.0f,-1.0f,0.0f));
00563 linesGeom->setNormalArray(normals);
00564 linesGeom->setNormalBinding(osg::Geometry::BIND_OVERALL);
00565
00566
00567
00568
00569 linesGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES,0,6));
00570
00571
00572
00573 geode->addDrawable(linesGeom);
00574
00575
00576
00577 viewer.setUpViewInWindow(0,0,300,300);
00578 viewer.setSceneData(geode);
00579 osg::Vec3 center(0,0,0), eye(-10,-10,0), up(0,0,1);
00580 viewer.getCamera()->setViewMatrixAsLookAt(eye, center, up);
00581
00582
00583 viewer.realize();
00584 viewer.run();
00585
00586
00587
00588 }
00589
00590 osg::StateSet* StereoEngine::makeStateSet(float size)
00591 {
00592 osg::StateSet *set = new osg::StateSet();
00593
00595 set->setMode(GL_BLEND, osg::StateAttribute::ON);
00596 osg::BlendFunc *fn = new osg::BlendFunc();
00597 fn->setFunction(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::DST_ALPHA);
00598 set->setAttributeAndModes(fn, osg::StateAttribute::ON);
00599
00601 osg::PointSprite *sprite = new osg::PointSprite();
00602 set->setTextureAttributeAndModes(0, sprite, osg::StateAttribute::ON);
00603
00605 osg::Point *point = new osg::Point();
00606 point->setSize(size);
00607 set->setAttribute(point);
00608
00610 set->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
00611 set->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
00612
00614
00615
00616
00617
00618 return set;
00619 }
00620
00621 void StereoEngine::loadMatrices()
00622 {
00623 if(uvsim::fileExists("M1.xml") && uvsim::fileExists("M2.xml") && uvsim::fileExists("D1.xml") && uvsim::fileExists("D2.xml") && uvsim::fileExists("R.xml") && uvsim::fileExists("T.xml") && uvsim::fileExists("E.xml") && uvsim::fileExists("F.xml"))
00624 {
00625 M1 = (CvMat*)cvLoad("M1.xml");
00626 M2 = (CvMat*)cvLoad("M2.xml");
00627
00628 D1 = (CvMat*)cvLoad("D1.xml");
00629 D2 = (CvMat*)cvLoad("D2.xml");
00630
00631 R = (CvMat*)cvLoad("R.xml");
00632 T = (CvMat*)cvLoad("T.xml");
00633
00634 E = (CvMat*)cvLoad("E.xml");
00635 F = (CvMat*)cvLoad("F.xml");
00636 }
00637 else
00638 {
00639 std::cout<<"Missing a calibration matrix xml file"<<std::endl;
00640 }
00641 }
00642
00643 void StereoEngine::capture()
00644 {
00645
00646 leftImage = frameL;
00647 rightImage = frameR;
00648
00649 cvCvtColor(leftImage, leftGrey, CV_BGR2GRAY);
00650 cvCvtColor(rightImage, rightGrey, CV_BGR2GRAY);
00651
00652
00653 }
00654
00655
00656
00657
00659
00660
00661
00662
00663
00664
00665
00666
00668
00669
00670
00671
00672
00673
00674
00676
00677
00678
00679
00680
00681
00682
00683
00684
00685
00686
00687
00688
00689
00690
00691
00692 void StereoEngine::drawPoints(int index)
00693 {
00694 if (index ==1)
00695 {
00696 for (int i = 0; i<pointCount; i++)
00697 {
00698 cvCircle(leftImage, cvPointFrom32f(points[0][i]), 3, CV_RGB(0,255,0), -1, 8,0);
00699 cvCircle(rightImage, cvPointFrom32f(points[1][i]), 3, CV_RGB(0,255,0), -1, 8,0);
00700
00701 }
00702 }
00703 else if (index ==2)
00704 {
00705 for (int i = 0; i<pointCount; i++)
00706 {
00707 cvCircle(leftImage, cvPointFrom32f(points[0][i]), 3, CV_RGB(255,0,0), -1, 8,0);
00708 cvCircle(rightImage, cvPointFrom32f(points[1][i]), 3, CV_RGB(255,0,0), -1, 8,0);
00709
00710 }
00711
00712 }
00713
00714
00715
00716 }
00717
00718 }
00719
00720