Thursday, December 26, 2013

SURF and BRISK in OpenCV

Today I spent half a day to compare the two feature detector and descriptor SURF and BRISK.

The conclusion is

  • SURF is more accurate but takes much longer
  • BRISK is 10 times faster with comparable accuracy. 
One thing to notice:
when using Brute-force matcher, mind the normType. See HERE
C++: BFMatcher::BFMatcher(int normType=NORM_L2, bool crossCheck=false )
Parameters:
* normType – One of NORM_L1, NORM_L2, NORM_HAMMING,NORM_HAMMING2. L1 and L2 norms are preferable choices for SIFT and SURF descriptors, 
* NORM_HAMMING should be used with ORB, BRISK and BRIEF, NORM_HAMMING2 should be used with ORB when WTA_K==3 or 4.

The other thing to notice is the threshold used to select good matches. Maybe RANSAC should be used.
SURF feature
BRISK feature
 



Saturday, November 16, 2013

Das ist eine brilliante Idee

Teil 4 Lektion 1


  • Gibt es schon Reaktionen auf den Sprachkurse?
  • Ja, wir haben viele Hörerbriefe bekommen.
  • Und was steht in den Briefen?
  • Die kann ich doch nicht alle vorlesen. Das dauert viel zu lange.
  • Nicht alle, aber einige - bitte!
  • Ja, das interessiert mich auch.
  • Na gut. 
  • Aber bitte, machen Sie es kurz!
  • Hier habe ich einen Brief von Herrn Card aus Amerika - Moment. Mir gefallen die Abenteuer von Andreas - asl Portier im Hotel Europa.
  • Mir auch
  • s
  • Und hier ist ein Brief von Angela aus Kolumbien. Sie schreibt, ' Ich bin so glücklick, weil ich die Grammatik studiert habe. Jetzt verstehe ich den Akkusativ - der war immer ...'
  • Grammatik, Grammatik, Akkusativ - das ist ja langweilig! Schreiben die Hörer den nichts über mich?! Wie finden die Hörer mich, das will ich wissen!
  • Kein Problem, Ex.  Hier ist ein Brief aus England - da steht etwas über dich! ' The intruduction of Ex is a brilliant idea. '
  • Das verstehe ich doch nicht! Was heißt das denn auf deutsch?
  • Du bist eine brillante Idee!
  • Idee? Wieso bin ich eine Idee? Ich bin ich!
  • s
  • Das hier ist noch ganz wichtig: Manche Hörer schreiben, daß sie Ex nicht so gut verstehen. 
  • Wir können ihr ja eine andere Stimme geben.
  • Probieren wir es doch mal! Ex, sprich mal etwas!
  • Ben dem Zauberwort sollte ich das Buch verlassen und ...
  • Okay! Stop! Und noch einmal bitte!
  • Ben dem Zauberwort sollte ich das Buch verlassen und ...
  • Kann ihre Stimme nicht ganz normal bleiben?
  • Nein - Ex ist ja eine besondere Person, ein weiblicher kobold, da braucht sie auch eine besondere Stimme. 
  • Das finde ich auch!
  • Aber das ist ein technisches Problem. Das lösen wir später. 

Saturday, November 2, 2013

Finds an object pose from 3D-2D point correspondences

C++: bool solvePnP(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess=false, int flags=ITERATIVE )


Parameters:
  • objectPoints – Array of object points in the object coordinate space, 3xN/Nx3 1-channel or 1xN/Nx1 3-channel, where N is the number of points. vector<Point3f> can be also passed here.
  • imagePoints – Array of corresponding image points, 2xN/Nx2 1-channel or 1xN/Nx1 2-channel, where N is the number of points. vector<Point2f> can be also passed here.
  • cameraMatrix – Input camera matrix A = \vecthreethree{fx}{0}{cx}{0}{fy}{cy}{0}{0}{1} .
  • distCoeffs – Input vector of distortion coefficients (k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6]]) of 4, 5, or 8 elements. If the vector is NULL/empty, the zero distortion coefficients are assumed.
  • rvec – Output rotation vector (see Rodrigues() ) that, together with tvec , brings points from the model coordinate system to the camera coordinate system.
  • tvec – Output translation vector.
  • useExtrinsicGuess – If true (1), the function uses the provided rvec and tvec values as initial approximations of the rotation and translation vectors, respectively, and further optimizes them.
  • flags –
    Method for solving a PnP problem:
    • CV_ITERATIVE Iterative method is based on Levenberg-Marquardt optimization. In this case the function finds such a pose that minimizes reprojection error, that is the sum of squared distances between the observed projections imagePoints and the projected (using projectPoints() ) objectPoints .
    • CV_P3P Method is based on the paper of X.S. Gao, X.-R. Hou, J. Tang, H.-F. Chang “Complete Solution Classification for the Perspective-Three-Point Problem”. In this case the function requires exactly four object and image points.
    • CV_EPNP Method has been introduced by F.Moreno-Noguer, V.Lepetit and P.Fua in the paper “EPnP: Efficient Perspective-n-Point Camera Pose Estimation”.
