Camera Calibration 相机标定:Opencv应用方法
本系列文章由 @YhL_Leo 出品,转载请注明出处。
文章链接: http://blog.csdn.net/yhl_leo/article/details/49427383
Opencv中Camera Calibration and 3D Reconstruction中使用的是Z. Zhang(PAMI, 2000). A Flexible New Technique for Camera Calibration的方法。原理见原理简介(五)本文将对其进行介绍。
1 标定步骤
简单来说,Opencv中基于二维标定平面的标定方法主要步骤有:
- 1 读取相关设置信息,包括采用的pattern 信息(类型,尺寸),输入标定数据的信息(图像列表文件,视频采样方法),输出文件设置等,这些信息可以存为XML或YAML文件的形式或者在代码里直接显示设置。这里给出Opencv中提供的configuration file:
<?xml version="1.0"?>
<opencv_storage>
<Settings>
<!--
Number of inner corners per a item row and column. (square, circle)
-->
<BoardSize_Width>9</BoardSize_Width>
<BoardSize_Height>6</BoardSize_Height>
<!--
The size of a square in some user defined metric system (pixel, millimeter)
-->
<Square_Size>50</Square_Size>
<!--
The type of input used for camera calibration. One of: CHESSBOARD CIRCLES_GRID ASYMMETRIC_CIRCLES_GRID
-->
<Calibrate_Pattern>"CHESSBOARD"</Calibrate_Pattern>
<!--
The input to use for calibration.
To use an input camera -> give the ID of the camera, like "1"
To use an input video -> give the path of the input video, like "/tmp/x.avi"
To use an image list -> give the path to the XML or YAML file containing the list of the images, like "/tmp/circles_list.xml"
-->
<Input>"images/CameraCalibraation/VID5/VID5.xml"</Input>
<!--
If true (non-zero) we flip the input images around the horizontal axis.
-->
<Input_FlipAroundHorizontalAxis>0</Input_FlipAroundHorizontalAxis>
<!-- Time delay between frames in case of camera. -->
<Input_Delay>100</Input_Delay>
<!-- How many frames to use, for calibration. -->
<Calibrate_NrOfFrameToUse>25</Calibrate_NrOfFrameToUse>
<!--
Consider only fy as a free parameter, the ratio fx/fy stays the same as in the input cameraMatrix.
Use or not setting. 0 - False Non-Zero - True
-->
<Calibrate_FixAspectRatio>1</Calibrate_FixAspectRatio>
<!--
If true (non-zero) tangential distortion coefficients are set to zeros and stay zero.
-->
<Calibrate_AssumeZeroTangentialDistortion>1</Calibrate_AssumeZeroTangentialDistortion>
<!--
If true (non-zero) the principal point is not changed during the global optimization.
-->
<Calibrate_FixPrincipalPointAtTheCenter>1</Calibrate_FixPrincipalPointAtTheCenter>
<!-- The name of the output log file. -->
<Write_outputFileName>"out_camera_data.xml"</Write_outputFileName>
<!--
If true (non-zero) we write to the output file the feature points.
-->
<Write_DetectedFeaturePoints>1</Write_DetectedFeaturePoints>
<!--
If true (non-zero) we write to the output file the extrinsic camera parameters.
-->
<Write_extrinsicParameters>1</Write_extrinsicParameters>
<!--
If true (non-zero) we show after calibration the undistorted images.
-->
<Show_UndistortedImage>1</Show_UndistortedImage>
</Settings>
</opencv_storage>
其中,图像文件列表images/CameraCalibraation/VID5/VID5.xml
Opencv中采用列举法:
<?xml version="1.0"?>
<opencv_storage>
<images>
images/CameraCalibraation/VID5/xx1.jpg
images/CameraCalibraation/VID5/xx2.jpg
images/CameraCalibraation/VID5/xx3.jpg
images/CameraCalibraation/VID5/xx4.jpg
images/CameraCalibraation/VID5/xx5.jpg
images/CameraCalibraation/VID5/xx6.jpg
images/CameraCalibraation/VID5/xx7.jpg
images/CameraCalibraation/VID5/xx8.jpg
</images>
</opencv_storage>
文件中参数的含义比较清晰明了,此处就不累述。
- 2 依次从图像中检测pattern信息,如果检测成功,角点信息将会存储记录,用于标定解算。
cv::Mat viewGray;
if ( view.channels() == 3 )
cv::cvtColor( view, viewGray, CV_BGR2GRAY );
else
view.copyTo( viewGray );
std::vector<cv::Point2f> imagePoints;
bool success = cv::findChessboardCorners( viewGray , boardSize, imagePoints);
- 3 优化角点检测精度,将上述检测成功的角点,通过精确角点定位方法,提高精度,下图为Opencv提供的检测结果。
cv::cornerSubPix( viewGray,
imagePoints,
cv::Size(11,11),
cv::TermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));
- 4 标定解算,每幅图像都进行上述的角点检测后,一般给像点对应的物方角点虚拟坐标的方式赋予对应的坐标,即可进行相机标定解算,包括相机内参,相机畸变系数,以及相机在虚拟坐标所在坐标系中相对于每幅图像的相对位置姿态(旋转向量和平移向量)。
double reprojectionError= cv::calibrateCamera(
objectPoints, // calibration pattern points in the calibration pattern coordinate space
imagePoints, // projections of calibration pattern points
imageSize, // Size of the image used only to initialize the intrinsic camera matrix
cameraMatrix, // camera matrix A
distCoeffs, // distortion coefficients (k1,k2,p1,p2[,k3[,k4,k5,k6]])
rvecs, // rotation vectors
tvecs, // translation vectors
flag, // different calibration model
criteria); // Termination criteria for iterative optimization algorithm
5 标定精度评估,为了评价标定后的结果,可以按照标定得到的相机成像模型,由像点反算出物方空间坐标,进而得到一系列点云,通过对比解算点云与虚拟点云之间的差异性,就可以知道获得模型的好坏(严格来讲,如果误差较小,两者基本应该是一致的)。
6 图像畸变校正,在opencv示例中,作为标定的最后一个步骤,但是个人认为,这个应该可以作为一个相机标定后的副产品,对于处理的图像产品精度要求较高时,可以先进行畸变校正,再投入生产。下图为Opencv提供的畸变校正结果。
2 代码及结果
下面是个人的代码程序,有些部分并没完全按照Opencv的做法:
/*
Calibrate camera with chess board pattern.
- Editor: Menghan Xia, Yahui Liu.
- Data: 2015-07-28
- Email: yahui.cvrs@gmail.com
- Address: Computer Vision and Remote Sensing(CVRS) Lab, Wuhan University.
**/
#include<iostream>
#include <vector>
#include <string>
#include "cv.h"
#include "highgui.h"
#include "toolFunction.h"
#define DEBUG_OUTPUT_INFO
using namespace std;
using namespace cv;
void main()
{
char* folderPath = "E:/Images/New"; // image folder
std::vector<std::string> graphPaths;
std::vector<std::string> graphSuccess;
CalibrationAssist calAssist;
graphPaths = calAssist.get_filelist(folderPath); // collect image list
#ifdef DEBUG_OUTPUT_INFO
std::cout << "loaded " << graphPaths.size() << " images"<< std::endl;
#endif
if ( !graphPaths.empty() )
{
#ifdef DEBUG_OUTPUT_INFO
std::cout << "Start corner detection ..." << std::endl;
#endif
cv::Mat curGraph; // current image
cv::Mat gray; // gray image of current image
int imageCount = graphPaths.size();
int imageCountSuccess = 0;
cv::Size image_size;
cv::Size boardSize = cv::Size(19, 19); // chess board pattern size
cv::Size squareSize = cv::Size(15, 15); // grid physical size, as a scale factor
std::vector<cv::Point2f> corners; // one image corner list
std::vector<std::vector<cv::Point2f> > seqCorners; // n images corner list
if ( graphPaths.size() < 3 )
{
#ifdef DEBUG_OUTPUT_INFO
std::cout << "Calibrate failed, with less than three images!" << std::endl;
#endif
return ;
}
for ( int i=0; i<graphPaths.size(); i++ )
{
string graphpath = folderPath;
graphpath += "/" + graphPaths[i];
curGraph = cv::imread(graphpath);
if ( curGraph.channels() == 3 )
cv::cvtColor( curGraph, gray, CV_BGR2GRAY );
else
curGraph.copyTo( gray );
// for every image, empty the corner list
std::vector<cv::Point2f>().swap( corners );
// corners detection
bool success = cv::findChessboardCorners( curGraph, boardSize, corners );
if ( success ) // succeed
{
#ifdef DEBUG_OUTPUT_INFO
std::cout << i << " " << graphPaths[i] << " succeed"<< std::endl;
#endif
int row = curGraph.rows;
int col = curGraph.cols;
graphSuccess.push_back( graphpath );
imageCountSuccess ++;
image_size = cv::Size( col, row );
// find sub-pixels
cv::cornerSubPix(
gray,
corners,
cv::Size( 11, 11 ),
cv::Size( -1, -1 ),
cv::TermCriteria( CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1 ) );
seqCorners.push_back( corners );
#if 1
// draw corners and show them in current image
cv::Mat imageDrawCorners;
if ( curGraph.channels() == 3 )
curGraph.copyTo( imageDrawCorners );
else
cv::cvtColor( curGraph, imageDrawCorners, CV_GRAY2RGB );
for ( int j = 0; j < corners.size(); j ++)
{
cv::Point2f dotPoint = corners[j];
cv::circle( imageDrawCorners, dotPoint, 3.0, cv::Scalar( 0, 255, 0 ), -1 );
cv::Point2f pt_m = dotPoint + cv::Point2f(4,4);
char text[100];
sprintf( text, "%d", j+1 ); // corner indexes which start from 1
cv::putText( imageDrawCorners, text, pt_m, 1, 0.5, cv::Scalar( 255, 0, 255 ) );
}
std::string pathTemp;
pathTemp = folderPath;
pathTemp += "/#" + graphPaths[i];
// save image drawn with corners and labeled with indexes
cv::imwrite( pathTemp, imageDrawCorners );
#endif
}
#ifdef DEBUG_OUTPUT_INFO
else // failed
{
std::cout << graphPaths[i] << " corner detect failed!" << std::endl;
}
#endif
}
#ifdef DEBUG_OUTPUT_INFO
std::cout << "Corner detect done!" << std::endl
<< imageCountSuccess << " succeed! " << std::endl;
#endif
if ( imageCountSuccess < 3 )
{
#ifdef DEBUG_OUTPUT_INFO
std::cout << "Calibrated success " << imageCountSuccess
<< " images, less than 3 images." << std::endl;
#endif
return ;
}
else
{
#ifdef DEBUG_OUTPUT_INFO
std::cout << "Start calibration ..." << std::endl;
#endif
cv::Point3f point3D;
std::vector<cv::Point3f> objectPoints;
std::vector<double> distCoeffs;
std::vector<double> rotation;
std::vector<double> translation;
std::vector<std::vector<cv::Point3f>> seqObjectPoints;
std::vector<std::vector<double>> seqRotation;
std::vector<std::vector<double>> seqTranslation;
cv::Mat_<double> cameraMatrix;
// calibration pattern points in the calibration pattern coordinate space
for ( int t=0; t<imageCountSuccess; t++ )
{
objectPoints.clear();
for ( int i=0; i<boardSize.height; i++ )
{
for ( int j=0; j<boardSize.width; j++ )
{
point3D.x = i * squareSize.width;
point3D.y = j * squareSize.height;
point3D.z = 0;
objectPoints.push_back(point3D);
}
}
seqObjectPoints.push_back(objectPoints);
}
double reprojectionError = calibrateCamera(
seqObjectPoints,
seqCorners,
image_size,
cameraMatrix,
distCoeffs,
seqRotation,
seqTranslation,
CV_CALIB_FIX_ASPECT_RATIO|CV_CALIB_FIX_PRINCIPAL_POINT );
#ifdef DEBUG_OUTPUT_INFO
std::cout << "Calibration done!" << std::endl;
#endif
// calculate the calibration pattern points with the camera model
std::vector<cv::Mat_<double>> projectMats;
for ( int i=0; i<imageCountSuccess; i++ )
{
cv::Mat_<double> R, T;
// translate rotation vector to rotation matrix via Rodrigues transformation
cv::Rodrigues( seqRotation[i], R );
T = cv::Mat( cv::Matx31d(
seqTranslation[i][0],
seqTranslation[i][1],
seqTranslation[i][2]) );
cv::Mat_<double> P = cameraMatrix * cv::Mat( cv::Matx34d(
R(0,0), R(0,1), R(0,2), T(0),
R(1,0), R(1,1), R(1,2), T(1),
R(2,0), R(2,1), R(2,2), T(2) ) );
projectMats.push_back(P);
}
std::vector<cv::Point2d> PointSet;
int pointNum = boardSize.width*boardSize.height;
std::vector<cv::Point3d> objectClouds;
for ( int i=0; i<pointNum; i++ )
{
PointSet.clear();
for ( int j=0; j<imageCountSuccess; j++ )
{
cv::Point2d tempPoint = seqCorners[j][i];
PointSet.push_back(tempPoint);
}
// calculate calibration pattern points
cv::Point3d objectPoint = calAssist.triangulate(projectMats,PointSet);
objectClouds.push_back(objectPoint);
}
std::string pathTemp_point;
pathTemp_point = folderPath;
pathTemp_point += "/point.txt";
calAssist.save3dPoint(pathTemp_point,objectClouds);
std::string pathTemp_calib;
pathTemp_calib = folderPath;
pathTemp_calib += "/calibration.txt";
FILE* fp = fopen( pathTemp_calib.c_str(), "w" );
fprintf( fp, "The average of re-projection error : %lf\n", reprojectionError );
for ( int i=0; i<imageCountSuccess; i++ )
{
std::vector<cv::Point2f> errorList;
cv::projectPoints(
seqObjectPoints[i],
seqRotation[i],
seqTranslation[i],
cameraMatrix,
distCoeffs,
errorList );
corners.clear();
corners = seqCorners[i];
double meanError(0.0);
for ( int j=0; j<corners.size(); j++ )
{
meanError += std::sqrt((errorList[j].x - corners[j].x)*(errorList[j].x - corners[j].x) +
(errorList[j].y - corners[j].y)*(errorList[j].y - corners[j].y));
}
rotation.clear();
translation.clear();
rotation = seqRotation[i];
translation = seqTranslation[i];
fprintf( fp, "Re-projection of image %d:%lf\n", i+1, meanError/corners.size() );
fprintf( fp, "Rotation vector :\n" );
fprintf( fp, "%lf %lf %lf\n", rotation[0], rotation[1], rotation[2] );
fprintf( fp, "Translation vector :\n" );
fprintf( fp, "%lf %lf %lf\n\n", translation[0], translation[1], translation[2] );
}
fprintf( fp, "Camera internal matrix :\n" );
fprintf( fp, "%lf %lf %lf\n%lf %lf %lf\n%lf %lf %lf\n",
cameraMatrix(0,0), cameraMatrix(0,1), cameraMatrix(0,2),
cameraMatrix(1,0), cameraMatrix(1,1), cameraMatrix(1,2),
cameraMatrix(2,0), cameraMatrix(2,1), cameraMatrix(2,2));
fprintf( fp,"Distortion coefficient :\n" );
for ( int k=0; k<distCoeffs.size(); k++)
fprintf( fp, "%lf ", distCoeffs[k] );
#ifdef DEBUG_OUTPUT_INFO
std::cout << "Results are saved!" << std::endl;
#endif
}
}
}
// toolFunction.h
#ifndef TOOL_FUNCTION_H
#pragma once
#define TOOL_FUNCTION_H
#include<iostream>
#include <Windows.h>
#include <math.h>
#include <fstream>
#include <vector>
#include <string>
#include "cv.h"
#include "highgui.h"
using namespace cv;
using namespace std;
class CalibrationAssist
{
public:
CalibrationAssist() {}
~CalibrationAssist() {}
public:
std::vector<std::string>get_filelist( std::string foldname );
cv::Point3d triangulate( std::vector<cv::Mat_<double>> &ProjectMats,
std::vector<cv::Point2d> &imagePoints );
void save3dPoint( std::string path_, std::vector<cv::Point3d> &Point3dLists );
};
#endif // TOOL_FUNCTION_H
// toolFunction.cpp
#include "toolFunction.h"
std::vector<std::string> CalibrationAssist::get_filelist( std::string foldname )
{
foldname += "/*.*";
const char * mystr=foldname.c_str();
std::vector<std::string> flist;
std::string lineStr;
std::vector<std::string> extendName;
extendName.push_back("jpg");
extendName.push_back("JPG");
extendName.push_back("bmp");
extendName.push_back("png");
extendName.push_back("gif");
HANDLE file;
WIN32_FIND_DATA fileData;
char line[1024];
wchar_t fn[1000];
mbstowcs( fn, mystr, 999 );
file = FindFirstFile( fn, &fileData );
FindNextFile( file, &fileData );
while(FindNextFile( file, &fileData ))
{
wcstombs( line, (const wchar_t*)fileData.cFileName, 259);
lineStr = line;
// remove the files which are not images
for (int i = 0; i < 4; i ++)
{
if (lineStr.find(extendName[i]) < 999)
{
flist.push_back(lineStr);
break;
}
}
}
return flist;
}
cv::Point3d CalibrationAssist::triangulate(
std::vector<cv::Mat_<double>> &ProjectMats,
std::vector<cv::Point2d> &imagePoints)
{
int i,j;
std::vector<cv::Point2d> pointSet;
int frameSum = ProjectMats.size();
cv::Mat A(2*frameSum,3,CV_32FC1);
cv::Mat B(2*frameSum,1,CV_32FC1);
cv::Point2d u,u1;
cv::Mat_<double> P;
cv::Mat_<double> rowA1,rowA2,rowB1,rowB2;
int k = 0;
for ( i = 0; i < frameSum; i++ ) //get the coefficient matrix A and B
{
u = imagePoints[i];
P = ProjectMats[i];
cv::Mat( cv::Matx13d(
u.x*P(2,0)-P(0,0),
u.x*P(2,1)-P(0,1),
u.x*P(2,2)-P(0,2) ) ).copyTo( A.row(k) );
cv::Mat( cv::Matx13d(
u.y*P(2,0)-P(1,0),
u.y*P(2,1)-P(1,1),
u.y*P(2,2)-P(1,2) ) ).copyTo( A.row(k+1) );
cv::Mat rowB1( 1, 1, CV_32FC1, cv::Scalar( -(u.x*P(2,3)-P(0,3)) ) );
cv::Mat rowB2( 1, 1, CV_32FC1, cv::Scalar(-(u.y*P(2,3)-P(1,3)) ) );
rowB1.copyTo( B.row(k) );
rowB2.copyTo( B.row(k+1) );
k += 2;
}
cv::Mat X;
cv::solve( A, B, X, DECOMP_SVD );
return Point3d(X);
}
void CalibrationAssist::save3dPoint( std::string path_, std::vector<cv::Point3d> &Point3dLists)
{
const char * path = path_.c_str();
FILE* fp = fopen( path, "w" );
for ( int i = 0; i < Point3dLists.size(); i ++)
{
// fprintf(fp,"%d ",i);
fprintf( fp, "%lf %lf %lf\n",
Point3dLists[i].x, Point3dLists[i].y, Point3dLists[i].z);
}
fclose(fp);
#if 1
std::cout << "clouds of points are saved!" << std::endl;
#endif
}
使用数据为9张1200×800的图像:
程序运行结果:
1 运行控制台输出结果
2 角点检测图
- 3 反投影点云(CloudCompare显示)
对于上述结果的生成文件,此处用了C语言写成txt
的方式,读者完全可以考虑使用XML或YAML格式文件保存,至于畸变纠正的问题,也很简单,直接利用标定得到的相机内参和畸变系数,查询remap
函数的使用方法即可。此外,处理较大图像时,Opencv提供的方法速度可能会较慢,遇到这种情况,可以考虑把图像缩小或重写角点检测算法。
Camera Calibration 相机标定:Opencv应用方法的更多相关文章
- Camera Calibration 相机标定
Camera Calibration 相机标定 一.相机标定方法 在opencv中提供了一组函数用于实现相机的标定,标定返回的值包括:相机内参矩阵(fx fy xc yc).相机外参矩阵(R t)以及 ...
- Camera Calibration 相机标定:原理简介(五)
5 基于2D标定物的标定方法 基于2D标定物的标定方法,原理与基于3D标定物相同,只是通过相机对一个平面进行成像,就可得到相机的标定参数,由于标定物为平面,本身所具有的约束条机,相对后者标定更为简单. ...
- Camera Calibration 相机标定:原理简介(一)
1 相机标定常见方法 广义来说,相机标定不单包括成像过程的几何关系标定,还包括辐射关系的标定,本文只探讨几何关系.相机标定是3D计算机视觉(Computer Vision)里从2D图像中提取量测信息的 ...
- Camera Calibration 相机标定:原理简介(四)
4 基于3D标定物的标定方法 使用基于3D标定物进行相机标定,是一种传统且常见的相机标定法.3D标定物在不同应用场景下不尽相同,摄影测量学中,使用的3D标定物种类最为繁杂,如图-1的室内控制场,由多条 ...
- Camera Calibration 相机标定:原理简介(二)
2 针孔相机模型 常见的相机标定中,使用的相机多为针孔相机(Pinhole camera),也就是大家熟知的小孔成像理论.将其中涉及的坐标系之间的相互转换抽离出来,即为针孔相机模型的核心. 上图所示的 ...
- Camera Calibration 相机标定:原理简介(三)
3 绝对圆锥曲线 在进一步了解相机标定前,有必要了解绝对圆锥曲线(Absolute Conic)这一概念. 对于一个3D空间的点x,其投影空间的坐标为:x~=[x1,x2,x3,x4]T.我们定义无穷 ...
- 张正友相机标定Opencv实现以及标定流程&&标定结果评价&&图像矫正流程解析(附标定程序和棋盘图)
使用Opencv实现张正友法相机标定之前,有几个问题事先要确认一下,那就是相机为什么需要标定,标定需要的输入和输出分别是哪些? 相机标定的目的:获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的 ...
- 【视频开发】【计算机视觉】相机标定(Camera calibration)原理、步骤
相机标定(Camera calibration)原理.步骤 author@jason_ql(lql0716) http://blog.csdn.net/lql0716 在图像测量过程以及机器视觉应用 ...
- SLAM入门之视觉里程计(6):相机标定 张正友经典标定法详解
想要从二维图像中获取到场景的三维信息,相机的内参数是必须的,在SLAM中,相机通常是提前标定好的.张正友于1998年在论文:"A Flexible New Technique fro Cam ...
随机推荐
- [BZOJ3438][洛谷P1361]小M的作物
题目大意:有A.B两个集合和n个物品,每个物品只能放在一个集合里.每个物品放在不同集合内能获得不同价值.有一些物品,如果它们同时放在一个集合内,则会产生新的价值(A和B中都有且不一定相同(c1和c2) ...
- Android开发进度02
1,今日:目标:创建第一个android项目,创建android虚拟机 2,昨天:完成eclipseandroid环境的搭建 3,收获:修改.xml文件,将出错地方解决 4,问题:版本问题
- 洛谷P1138 第k小整数
我偏不用sort Treap好题啊 看到只有一个人写Treap,而且写的不清楚,那我就来详细地写一下,方便新人学习 第(-1)部分:前置知识 二叉查找树:满足左子树的数据都比根节点小,右子树的数据都比 ...
- Mysql学习总结(34)——Mysql 彻底解决中文乱码的问题
mysql 中常常出现对中文支持不友好的情况 常见的错误 "Illegal mix of collations for operation" 下面我们规整一下 mysql 数据库中 ...
- springmvc 异常Interceptor
无论做什么项目,进行异常处理都是非常有必要的,而且你不能把一些只有程序员才能看懂的错误代码抛给用户去看,所以这时候进行统一的异常处理,展现一个比较友好的错误页面就显得很有必要了. springMVC提 ...
- LintCode-交叉字符串
给出三个字符串:s1.s2.s3,推断s3是否由s1和s2交叉构成. 您在真实的面试中是否遇到过这个题? Yes 例子 比方 s1 = "aabcc" s2 = "dbb ...
- 设计模式入门之代理模式Proxy
//代理模式定义:为其它对象提供一种代理以控制对这个对象的訪问 //实例:鉴于书中给出的样例不太好.并且有些疑问,所以直接用保护代理作为实例 //要求,一旦订单被创建,仅仅有订单的创建人才干够改动订单 ...
- OC的动态继承编译机制
[问]为什么OC不能sizeof一个对象的大小或一个类的大小?和类结构相近的结构体却能够. [再问]为什么OC不能将对象声明到静态空间,如栈中?和类结构相近的结构体却能够. [答]由于OC的动态继承编 ...
- 通过setInterval函数在地图上每隔1s打一次点
<?php echo <<<_END <!doctype html> <html> <head> <meta charset=&quo ...
- 【原创】TimeSten安装与配置
1.安装TimeSten 2.安装时要指定TNS_ADMIN_LOCATION,即tnsnames.ora的路径,因为tt会根据这个连接Oracle.C:\TimesTen\tt1122_32\net ...