The function estimates the object pose given a set of object points, their corresponding image projections, as well as the camera matrix and the distortion coefficients.

Friday, October 11, 2013

Tuesday, September 10, 2013

Thursday, August 15, 2013

Image stiching in OpenCV

Reference to http://ramsrigoutham.com/2012/11/22/panorama-image-stitching-in-opencv/

Main steps of the code:
  1. Load two images;
  2. Convert to gray scale;
  3. Using SURF detector to find SURF descriptor in both images;
  4. matching the SURF descriptor using FLANN Matcher;
  5. Postprocessing matches to find good matches
  6. Using RANSAC to estimate the Homography matrix using the matched SURF descriptors;
  7. Warping the images based on the homography matrix

The image shows the definition of Homography which transforms 2d Planar point to the image plane. 


 The following image is shows the initial image and the matched SURF features of the two. Homography is derived from the left image to the right image.


Stiching image result.

Source Code

Tuesday, August 6, 2013

Resource to learn optimization

http://www.quora.com/Mathematical-Optimization/What-are-some-good-resources-to-learn-about-optimization#


Theory behind MPC


MPC is based on iterative, finite horizon optimization of a plant model. At time t the current plant state is sampled and a cost minimizing control strategy is computed (via a numerical minimization algorithm) for a relatively short time horizon in the future: [t,t+T]. Specifically, an online or on-the-fly calculation is used to explore state trajectories that emanate from the current state and find (via the solution of Euler-Lagrange equations) a cost-minimizing control strategy until time t+T. Only the first step of the control strategy is implemented, then the plant state is sampled again and the calculations are repeated starting from the current state, yielding a new control and new predicted state path. The prediction horizon keeps being shifted forward and for this reason MPC is also called receding horizon control.

Sunday, May 26, 2013

Hessian Matrix and Jacobian Matrix

Hessian Matrix
In mathematics, the Hessian matrix or Hessian is a square matrix of second-order partial derivatives of a function. It describes the local curvature of a function of many variables. The Hessian matrix was developed in the 19th century by the German mathematician Ludwig Otto Hesse and later named after him. Hesse originally used the term "functional determinants".
Given the real-valued function
f(x_1, x_2, \dots, x_n),\,\!
if all second partial derivatives of f exist and are continuous over the domain of the function, then the Hessian matrix of f is
H(f)_{ij}(\mathbf x) = D_i D_j f(\mathbf x)\,\!
where x = (x1x2, ..., xn) and Di is the differentiation operator with respect to the ith argument. Thus
H(f) = \begin{bmatrix}
\dfrac{\partial^2 f}{\partial x_1^2} & \dfrac{\partial^2 f}{\partial x_1\,\partial x_2} & \cdots & \dfrac{\partial^2 f}{\partial x_1\,\partial x_n} \\[2.2ex]
\dfrac{\partial^2 f}{\partial x_2\,\partial x_1} & \dfrac{\partial^2 f}{\partial x_2^2} & \cdots & \dfrac{\partial^2 f}{\partial x_2\,\partial x_n} \\[2.2ex]
\vdots & \vdots & \ddots & \vdots \\[2.2ex]
\dfrac{\partial^2 f}{\partial x_n\,\partial x_1} & \dfrac{\partial^2 f}{\partial x_n\,\partial x_2} & \cdots & \dfrac{\partial^2 f}{\partial x_n^2}
\end{bmatrix}.
Because f is often clear from context, H(f)(\mathbf x) is frequently abbreviated to H(\mathbf x).
The Hessian matrix is related to the Jacobian matrix by H(f)(\mathbf x) = J(\nabla \! f)(\mathbf x).
The determinant of the above matrix is also sometimes referred to as the Hessian.[1]
Hessian matrices are used in large-scale optimization problems within Newton-type methods because they are the coefficient of the quadratic term of a local Taylor expansion of a function. That is,
y=f(\mathbf{x}+\Delta\mathbf{x})\approx f(\mathbf{x}) + J(\mathbf{x})\Delta \mathbf{x} +\frac{1}{2} \Delta\mathbf{x}^\mathrm{T} H(\mathbf{x}) \Delta\mathbf{x}
where J is the Jacobian matrix, which is a vector (the gradient) for scalar-valued functions. The full Hessian matrix can be difficult to compute in practice; in such situations, quasi-Newton algorithms have been developed that use approximations to the Hessian. The best-known quasi-Newton algorithm is the BFGS algorithm
Jacobian Matrix

In vector calculus, the Jacobian matrix is the matrix of all first-order partial derivatives of a vector-valued function. Specifically, suppose F : \mathbb{R}^n \rightarrow \mathbb{R}^m is a function (which takes as input real n-tuples and produces as output real m-tuples). Such a function is given by m real-valued component functions, F_1(x_1,\ldots,x_n),\ldots,F_m(x_1,\ldots,x_n). The partial derivatives of all these functions with respect to the variables x_1,\ldots,x_n (if they exist) can be organized in an m-by-n matrix, the Jacobian matrix J of F, as follows:
J=\begin{bmatrix} \dfrac{\partial F_1}{\partial x_1} & \cdots & \dfrac{\partial F_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \dfrac{\partial F_m}{\partial x_1} & \cdots & \dfrac{\partial F_m}{\partial x_n}  \end{bmatrix}.


Friday, May 3, 2013

Implementation of KalmanFilter in Opencv

Two Functions used: gemm , solve
gemm: Performs generalized matrix multiplication.
C++: void gemm(InputArray src1, InputArray src2, double alpha, InputArray src3, double gamma, OutputArray dst, intflags=0 )
The function performs generalized matrix multiplication similar to the gemm functions in BLAS level 3. For example, gemm(src1,src2, alpha, src3, beta, dst, GEMM_1_T + GEMM_3_T) corresponds to
\texttt{dst} =  \texttt{alpha} \cdot \texttt{src1} ^T  \cdot \texttt{src2} +  \texttt{beta} \cdot \texttt{src3} ^T

solve: Solves one or more linear systems or least-squares problems.
C++: bool solve(InputArray src1, InputArray src2, OutputArray dst, int flags=DECOMP_LU)
  • solution (matrix inversion) method.
    • DECOMP_LU Gaussian elimination with optimal pivot element chosen.
    • DECOMP_CHOLESKY Cholesky LL^T factorization; the matrix src1 must be symmetrical and positively defined.
    • DECOMP_EIG eigenvalue decomposition; the matrix src1 must be symmetrical.
    • DECOMP_SVD singular value decomposition (SVD) method; the system can be over-defined and/or the matrix src1 can be singular.
    • DECOMP_QR QR factorization; the system can be over-defined and/or the matrix src1 can be singular.
    • DECOMP_NORMAL while all the previous flags are mutually exclusive, this flag can be used together with any of the previous; it means that the normal equations\texttt{src1}^T\cdot\texttt{src1}\cdot\texttt{dst}=\texttt{src1}^T\texttt{src2} are solved instead of the original system \texttt{src1}\cdot\texttt{dst}=\texttt{src2} .
The function solve solves a linear system or least-squares problem (the latter is possible with SVD or QR methods, or by specifying the flag DECOMP_NORMAL ):
\texttt{dst} =  \arg \min _X \| \texttt{src1} \cdot \texttt{X} -  \texttt{src2} \|

OpenCV: Surf Matching in Video Sequence

OpenCV: SURF Feature matching

  1. Load two images
  2. do SURF feature extraction
  3. Using Flann matching to match the keypoints
  4. Identify good matches
  5. find the object in the scene image

Wednesday, May 1, 2013

OpenCV: SURF Feature extractor

Main steps:
  1. Read Two Images
  2. Resize to half of its size
  3. Detectect Keypoints  using SURF
  4. Calculate Feature Descriptor
  5. Matching Descriptor using Brute Force Matcher
Original Image
 SURF Keypoints
SURF Matches


Sunday, April 28, 2013

Surf Detector

OpenCV Sobel edge detector

Orignal

Sobel X Gradient

Sobel Y Gradient

Sobel Gradient

OpenCV Tutorial 1: Mat - The basic Image Container


Color Space In OpenCV
There are, however, many other color systems each with their own advantages:
  • RGB is the most common as our eyes use something similar, our display systems also compose colors using these.
  • The HSV and HLS decompose colors into their hue, saturation and value/luminance components, which is a more natural way for us to describe colors. You might, for example, dismiss the last component, making your algorithm less sensible to the light conditions of the input image.
  • YCrCb is used by the popular JPEG image format.
  • CIE L*a*b* is a perceptually uniform color space, which comes handy if you need to measure the distance of a given color to another color.
Sample Code

Tuesday, April 16, 2013

Camera Calibration tool box from Caltech

http://www.vision.caltech.edu/bouguetj/calib_doc/
This is a release of a Camera Calibration Toolbox for Matlab® with a complete documentation. This document may also be used as a tutorial on camera calibration since it includes general information about calibration, references and related links. 

Monday, April 1, 2013

Latex array stretch

Stretch the row size of a table
\renewcommand\arraystretch{MyValue}% (MyValue=1.0 is for standard spacing)

Saturday, March 23, 2013

German 1

wir haben uns kennengelernt

Da begrüßte er mich, wir hatten uns persönlich nie kennengelernt, aber er legte Wert darauf, mich am 4. November kennenzulernen.

He greeted me, we had never met in person, but it was important to him that he meet me on the 4th of November.



独处也是一种能力



独处也是一种能力
周国平

                                       1
    人们往往把交往看作一种能力,却忽略了独处也是一种能力,并且在一定意义上是比交往更为重要的一种能力。反过来说,不擅交际固然是一种遗憾,不耐孤独也未尝不是一种很严重的缺陷。

                                       2
    独处也是一种能力,并非任何人任何时候都可具备的。具备这种能力并不意味着不再感到寂寞,而在于安于寂寞并使之具有生产力。人在寂寞中有三种状态。一是惶惶不安,茫无头绪,百事无心,一心逃出寂寞。二是渐渐习惯于寂寞,安下心来,建立起生活的条理,用读书、写作或别的事务来驱逐寂寞。三是寂寞本身成为一片诗意的土壤,一种创造的契机,诱发出关于存在、生命、自我的深邃思考和体验。

                                       3
    独处是人生中的美好时刻和美好体验,虽则有些寂寞,寂寞中却又有一种充实。独处是灵魂生长的必要空间,在独处时,我们从别人和事务中抽身出来,回到了自己。这时候,我们独自面对自己和上帝,开始了与自己的心灵以及与宇宙中的神秘力量的对话。一切严格意义上的灵魂生活都是在独处时展开的。和别人一起谈古说今,引经据典,那是闲聊和讨论;唯有自己沉浸于古往今来大师们的杰作之时,才会有真正的心灵感悟。和别人一起游山玩水,那只是旅游;唯有自己独自面对苍茫的群山和大海之时,才会真正感受到与大自然的沟通。

                                       4
    从心理学的观点看,人之需要独处,是为了进行内在的整合。所谓整合,就是把新的经验放到内在记忆中的某个恰当位置上。唯有经过这一整合的过程,外来的印象才能被自我所消化,自我也才能成为一个既独立又生长着的系统。所以,有无独处的能力,关系到一个人能否真正形成一个相对自足的内心世界,而这又会进而影响到他与外部世界的关系。

                                       5
    怎么判断一个人究竟有没有他的“自我”呢?有一个可靠的检验方法,就是看他能不能独处。当你自己一个人呆着时,你是感到百无聊赖,难以忍受呢,还是感到一种宁静、充实和满足?

                                       6
    对于独处的爱好与一个人的性格完全无关,爱好独处的人同样可能是一个性格活泼、喜欢朋友的人,只是无论他怎么乐于与别人交往,独处始终是他生活中的必需。在他看来,一种缺乏交往的生活当然是一种缺陷,一种缺乏独处的生活则简直是一种灾难了。

                                       7
    世上没有一个人能够忍受绝对的孤独。但是,绝对不能忍受孤独的人却是一个灵魂空虚的人。世上正有这样的一些人,他们最怕的就是独处,让他们和自己呆一会儿,对于他们简直是一种酷刑。只要闲了下来,他们就必须找个地方去消遣。他们的日子表面上过得十分热闹,实际上他们的内心极其空虚。他们所做的一切都是为了想方设法避免面对面看见自己。对此我只能有一个解释,就是连他们自己也感觉到了自己的贫乏,和这样贫乏的自己呆在一起是顶没有意思的,再无聊的消遣也比这有趣得多。这样做的结果是他们变得越来越贫乏,越来越没有了自己,形成了一个恶性循环。


【助您成才的15个建议】1.学会换位思考;2.学会适应环境;3.学会大方;4.学会低调;5.嘴要甜;6.有礼貌;7.言多必失;8.学会感恩;9.遵守时间;10.信守诺言;11.学会忍耐;12.有一颗平常心;13.学会赞扬别人;14.待上以敬,待下以宽;15.经常检讨自己。

【励志语录】1.好多人做不好自己,是因为总想着做别人!2.从不奢求生活能给予我最好的,只是执着于寻求最适合我的!3.宁愿跑起来被拌倒无数次,也不愿规规矩矩走一辈子,就算跌倒也要豪迈的笑 。4.不要生气要争气,不要看破要突破,不要嫉妒要欣赏,不要托延要积极,不要心动要行动。



Saturday, March 2, 2013

Convex Optimization


Reference in Convex optimization

Examples

The following problems are all convex minimization problems, or can be transformed into convex minimizations problems via a change of variables:

Methods

Convex minimization problems can be solved by the following contemporary methods:[4]
Other methods of interest:
Subgradient methods can be implemented simply and so are widely used.[5] Dual subgradient methods are subgradient methods applied to a dual problem. The drift-plus-penalty method is similar to the dual subgradient method, but takes a time average of the primal variables.

Friday, March 1, 2013

OpenOF Framework for Sparse Non-linear Least Squares Optimization on a GPU


2013-3-2
OpenOF Framework for Sparse Non-linear Least Squares Optimization on a GPU
With OpenOF, a framework is presented, which enables developers to design sparse optimizations regarding parameters and measurements and utilize the parallel power of a GPU


This code is written in Python with three major libraries: Thrust, CUSP and SymPy. Code framework is written in Python but can also generate C++ code.


Code website
https://github.com/OpenOF/OpenOF

Process of Nonlinear least squares optimization: 
1. Iterative method
2. Linearize the cost function in each iteration
3. Levenberg-Marquardt(LM) algorithm is standard, combing the Gauss-Newton algorithm with the gradient descent approach. LM guarantees convergence.
4. In each interation , solving linear Ax = b is most intensive.
5. Sparse matrix representation is used: sparseLM (Lourakis, 2010) and g2o (Kummerle et al., 2011), but on CPU
6. Solving Ax =b, many algorithms can achieve, Cholesky docomposition A = LDL'
7. this paper use Conjugate gradient (CG) approach on GPU.

Nonlinear least squares optimization is widely used in SLAM and BA. 
The authors' some comments about three BA libraries:
   1.The SBA library (Lourakis and Argyros,2009) takes advantage of the special structure of the Hessian matrix to apply the Schur complement for solving the linear system. Nevertheless it has several drawbacks. Integrating additional parameters which remain identical for all measurements (e.g. camera calibration) is not possible, as the structure would change such that the Schur complement could not be applied anymore.

2. sparseLM (Lourakis, 2010) is slow.
3. g2o: the Jacobian is evaluated by numerical differentiation which is time consuming and also degrades the convergence rate.
4. ISAM: (Kaess et al., 2011),which address only a subset of problems, have been presented previously for least squares optimization

Overall Comment: this paper is claims to present an open source framework for sparse nonlinear opitmization. The cost functions is described in high level scripting language. It can not be used without GPU yet. It seems for me g2o or iSam would be more useful on CPU. 


Thursday, February 28, 2013

C++类模版详解


有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
class Compare_int
{
public :
Compare(int a,int b)
{
x=a;
y=b;
}
int max( )
{
return (x>y)?x:y;
}
int min( )
{
return (x<y)?x:y;}
private :
int x,y;
};
其作用是对两个整数作比较,可以通过调用成员函数max和min得到两个整数中的大者和小者。

如果想对两个浮点数(float型)作比较,需要另外声明一个类:
class Compare_float
{
public :
Compare(float a,float b)
{x=a;y=b;}
float max( )
{return (x>y)?x:y;}
float min( )
{return (x<y)?x:y;}
private :
float x,y;
}
显然这基本上是重复性的工作,应该有办法减少重复的工作。

C++在发展的后期增加了模板(template )的功能,提供了解决这类问题的途径。可以声明一个通用的类模板,它可以有一个或多个虚拟的类型参数,如对以上两个类可以综合写出以下的类模板:
template <class numtype> //声明一个模板,虚拟类型名为numtype
class Compare //类模板名为Compare
{
public :
Compare(numtype a,numtype b)
{x=a;y=b;}
numtype max( )
{return (x>y)?x:y;}
numtype min( )
{return (x<y)?x:y;}
private :
numtype x,y;
};

请将此类模板和前面第一个Compare_int类作一比较,可以看到有两处不同:
  1. 声明类模板时要增加一行
    template <class 类型参数名>
  2. 原有的类型名int换成虚拟类型参数名numtype。
    在建立类对象时,如果将实际类型指定为int型,编译系统就会用int取代所有的numtype,如果指定为float型,就用float取代所有的numtype。这样就能实现“一类多用”。

由于类模板包含类型参数,因此又称为参数化的类。如果说类是对象的抽象,对象是类的实例,则类模板是类的抽象,类是类模板的实例。

利用类模板可以建立含各种数据类型的类。在声明了一个类模板后,怎样使用它?怎样使它变成一个实际的类?
先回顾一下用类来定义对象的方法:
Compare_int cmp1(4,7); // Compare_int是已声明的类
用类模板定义对象的方法与此相似,但是不能直接写成
Compare cmp(4,7); // Compare是类模板名
Compare是类模板名,而不是一个具体的类,类模板体中的类型numtype并不是一个实际的类型,只是一个虚拟的类型,无法用它去定义对象。
必须用实际类型名去取代虚拟的类型,具体的做法是:
Compare <int> cmp(4,7);
即在类模板名之后在尖括号内指定实际的类型名,在进行编译时,编译系统就用int取代类模板中的类型参数numtype,这样就把类模板具体化了,或者说实例化了。这时Compare<int>就相当于前面介绍的Compare_int类。

例9.14是一个完整的例子。
例9.14 声明一个类模板,利用它分别实现两个整数、浮点数和字符的比较,求出大数和小数。
#include <iostream>
using namespace std;
template <class numtype>
//定义类模板
class Compare
{
public :
Compare(numtype a,numtype b)
{x=a;y=b;}
numtype max( )
{return (x>y)?x:y;}
numtype min( )
{return (x<y)?x:y;}
private :
numtype x,y;
};
int main( )
{
Compare<int > cmp1(3,7);//定义对象cmp1,用于两个整数的比较
cout<<cmp1.max( )<<″ is the Maximum of two integer numbers.″<<endl;
cout<<cmp1.min( )<<″ is the Minimum of two integer numbers.″<<endl<<endl;
Compare<float > cmp2(45.78,93.6); //定义对象cmp2,用于两个浮点数的比较
cout<<cmp2.max( )<<″ is the Maximum of two float numbers.″<<endl;
cout<<cmp2.min( )<<″ is the Minimum of two float numbers.″<<endl<<endl;
Compare<char> cmp3(′a′,′A′); //定义对象cmp3,用于两个字符的比较
cout<<cmp3.max( )<<″ is the Maximum of two characters.″<<endl;
cout<<cmp3.min( )<<″ is the Minimum of two characters.″<<endl;
return 0;
}
运行结果如下:
7 is the Maximum of two integers.
3 is the Minimum of two integers.
93.6 is the Maximum of two float numbers.
45.78 is the Minimum of two float numbers.
a is the Maximum of two characters.
A is the Minimum of two characters.

还有一个问题要说明: 上面列出的类模板中的成员函数是在类模板内定义的。如果改为在类模板外定义,不能用一般定义类成员函数的形式:
numtype Compare::max( ) {…} //不能这样定义类模板中的成员函数
而应当写成类模板的形式:
template <class numtype>
numtype Compare<numtype>::max( )
{{return (x>y)?x:y;}

归纳以上的介绍,可以这样声明和使用类模板:
  1. 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
  2. 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
  3. 在类声明前面加入一行,格式为
    template <class 虚拟类型参数>,如
    template <class numtype> //注意本行末尾无分号
    class Compare
    {…}; //类体
  4. 用类模板定义对象时用以下形式:
    类模板名<实际类型名> 对象名;
    类模板名<实际类型名> 对象名(实参表列);

    Compare<int> cmp;
    Compare<int> cmp(3,7);
  5. 如果在类模板外定义成员函数,应写成类模板形式:
    template <class 虚拟类型参数>
    函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}

关于类模板的几点说明:
  1. 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如
    template <class T1,class T2>
    class someclass
    {…};
    在定义对象时分别代入实际的类型名,如
    someclass<int,double> obj;
  2. 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
  3. 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。