OpenCV 常用算法

来源:互联网 发布:软件过程成熟度 编辑:程序博客网 时间:2024/06/08 01:26
 
6月8日

关于直方图

    图像直方图是图像处理中一种十分重要的图像分析工具,它描述了一幅图像的灰度级内 容。
   
    从数学上来说图像直方图是图像各灰度值统计特性与图像灰度值的函数,它统计一幅图像中各个灰度级出现的次数或概率;
   
    从图形上来说,它是一个二维图, 横坐标表示图像中各个像素点的灰度级,纵坐标为各个灰度级上图像各个像素点出现的 次数或概率。任何一幅图像的直方图都包含了丰富的信息,它主要用在图象分割,图像灰度变换等处理过程中。
   
    最合适直方图的典型特征是从左端到右端的每一个辉度级别都有相对应的图形存在,也就是说,值是平均分布的,而且图像应该服从正态分布,即中间大,两头小的情况。
 

 

 

 

1.正常曝光的照片

2.
曝光不足的照片


3.
曝光过渡的照片

4.
反差过低的照片

5.
反差过高的照片

 
 

    
    没有任何两幅图像的直方图是完全一样的。 因此,利用直方图可以在目标跟踪时有所作为。
6月1日

你够鲁棒吗

鲁棒是Robust的音译,也就是健壮和强壮的意思。
鲁棒性(robustness)就是系统的健壮性。它是在异常和危险情况下系统生存的关键。比如说,计算机软件在输入错误、磁盘故障、网络过载或有意攻击情况下,能否不死机、不崩溃,就是该软件的鲁棒性。所谓“鲁棒性”,是指控制系统在一定(结构,大小)的参数摄动下,维持某些性能的特性。根据对性能的不同定义,可分为稳定鲁棒性和性能鲁棒性。以闭环系统的鲁棒性作为目标设计得到的固定控制器称为鲁棒控制器。

4月12日

OpenCV学习笔记(五)卡尔曼滤波器

 

 

 

卡尔曼滤波器 Kalman Filter

 

1    什么是卡尔曼滤波器

What is the Kalman Filter?

 

在学习卡尔曼滤波器之前,首先看看为什么叫“卡尔曼”。跟其他著名的理论(例如傅立叶变换,泰勒级数等等)一样,卡尔曼也是一个人的名字,而跟他们不同的是,他是个现代人!

 

卡尔曼全名Rudolf Emil Kalman,匈牙利数学家,1930年出生于匈牙利首都布达佩斯。19531954年于麻省理工学院分别获得电机工程学士及硕士学位。1957年于哥伦比亚大学获得博士学位。我们现在要学习的卡尔曼滤波器,正是源于他的博士论文和1960年发表的论文《A New Approach to Linear Filtering and Prediction Problems》(线性滤波与预测问题的新方法)。如果对这编论文有兴趣,可以到这里的地址下载: http://www.cs.unc.edu/~welch/media/pdf/Kalman1960.pdf

 

简单来说,卡尔曼滤波器是一个“optimal recursive data processing algorithm(最优化自回归数据处理算法)”。对于解决很大部分的问题,他是最优,效率最高甚至是最有用的。他的广泛应用已经超过30年,包括机器人导航,控制,传感器数据融合甚至在军事方面的雷达系统以及导弹追踪等等。近年来更被应用于计算机图像处理,例如头脸识别,图像分割,图像边缘检测等等。

 

2.卡尔曼滤波器的介绍

Introduction to the Kalman Filter

 

为了可以更加容易的理解卡尔曼滤波器,这里会应用形象的描述方法来讲解,而不是像大多数参考书那样罗列一大堆的数学公式和数学符号。但是,他的5条公式是其核心内容。结合现代的计算机,其实卡尔曼的程序相当的简单,只要你理解了他的那5条公式。

 

在介绍他的5条公式之前,先让我们来根据下面的例子一步一步的探索。

 

假设我们要研究的对象是一个房间的温度。根据你的经验判断,这个房间的温度是恒定的,也就是下一分钟的温度等于现在这一分钟的温度(假设我们用一分钟来做时间单位)。假设你对你的经验不是100%的相信,可能会有上下偏差几度。我们把这些偏差看成是高斯白噪声(White Gaussian Noise),也就是这些偏差跟前后时间是没有关系的而且符合高斯分配(Gaussian Distribution)。另外,我们在房间里放一个温度计,但是这个温度计也不准确的,测量值会比实际值偏差。我们也把这些偏差看成是高斯白噪声。

 

好了,现在对于某一分钟我们有两个有关于该房间的温度值:你根据经验的预测值(系统的预测值)和温度计的值(测量值)。下面我们要用这两个值结合他们各自的噪声来估算出房间的实际温度值。

 

假如我们要估算k时刻的是实际温度值。首先你要根据k-1时刻的温度值,来预测k时刻的温度。因为你相信温度是恒定的,所以你会得到k时刻的温度预测值是跟k-1时刻一样的,假设是23度,同时该值的高斯噪声的偏差是5度(5是这样得到的:如果k-1时刻估算出的最优温度值的偏差是3,你对自己预测的不确定度是4度,他们平方相加再开方,就是5)。然后,你从温度计那里得到了k时刻的温度值,假设是25度,同时该值的偏差是4度。

 

由于我们用于估算k时刻的实际温度有两个温度值,分别是23度和25度。究竟实际温度是多少呢?相信自己还是相信温度计呢?究竟相信谁多一点,我们可以用他们的covariance来判断。因为Kg^2=5^2/(5^2+4^2),所以Kg=0.78,我们可以估算出k时刻的实际温度值是:23+0.78*(25-23)=24.56度。可以看出,因为温度计的covariance比较小(比较相信温度计),所以估算出的最优温度值偏向温度计的值。

 

现在我们已经得到k时刻的最优温度值了,下一步就是要进入k+1时刻,进行新的最优估算。到现在为止,好像还没看到什么自回归的东西出现。对了,在进入k+1时刻之前,我们还要算出k时刻那个最优值(24.56度)的偏差。算法如下:((1-Kg)*5^2)^0.5=2.35。这里的5就是上面的k时刻你预测的那个23度温度值的偏差,得出的2.35就是进入k+1时刻以后k时刻估算出的最优温度值的偏差(对应于上面的3)。

 

就是这样,卡尔曼滤波器就不断的把covariance递归,从而估算出最优的温度值。他运行的很快,而且它只保留了上一时刻的covariance。上面的Kg,就是卡尔曼增益(Kalman Gain)。他可以随不同的时刻而改变他自己的值,是不是很神奇!

 

下面就要言归正传,讨论真正工程系统上的卡尔曼。

 

3    卡尔曼滤波器算法

The Kalman Filter Algorithm

 

在这一部分,我们就来描述源于Dr Kalman 的卡尔曼滤波器。下面的描述,会涉及一些基本的概念知识,包括概率(Probability),随即变量(Random Variable),高斯或正态分配(Gaussian Distribution)还有State-space Model等等。但对于卡尔曼滤波器的详细证明,这里不能一一描述。

 

首先,我们先要引入一个离散控制过程的系统。该系统可用一个线性随机微分方程(Linear Stochastic Difference equation)来描述:

X(k)=A X(k-1)+B U(k)+W(k)

再加上系统的测量值:

Z(k)=H X(k)+V(k)

上两式子中,X(k)k时刻的系统状态,U(k)k时刻对系统的控制量。AB是系统参数,对于多模型系统,他们为矩阵。Z(k)k时刻的测量值,H是测量系统的参数,对于多测量系统,H为矩阵。W(k)V(k)分别表示过程和测量的噪声。他们被假设成高斯白噪声(White Gaussian Noise),他们的covariance 分别是QR(这里我们假设他们不随系统状态变化而变化)。

 

对于满足上面的条件(线性随机微分系统,过程和测量都是高斯白噪声),卡尔曼滤波器是最优的信息处理器。下面我们来用他们结合他们的covariances 来估算系统的最优化输出(类似上一节那个温度的例子)。

 

首先我们要利用系统的过程模型,来预测下一状态的系统。假设现在的系统状态是k,根据系统的模型,可以基于系统的上一状态而预测出现在状态:

X(k|k-1)=A X(k-1|k-1)+B U(k) ……….. (1)

(1)中,X(k|k-1)是利用上一状态预测的结果,X(k-1|k-1)是上一状态最优的结果,U(k)为现在状态的控制量,如果没有控制量,它可以为0

 

到现在为止,我们的系统结果已经更新了,可是,对应于X(k|k-1)covariance还没更新。我们用P表示covariance

P(k|k-1)=A P(k-1|k-1) A+Q ……… (2)

(2)中,P(k|k-1)X(k|k-1)对应的covarianceP(k-1|k-1)X(k-1|k-1)对应的covarianceA’表示A的转置矩阵,Q是系统过程的covariance。式子12就是卡尔曼滤波器5个公式当中的前两个,也就是对系统的预测。

 

现在我们有了现在状态的预测结果,然后我们再收集现在状态的测量值。结合预测值和测量值,我们可以得到现在状态(k)的最优化估算值X(k|k)

X(k|k)= X(k|k-1)+Kg(k) (Z(k)-H X(k|k-1)) ……… (3)

其中Kg为卡尔曼增益(Kalman Gain)

Kg(k)= P(k|k-1) H / (H P(k|k-1) H + R) ……… (4)

 

到现在为止,我们已经得到了k状态下最优的估算值X(k|k)。但是为了要另卡尔曼滤波器不断的运行下去直到系统过程结束,我们还要更新k状态下X(k|k)covariance

P(k|k)=I-Kg(k) HP(k|k-1) ……… (5)

其中I 1的矩阵,对于单模型单测量,I=1。当系统进入k+1状态时,P(k|k)就是式子(2)P(k-1|k-1)。这样,算法就可以自回归的运算下去。

 

卡尔曼滤波器的原理基本描述了,式子12345就是他的5 个基本公式。根据这5个公式,可以很容易的实现计算机的程序

 

 

4月4日

OpenCV学习笔记(四)运动物体跟踪的camshift算法

CamShift算法
简介
CamShift算法,即"Continuously Apative Mean-Shift"算法,是一种运动跟踪算法。它主要通过视频图像中运动物体的颜色信息来达到跟踪的目的。我把这个算法分解成三个部分,便于理解:

Back Projection计算。

Mean Shift算法

CamShift算法

1 Back Projection计算
计算Back Projection的步骤是这样的:

1. 计算被跟踪目标的色彩直方图。在各种色彩空间中,只有HSI空间(或与HSI类似的色彩空间)中的H分量可以表示颜色信息。所以在具体的计算过程中,首先将其他的色彩空间的值转化到HSI空间,然后会其中的H分量做1D直方图计算。

2. 根据获得的色彩直方图将原始图像转化成色彩概率分布图像,这个过程就被称作"Back Projection"。

在OpenCV中的直方图函数中,包含Back Projection的函数,函数原型是:

   void cvCalcBackProject(IplImage** img, CvArr** backproject, const CvHistogram* hist);

传递给这个函数的参数有三个:

1. IplImage** img:存放原始图像,输入。

2. CvArr** backproject:存放Back Projection结果,输出。

3. CvHistogram* hist:存放直方图,输入

 

下面就给出计算Back Projection的OpenCV代码。

1.准备一张只包含被跟踪目标的图片,将色彩空间转化到HSI空间,获得其中的H分量:

  IplImage* target=cvLoadImage("target.bmp",-1);  //装载图片

  IplImage* target_hsv=cvCreateImage( cvGetSize(target), IPL_DEPTH_8U, 3 );

  IplImage* target_hue=cvCreateImage( cvGetSize(target), IPL_DEPTH_8U, 3 );

  cvCvtColor(target,target_hsv,CV_BGR2HSV);       //转化到HSV空间

  cvSplit( target_hsv, target_hue, NULL, NULL, NULL );    //获得H分量

2.计算H分量的直方图,即1D直方图:

  IplImage* h_plane=cvCreateImage( cvGetSize(target_hsv),IPL_DEPTH_8U,1 );

  int hist_size[]={255};          //将H分量的值量化到[0,255]

  float* ranges[]={ {0,360} };    //H分量的取值范围是[0,360)

  CvHistogram* hist=cvCreateHist(1, hist_size, ranges, 1);

  cvCalcHist(&target_hue, hist, 0, NULL);

在这里需要考虑H分量的取值范围的问题,H分量的取值范围是[0,360),这个取值范围的值不能用一个byte来表示,为了能用一个byte表示,需要将H值做适当的量化处理,在这里我们将H分量的范围量化到[0,255].

4.计算Back Projection:

  IplImage* rawImage;

  //----------------------------------------------

  //get from video frame,unsigned byte,one channel

  //----------------------------------------------

  IplImage* result=cvCreateImage(cvGetSize(rawImage),IPL_DEPTH_8U,1);

  cvCalcBackProject(&rawImage,result,hist);

5.结果:result即为我们需要的.

2) Mean Shift算法
 

这里来到了CamShift算法,OpenCV实现的第二部分,这一次重点讨论Mean Shift算法。

在讨论Mean Shift算法之前,首先讨论在2D概率分布图像中,如何计算某个区域的重心(Mass Center)的问题,重心可以通过以下公式来计算:

1.计算区域内0阶矩

for(int i=0;i<height;i++)

  for(int j=0;j<width;j++)

     M00+=I(i,j)

2.区域内1阶矩:

for(int i=0;i<height;i++)

  for(int j=0;j<width;j++)

  {

    M10+=i*I(i,j);

    M01+=j*I(i,j);

  }

3.则Mass Center为:

Xc=M10/M00; Yc=M01/M00

接下来,讨论Mean Shift算法的具体步骤,Mean Shift算法可以分为以下4步:

1.选择窗的大小和初始位置.

2.计算此时窗口内的Mass Center.

3.调整窗口的中心到Mass Center.

4.重复2和3,直到窗口中心"会聚",即每次窗口移动的距离小于一定的阈值。

 

在OpenCV中,提供Mean Shift算法的函数,函数的原型是:

int cvMeanShift(IplImage* imgprob,CvRect windowIn,

                    CvTermCriteria criteria,CvConnectedComp* out);

 

需要的参数为:

1.IplImage* imgprob:2D概率分布图像,传入;

2.CvRect windowIn:初始的窗口,传入;

3.CvTermCriteria criteria:停止迭代的标准,传入;

4.CvConnectedComp* out:查询结果,传出。

(注:构造CvTermCriteria变量需要三个参数,一个是类型,另一个是迭代的最大次数,最后一个表示特定的阈值。例如可以这样构造criteria:criteria=cvTermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS,10,0.1)。)

 

返回的参数:

1.int:迭代的次数。

 

实现代码:暂时缺

3) CamShift算法
1.原理

在了解了MeanShift算法以后,我们将MeanShift算法扩展到连续图像序列(一般都是指视频图像序列),这样就形成了CamShift算法。CamShift算法的全称是"Continuously Apaptive Mean-SHIFT",它的基本思想是视频图像的所有帧作MeanShift运算,并将上一帧的结果(即Search Window的中心和大小)作为下一帧MeanShift算法的Search Window的初始值,如此迭代下去,就可以实现对目标的跟踪。整个算法的具体步骤分5步:

Step 1:将整个图像设为搜寻区域。

Step 2:初始话Search Window的大小和位置。

Step 3:计算Search Window内的彩色概率分布,此区域的大小比Search Window要稍微大一点。

Step 4:运行MeanShift。获得Search Window新的位置和大小。

Step 5:在下一帧视频图像中,用Step 3获得的值初始化Search Window的位置和大小。跳转到Step 3继续运行。

2.实现

在OpenCV中,有实现CamShift算法的函数,此函数的原型是:

  cvCamShift(IplImage* imgprob, CvRect windowIn,

                CvTermCriteria criteria,

                CvConnectedComp* out, CvBox2D* box=0);

其中:

   imgprob:色彩概率分布图像。

   windowIn:Search Window的初始值。

   Criteria:用来判断搜寻是否停止的一个标准。

   out:保存运算结果,包括新的Search Window的位置和面积。

   box:包含被跟踪物体的最小矩形。

 

说明:

1.在OpenCV 4.0 beta的目录中,有CamShift的例子。遗憾的是这个例子目标的跟踪是半自动的,即需要人手工选定一个目标。我正在努力尝试全自动的目标跟踪,希望可以和大家能在这方面与大家交流。

 

3月31日

OpenCV学习笔记(三)人脸检测的代码分析

 
OpenCV学习笔记(三)人脸检测的代码分析
一、预备知识:
1、动态内存存储及操作函数
CvMemStorage
typedef struct CvMemStorage
{
    struct CvMemBlock* bottom;/* first allocated block */
    struct CvMemBlock* top; /* the current memory block - top of the stack */
    struct CvMemStorage* parent; /* borrows new blocks from */
    int block_size; /* block size */
    int free_space; /* free space in the top block (in bytes) */
} CvMemStorage;
内存存储器是一个可用来存储诸如序列,轮廓,图形,子划分等动态增长数据结构的底层结构。它是由一系列以同等大小的内存块构成,呈列表型 ---bottom 域指的是列首,top 域指的是当前指向的块但未必是列尾.在bottom和top之间所有的块(包括bottom, 不包括top)被完全占据了空间;在 top和列尾之间所有的块(包括块尾,不包括top)则是空的;而top块本身则被占据了部分空间 -- free_space 指的是top块剩余的空字节数。新分配的内存缓冲区(或显示的通过 cvMemStorageAlloc 函数分配,或隐示的通过 cvSeqPush, cvGraphAddEdge等高级函数分配)总是起始于当前块(即top块)的剩余那部分,如果剩余那部分能满足要求(够分配的大小)。分配后,free_space 就减少了新分配的那部分内存大小,外加一些用来保存适当列型的附加大小。当top块的剩余空间无法满足被分配的块(缓冲区)大小时,top块的下一个存储块被置为当前块(新的top块) --  free_space 被置为先前分配的整个块的大小。如果已经不存在空的存储块(即:top块已是列尾),则必须再分配一个新的块(或从parent那继承,见 cvCreateChildMemStorage)并将该块加到列尾上去。于是,存储器(memory storage)就如同栈(Stack)那样, bottom指向栈底,(top, free_space)对指向栈顶。栈顶可通过 cvSaveMemStoragePos保存,通过 cvRestoreMemStoragePos 恢复指向, 通过 cvClearStorage 重置。
CvMemBlock
内存存储块结构
typedef struct CvMemBlock
{
    struct CvMemBlock* prev;
    struct CvMemBlock* next;
} CvMemBlock;
CvMemBlock 代表一个单独的内存存储块结构。 内存存储块中的实际数据存储在 header块 之后(即:存在一个头指针 head 指向的块 header ,该块不存储数据),于是,内存块的第 i 个字节可以通过表达式 ((char*)(mem_block_ptr+1))[i] 获得。然而,通常没必要直接去获得存储结构的域。
CvMemStoragePos
内存存储块地址
typedef struct CvMemStoragePos
{
    CvMemBlock* top;
    int free_space;
} CvMemStoragePos;
该结构(如以下所说)保存栈顶的地址,栈顶可以通过 cvSaveMemStoragePos 保存,也可以通过 cvRestoreMemStoragePos 恢复。
________________________________________
cvCreateMemStorage
创建内存块
CvMemStorage* cvCreateMemStorage( int block_size=0 );
 block_size:存储块的大小以字节表示。如果大小是 0 byte, 则将该块设置成默认值  当前默认大小为64k.
函数 cvCreateMemStorage 创建一内存块并返回指向块首的指针。起初,存储块是空的。头部(即:header)的所有域值都为 0,除了 block_size 外.
________________________________________
cvCreateChildMemStorage
创建子内存块
CvMemStorage* cvCreateChildMemStorage( CvMemStorage* parent );
parent    父内存块
函数 cvCreateChildMemStorage 创建一类似于普通内存块的子内存块,除了内存分配/释放机制不同外。当一个子存储块需要一个新的块加入时,它就试图从parent 那得到这样一个块。如果 parent 中 还未被占据空间的那些块中的第一个块是可获得的,就获取第一个块(依此类推),再将该块从 parent  那里去除。如果不存在这样的块,则 parent 要么分配一个,要么从它自己 parent (即:parent 的 parent) 那借个过来。换句话说,完全有可能形成一个链或更为复杂的结构,其中的内存存储块互为 child/ parent 关系(父子关系)。当子存储结构被释放或清除,它就把所有的块还给各自的 parent. 在其他方面,子存储结构同普通存储结构一样。
子存储结构在下列情况中是非常有用的。想象一下,如果用户需要处理存储在某个块中的动态数据,再将处理的结果存放在该块中。在使用了最简单的方法处理后,临时数据作为输入和输出数据被存放在了同一个存储块中,于是该存储块看上去就类似下面处理后的样子: Dynamic data processing without using child storage. 结果,在存储块中,出现了垃圾(临时数据)。然而,如果在开始处理数据前就先建立一个子存储块,将临时数据写入子存储块中并在最后释放子存储块,那么最终在 源/目的存储块 (source / destination storage) 中就不会出现垃圾, 于是该存储块看上去应该是如下形式:Dynamic data processing using a child storage.
cvReleaseMemStorage
释放内存块
void cvReleaseMemStorage( CvMemStorage** storage );
storage: 指向被释放了的存储块的指针
函数 cvReleaseMemStorage 释放所有的存储(内存)块 或者 将它们返回给各自的 parent(如果需要的话)。 接下来再释放 header块(即:释放头指针 head 指向的块 = free(head))并清除指向该块的指针(即:head = NULL)。在释放作为 parent 的块之前,先清除各自的 child 块。
cvClearMemStorage
清空内存存储块
void cvClearMemStorage( CvMemStorage* storage );
storage:存储存储块
函数 cvClearMemStorage 将存储块的 top 置到存储块的头部(注:清空存储块中的存储内容)。该函数并不释放内存(仅清空内存)。假使该内存块有一个父内存块(即:存在一内存块与其有父子关系),则函数就将所有的块返回给其 parent.
cvMemStorageAlloc
在存储块中分配以内存缓冲区
void* cvMemStorageAlloc( CvMemStorage* storage, size_t size );
storage:内存块.
size:缓冲区的大小.
函数 cvMemStorageAlloc 在存储块中分配一内存缓冲区。该缓冲区的大小不能超过内存块的大小,否则就会导致运行时错误。缓冲区的地址被调整为CV_STRUCT_ALIGN 字节 (当前为 sizeof(double)).
cvMemStorageAllocString
在存储块中分配一文本字符串
typedef struct CvString
{
    int len;
    char* ptr;
}
CvString;
CvString cvMemStorageAllocString( CvMemStorage* storage, const char* ptr, int len=-1 );
storage:存储块
ptr:字符串
len:字符串的长度(不计算'/0')。如果参数为负数,函数就计算该字符串的长度。
函数 cvMemStorageAlloString 在存储块中创建了一字符串的拷贝。它返回一结构,该结构包含字符串的长度(该长度或通过用户传递,或通过计算得到)和指向被拷贝了的字符串的指针。
cvSaveMemStoragePos
保存内存块的位置(地址)
void cvSaveMemStoragePos( const CvMemStorage* storage, CvMemStoragePos* pos );
storage:内存块.
pos:内存块顶部位置。
函数 cvSaveMemStoragePos 将存储块的当前位置保存到参数 pos 中。 函数 cvRestoreMemStoragePos 可进一步获取该位置(地址)。
cvRestoreMemStoragePos
恢复内存存储块的位置
void cvRestoreMemStoragePos( CvMemStorage* storage, CvMemStoragePos* pos );
storage:内存块.
pos:新的存储块的位置
函数 cvRestoreMemStoragePos 通过参数 pos 恢复内存块的位置。该函数和函数 cvClearMemStorage 是释放被占用内存块的唯一方法。注意:没有什么方法可去释放存储块中被占用的部分内存。
2、分类器结构及操作函数:
CvHaarFeature
#define CV_HAAR_FEATURE_MAX  3
typedef struct CvHaarFeature
{
    int  tilted; 
    struct
    {
        CvRect r;
        float weight;
} rect[CV_HAAR_FEATURE_MAX];
 
}
CvHaarFeature;
一个 harr 特征由 2-3 个具有相应权重的矩形组成
titled :/* 0 means up-right feature, 1 means 45--rotated feature */
rect[CV_HAAR_FEATURE_MAX];  /* 2-3 rectangles with weights of opposite signs and       with absolute values inversely proportional to the areas of the rectangles. if rect[2].weight !=0, then  the feature consists of 3 rectangles, otherwise it consists of 2 */
CvHaarClassifier
typedef struct CvHaarClassifier
{
    int count; 
    CvHaarFeature* haar_feature;
    float* threshold;
    int* left;
    int* right;
    float* alpha;
}
CvHaarClassifier;
/* a single tree classifier (stump in the simplest case) that returns the response for the feature   at the particular image location (i.e. pixel sum over subrectangles of the window) and gives out a value depending on the responce */
int count;  /* number of nodes in the decision tree */
 /* these are "parallel" arrays. Every index i corresponds to a node of the decision tree (root has 0-th index).
left[i] - index of the left child (or negated index if the left child is a leaf)
right[i] - index of the right child (or negated index if the right child is a leaf)
threshold[i] - branch threshold. if feature responce is <= threshold, left branch                      is chosen, otherwise right branch is chosed.
alpha[i] - output value correponding to the leaf. */
CvHaarStageClassifier
typedef struct CvHaarStageClassifier
{
    int  count;  /* number of classifiers in the battery */
    float threshold; /* threshold for the boosted classifier */
    CvHaarClassifier* classifier; /* array of classifiers */
    /* these fields are used for organizing trees of stage classifiers,
       rather than just stright cascades */
    int next;
    int child;
    int parent;
}
CvHaarStageClassifier;

/* a boosted battery of classifiers(=stage classifier): the stage classifier returns 1 if the sum of the classifiers' responces is greater than threshold and 0 otherwise */
int  count;  /* number of classifiers in the battery */
float threshold; /* threshold for the boosted classifier */
CvHaarClassifier* classifier; /* array of classifiers */
/* these fields are used for organizing trees of stage classifiers, rather than just stright cascades */
CvHaarClassifierCascade
typedef struct CvHidHaarClassifierCascade CvHidHaarClassifierCascade;
typedef struct CvHaarClassifierCascade
{
    int  flags;
    int  count;
    CvSize orig_window_size;
    CvSize real_window_size;
    double scale;
    CvHaarStageClassifier* stage_classifier;
    CvHidHaarClassifierCascade* hid_cascade;
}
CvHaarClassifierCascade;

/* cascade or tree of stage classifiers */
int  flags; /* signature */
int  count; /* number of stages */
CvSize orig_window_size; /* original object size (the cascade is trained for) */
/* these two parameters are set by cvSetImagesForHaarClassifierCascade */
CvSize real_window_size; /* current object size */
double scale; /* current scale */
CvHaarStageClassifier* stage_classifier; /* array of stage classifiers */
CvHidHaarClassifierCascade* hid_cascade; /* hidden optimized representation of the cascade, created by cvSetImagesForHaarClassifierCascade */
所有的结构都代表一个级联boosted Haar分类器。级联有下面的等级结构:
    Cascade:
        Stage1:
            Classifier11:
                Feature11
            Classifier12:
                Feature12
            ...
        Stage2:
            Classifier21:
                Feature21
            ...
        ...
整个等级可以手工构建,也可以利用函数cvLoadHaarClassifierCascade从已有的磁盘文件或嵌入式基中导入。
特征检测用到的函数:
cvLoadHaarClassifierCascade
从文件中装载训练好的级联分类器或者从OpenCV中嵌入的分类器数据库中导入
CvHaarClassifierCascade* cvLoadHaarClassifierCascade(
                         const char* directory,
                         CvSize orig_window_size );
directory :训练好的级联分类器的路径
orig_window_size:级联分类器训练中采用的检测目标的尺寸。因为这个信息没有在级联分类器中存储,所有要单独指出。
函数 cvLoadHaarClassifierCascade 用于从文件中装载训练好的利用海尔特征的级联分类器,或者从OpenCV中嵌入的分类器数据库中导入。分类器的训练可以应用函数haartraining(详细察看opencv/apps/haartraining)
函数 已经过时了。现在的目标检测分类器通常存储在  XML 或 YAML 文件中,而不是通过路径导入。从文件中导入分类器,可以使用函数 cvLoad 。
cvReleaseHaarClassifierCascade
释放haar classifier cascade。
void cvReleaseHaarClassifierCascade( CvHaarClassifierCascade** cascade );
cascade :双指针类型指针指向要释放的cascade. 指针由函数声明。
函数 cvReleaseHaarClassifierCascade 释放cascade的动态内存,其中cascade的动态内存或者是手工创建,或者通过函数 cvLoadHaarClassifierCascade 或 cvLoad分配。
cvHaarDetectObjects
检测图像中的目标
typedef struct CvAvgComp
{
CvRect rect; /* bounding rectangle for the object (average rectangle of a group) */
int neighbors; /* number of neighbor rectangles in the group */
}
CvAvgComp;
CvSeq* cvHaarDetectObjects( const CvArr* image,
CvHaarClassifierCascade* cascade,
                            CvMemStorage* storage,
                            double scale_factor=1.1,
                            int min_neighbors=3, int flags=0,
                            CvSize min_size=cvSize(0,0) );
image 被检图像
cascade harr 分类器级联的内部标识形式
storage 用来存储检测到的一序列候选目标矩形框的内存区域。
scale_factor 在前后两次相继的扫描中,搜索窗口的比例系数。例如1.1指将搜索窗口依次扩大10%。
min_neighbors 构成检测目标的相邻矩形的最小个数(缺省-1)。如果组成检测目标的小矩形的个数和小于min_neighbors-1 都会被排除。如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框,这种设定值一般用在用户自定义对检测结果的组合程序上。
flags 操作方式。当前唯一可以定义的操作方式是 CV_HAAR_DO_CANNY_PRUNING。如果被设定,函数利用Canny边缘检测器来排除一些边缘很少或者很多的图像区域,因为这样的区域一般不含被检目标。人脸检测中通过设定阈值使用了这种方法,并因此提高了检测速度。 
min_size 检测窗口的最小尺寸。缺省的情况下被设为分类器训练时采用的样本尺寸(人脸检测中缺省大小是~20×20)。
函数 cvHaarDetectObjects 使用针对某目标物体训练的级联分类器在图像中找到包含目标物体的矩形区域,并且将这些区域作为一序列的矩形框返回。函数以不同比例大小的扫描窗口对图像进行几次搜索(察看cvSetImagesForHaarClassifierCascade)。 每次都要对图像中的这些重叠区域利用cvRunHaarClassifierCascade进行检测。 有时候也会利用某些继承(heuristics)技术以减少分析的候选区域,例如利用 Canny 裁减 (prunning)方法。 函数在处理和收集到候选的方框(全部通过级联分类器各层的区域)之后,接着对这些区域进行组合并且返回一系列各个足够大的组合中的平均矩形。调节程序中的缺省参数(scale_factor=1.1, min_neighbors=3, flags=0)用于对目标进行更精确同时也是耗时较长的进一步检测。为了能对视频图像进行更快的实时检测,参数设置通常是:scale_factor=1.2, min_neighbors=2, flags=CV_HAAR_DO_CANNY_PRUNING, min_size=<minimum possible face size> (例如, 对于视频会议的图像区域).
cvSetImagesForHaarClassifierCascade
为隐藏的cascade(hidden cascade)指定图像
void cvSetImagesForHaarClassifierCascade( CvHaarClassifierCascade* cascade,
                                          const CvArr* sum, const CvArr* sqsum,
                                          const CvArr* tilted_sum, double scale );
cascade 隐藏 Harr 分类器级联 (Hidden Haar classifier cascade), 由函数 cvCreateHidHaarClassifierCascade生成
sum 32-比特,单通道图像的积分图像(Integral (sum) 单通道 image of 32-比特 integer format). 这幅图像以及随后的两幅用于对快速特征的评价和亮度/对比度的归一化。 它们都可以利用函数 cvIntegral从8-比特或浮点数 单通道的输入图像中得到。
sqsum 单通道64比特图像的平方和图像
tilted_sum 单通道32比特整数格式的图像的倾斜和(Tilted sum)
scale cascade的窗口比例. 如果 scale=1, 就只用原始窗口尺寸检测 (只检测同样尺寸大小的目标物体) - 原始窗口尺寸在函数cvLoadHaarClassifierCascade中定义 (在 "<default_face_cascade>"中缺省为24x24), 如果scale=2, 使用的窗口是上面的两倍 (在face cascade中缺省值是48x48 )。 这样尽管可以将检测速度提高四倍,但同时尺寸小于48x48的人脸将不能被检测到。
函数 cvSetImagesForHaarClassifierCascade 为hidden classifier cascade 指定图像 and/or 窗口比例系数。 如果图像指针为空,会继续使用原来的图像(i.e. NULLs 意味这"不改变图像")。比例系数没有 "protection" 值,但是原来的值可以通过函数 cvGetHaarClassifierCascadeScale 重新得到并使用。这个函数用于对特定图像中检测特定目标尺寸的cascade分类器的设定。函数通过cvHaarDetectObjects进行内部调用,但当需要在更低一层的函数cvRunHaarClassifierCascade中使用的时候,用户也可以自行调用。
cvRunHaarClassifierCascade
在给定位置的图像中运行 cascade of boosted classifier
int cvRunHaarClassifierCascade( CvHaarClassifierCascade* cascade,
                                CvPoint pt, int start_stage=0 );
cascade Haar 级联分类器
pt 待检测区域的左上角坐标。待检测区域大小为原始窗口尺寸乘以当前设定的比例系数。当前窗口尺寸可以通过cvGetHaarClassifierCascadeWindowSize重新得到。
start_stage 级联层的初始下标值(从0开始计数)。函数假定前面所有每层的分类器都已通过。这个特征通过函数cvHaarDetectObjects内部调用,用于更好的处理器高速缓冲存储器。
函数 cvRunHaarHaarClassifierCascade 用于对单幅图片的检测。在函数调用前首先利用 cvSetImagesForHaarClassifierCascade设定积分图和合适的比例系数 (=> 窗口尺寸)。当分析的矩形框全部通过级联分类器每一层的时返回正值(这是一个候选目标),否则返回0或负值。
二、例程分析:
例子:利用级联的Haar classifiers寻找检测目标(e.g. faces).
#include "cv.h"
#include "highgui.h"
//读取训练好的分类器。
CvHaarClassifierCascade* load_object_detector( const char* cascade_path )
{
    return (CvHaarClassifierCascade*)cvLoad( cascade_path );
}

void detect_and_draw_objects( IplImage* image,
                              CvHaarClassifierCascade* cascade,
                              int do_pyramids )
{
    IplImage* small_image = image;
    CvMemStorage* storage = cvCreateMemStorage(0); //创建动态内存
    CvSeq* faces;
    int i, scale = 1;
    /* if the flag is specified, down-scale the 输入图像 to get a
       performance boost w/o loosing quality (perhaps) */
    if( do_pyramids )
    {
        small_image = cvCreateImage( cvSize(image->width/2,image->height/2), IPL_DEPTH_8U, 3 );
        cvPyrDown( image, small_image, CV_GAUSSIAN_5x5 );//函数 cvPyrDown 使用 Gaussian 金字塔分解对输入图像向下采样。首先它对输入图像用指定滤波器进行卷积,然后通过拒绝偶数的行与列来下采样图像。
        scale = 2;
    }
    /* use the fastest variant */
    faces = cvHaarDetectObjects( small_image, cascade, storage, 1.2, 2, CV_HAAR_DO_CANNY_PRUNING );
    /* draw all the rectangles */
    for( i = 0; i < faces->total; i++ )
    {
        /* extract the rectanlges only */
        CvRect face_rect = *(CvRect*)cvGetSeqElem( faces, i, 0 );
        cvRectangle( image, cvPoint(face_rect.x*scale,face_rect.y*scale),
                     cvPoint((face_rect.x+face_rect.width)*scale,
                             (face_rect.y+face_rect.height)*scale),
                     CV_RGB(255,0,0), 3 );
    }
    if( small_image != image )
        cvReleaseImage( &small_image );
    cvReleaseMemStorage( &storage );  //释放动态内存
}
/* takes image filename and cascade path from the command line */
int main( int argc, char** argv )
{
    IplImage* image;
    if( argc==3 && (image = cvLoadImage( argv[1], 1 )) != 0 )
    {
        CvHaarClassifierCascade* cascade = load_object_detector(argv[2]);
        detect_and_draw_objects( image, cascade, 1 );
        cvNamedWindow( "test", 0 );
        cvShowImage( "test", image );
        cvWaitKey(0);
        cvReleaseHaarClassifierCascade( &cascade );
        cvReleaseImage( &image );
    }
    return 0;
}
关键代码很简单,装载分类器,对输入图像进行金字塔采样,然后用cv的函数进行检测目标,最后输出检测到的目标矩形。

OpenCV学习笔记(二)基于Haar-like特征的层叠推进分类器快速目标检测


OpenCV学习笔记之二――基于Haar-like特征的层叠推进分类器快速目标检测
一、简介
目标检测方法最初由Paul Viola [Viola01]提出,并由Rainer Lienhart [Lienhart02]对这一方法进行了改善。该方法的基本步骤为: 首先,利用样本(大约几百幅样本图片)的 harr 特征进行分类器训练,得到一个级联的boosted分类器。
分类器中的"级联"是指最终的分类器是由几个简单分类器级联组成。在图像检测中,被检窗口依次通过每一级分类器, 这样在前面几层的检测中大部分的候选区域就被排除了,全部通过每一级分类器检测的区域即为目标区域。
分类器训练完以后,就可以应用于输入图像中的感兴趣区域(与训练样本相同的尺寸)的检测。检测到目标区域(汽车或人脸)分类器输出为1,否则输出为0。为了检测整副图像,可以在图像中移动搜索窗口,检测每一个位置来确定可能的目标。 为了搜索不同大小的目标物体,分类器被设计为可以进行尺寸改变,这样比改变待检图像的尺寸大小更为有效。所以,为了在图像中检测未知大小的目标物体,扫描程序通常需要用不同比例大小的搜索窗口对图片进行几次扫描。
目前支持这种分类器的boosting技术有四种: Discrete Adaboost, Real Adaboost, Gentle Adaboost and Logitboost。
"boosted" 即指级联分类器的每一层都可以从中选取一个boosting算法(权重投票),并利用基础分类器的自我训练得到。
根据上面的分析,目标检测分为三个步骤:
1、 样本的创建
2、 训练分类器
3、 利用训练好的分类器进行目标检测。
二、样本创建
训练样本分为正例样本和反例样本,其中正例样本是指待检目标样本(例如人脸或汽车等),反例样本指其它任意图片,所有的样本图片都被归一化为同样的尺寸大小(例如,20x20)。
负样本
负样本可以来自于任意的图片,但这些图片不能包含目标特征。负样本由背景描述文件来描述。背景描述文件是一个文本文件,每一行包含了一个负样本图片的文件名(基于描述文件的相对路径)。该文件必须手工创建。
e.g: 负样本描述文件的一个例子:
假定目录结构如下:
/img
img1.jpg
img2.jpg
bg.txt
则背景描述文件bg.txt的内容为:
img/img1.jpg
img/img2.jpg
正样本
正样本由程序craatesample程序来创建。该程序的源代码由OpenCV给出,并且在bin目录下包含了这个可执行的程序。
正样本可以由单个的目标图片或者一系列的事先标记好的图片来创建。
Createsamples程序的命令行参数:
命令行参数:
-vec <vec_file_name>
训练好的正样本的输出文件名。
-img<image_file_name>
源目标图片(例如:一个公司图标)
-bg<background_file_name>
背景描述文件。
-num<number_of_samples>
要产生的正样本的数量,和正样本图片数目相同。
-bgcolor<background_color>
背景色(假定当前图片为灰度图)。背景色制定了透明色。对于压缩图片,颜色方差量由bgthresh参数来指定。则在bgcolor-bgthresh和bgcolor+bgthresh中间的像素被认为是透明的。
-bgthresh<background_color_threshold>
-inv
如果指定,颜色会反色
-randinv
如果指定,颜色会任意反色
-maxidev<max_intensity_deviation>
背景色最大的偏离度。
-maxangel<max_x_rotation_angle>
-maxangle<max_y_rotation_angle>,
-maxzangle<max_x_rotation_angle>
最大旋转角度,以弧度为单位。
-show
如果指定,每个样本会被显示出来,按下"esc"会关闭这一开关,即不显示样本图片,而创建过程继续。这是个有用的debug选项。
-w<sample_width>
输出样本的宽度(以像素为单位)
-h《sample_height》
输出样本的高度,以像素为单位。
注:正样本也可以从一个预先标记好的图像集合中获取。这个集合由一个文本文件来描述,类似于背景描述文件。每一个文本行对应一个图片。每行的第一个元素是图片文件名,第二个元素是对象实体的个数。后面紧跟着的是与之匹配的矩形框(x, y, 宽度,高度)。
下面是一个创建样本的例子:
假定我们要进行人脸的检测,有5个正样本图片文件img1.bmp,…img5.bmp;有2个背景图片文件:bg1.bmp,bg2.bmp,文件目录结构如下:
positive
  img1.bmp
  ……
  Img5.bmp
negative
  bg1.bmp
  bg2.bmp
info.dat
bg.txt
正样本描述文件info.dat的内容如下:
Positive/imag1.bmp 1 0 0 24 28
……
Positive/imag5.bmp 1 0 0 24 28
图片img1.bmp包含了单个目标对象实体,矩形为(0,0,24,28)。
注意:要从图片集中创建正样本,要用-info参数而不是用-img参数。
-info <collect_file_name>
标记特征的图片集合的描述文件。
背景(负样本)描述文件的内容如下:
nagative/bg1.bmp
nagative/bg2.bmp
我们用一个批处理文件run.bat来进行正样本的创建:该文件的内容如下:
cd  e:/face/bin
CreateSamples   -vec e:/face/a.vec
 -info e:/face/info.dat
-bg e:/face/bg.txt
-num 5
-show
-w 24
 -h 28
其中e:/face/bin目录包含了createsamples可执行程序,生成的正样本文件a.vec在e:/face目录下。
三、训练分类器
样本创建之后,接下来要训练分类器,这个过程是由haartraining程序来实现的。该程序源码由OpenCV自带,且可执行程序在OpenCV安装目录的bin目录下。
Haartraining的命令行参数如下:
-data<dir_name>
存放训练好的分类器的路径名。
-vec<vec_file_name>
正样本文件名(由trainingssamples程序或者由其他的方法创建的)
-bg<background_file_name>
背景描述文件。
-npos<number_of_positive_samples>,
-nneg<number_of_negative_samples>
用来训练每一个分类器阶段的正/负样本。合理的值是:nPos = 7000;nNeg = 3000
-nstages<number_of_stages>
训练的阶段数。
-nsplits<number_of_splits>
决定用于阶段分类器的弱分类器。如果1,则一个简单的stump classifier被使用。如果是2或者更多,则带有number_of_splits个内部节点的CART分类器被使用。
-mem<memory_in_MB>
预先计算的以MB为单位的可用内存。内存越大则训练的速度越快。
-sym(default)
-nonsym
指定训练的目标对象是否垂直对称。垂直对称提高目标的训练速度。例如,正面部是垂直对称的。
-minhitrate《min_hit_rate》
每个阶段分类器需要的最小的命中率。总的命中率为min_hit_rate的number_of_stages次方。
-maxfalsealarm<max_false_alarm_rate>
没有阶段分类器的最大错误报警率。总的错误警告率为max_false_alarm_rate的number_of_stages次方。
-weighttrimming<weight_trimming>
指定是否使用权修正和使用多大的权修正。一个基本的选择是0.9
-eqw
-mode<basic(default)|core|all>
选择用来训练的haar特征集的种类。basic仅仅使用垂直特征。all使用垂直和45度角旋转特征。
-w《sample_width》
-h《sample_height》
训练样本的尺寸,(以像素为单位)。必须和训练样本创建的尺寸相同。
一个训练分类器的例子:
同上例,分类器训练的过程用一个批处理文件run2.bat来完成:
cd e:/face/bin
haartraining -data e:/face/data
-vec e:/face/a.vec
-bg e:/face/bg.txt
-npos 5
-nneg 2
 -w 24
 -h 28
训练结束后,会在目录data下生成一些子目录,即为训练好的分类器。
注:OpenCv的某些版本可以将这些目录中的分类器直接转换成xml文件。但在实际的操作中,haartraining程序却好像永远不会停止,而且没有生成xml文件,后来在OpenCV的yahoo论坛上找到一个haarconv的程序,才将分类器转换为xml文件,其中的原因尚待研究。
四、目标检测
OpenCV的cvHaarDetectObjects()函数(在haarFaceDetect演示程序中示例)被用来做侦测。关于该检测的详细分析,将在下面的笔记中详细描述。

 
3月29日

一个合格的程序员该做的事情


2006.03.15  来自:CSDN 选自Mailbomb 的blog  
 

  程序员每天该做的事

1、总结自己一天任务的完成情况

最好的方式是写工作日志,把自己今天完成了什么事情,遇见了什么问题都记录下来,日后翻看好处多多

2、考虑自己明天应该做的主要工作

把明天要做的事情列出来,并按照优先级排列,第二天应该把自己效率最高的时间分配给最重要的工作

3、考虑自己一天工作中失误的地方,并想出避免下一次再犯的方法

出错不要紧,最重要的是不要重复犯相同的错误,那是愚蠢

4、考虑自己一天工作完成的质量和效率能否还能提高

一天只提高1%,365天你的效率就能提高多少倍你知道吗? (1+0.01)^365 = 37 倍

5、看一个有用的新闻网站或读一张有用的报纸,了解业界动态

闭门造车是不行的,了解一下别人都在做什么,对自己能带来很多启示

6、记住一位同事的名字及其特点

你认识公司的所有同事吗?你了解他们吗?

7、清理自己的代码

今天完成的代码,把中间的调试信息,测试代码清理掉,按照编码风格整理好,注释都写好了吗?

8、清理自己的桌面

当日事当日毕,保持清洁干劲的桌面才能让你工作时不分心,程序员特别要把电脑的桌面清理干净

程序员每月该做的事

1、至少和一个同事一起吃饭或喝茶
不光了解自己工作伙伴的工作,还要了解他们的生活

2、自我考核一次
相对正式地考核自己一下,你对得起这个月的工资吗?

3、对你的同事考核一次
你的同事表现怎么样?哪些人值得学习,哪些人需要帮助?

3、制定下月的计划,确定下月的工作重点

4、总结自己工作质量改进状况
自己的质量提高了多少?

5、有针对性地对一项工作指标做深入地分析并得出改进的方案
可以是对自己的,也可以是对公司的,一定要深入地分析后拿出自己的观点来。要想在老板面前说得上话,做的成事,工作上功夫要做足。

6、与老板沟通一次
最好是面对面地沟通,好好表现一下自己,虚心听取老板的意见,更重要的是要了解老板当前关心的重点

程序员每年该做的事

1、年终总结
每个公司都会做的事情,但你真正认真地总结过自己吗?

2、兑现给自己、给家人的承诺
给老婆、儿子的新年礼物买了没有?给自己的呢?

3、下年度工作规划
好好想想自己明年的发展目标,争取升职/加薪、跳槽还是自己出来干?

4、掌握一项新技术
至少是一项,作为程序员一年要是一项新技术都学不到手,那就一定会被淘汰。
掌握可不是看本书就行的,要真正懂得应用,最好你能够写一篇教程发表到你的blog

5、推出一种新产品
可以是一个真正的产品,也可以只是一个类库,只要是你创造的东西就行,让别人使用它,也为世界作点贡献。当然如果真的很有价值,收点注册费也是应该的

6、与父母团聚一次
常回家看看,常回家看看

 

3月28日

OpenCV学习笔记(一)概述和系统配置


OpenCV学习笔记(一)概述和系统配置

一、概述
OpenCV是英特尔公司于1999年在俄罗斯设立的软件开发中心“Software Development Center”开发的。该公司一直致力于基于个人电脑的计算机视觉应用的开发,可以实时追踪的视觉用户接口技术的普及为目标。初步拟定应用于Human-Computer Interaction(HCI,人机互动)、物体确定、面孔识别、表情识别,移动物体追踪、自主运动(Ego-motion)、移动机器人等领域。因此,“OpenCV 2.1将提供给玩具制造商及机器人制造商等从事计算机视觉相关技术的各类企业/团体”
    OpenCV将以公开源码的方式提供,也就是接受方有权在修改之后另行向第三方提供。源代码(C语言)中包括有库(Library)的所有功能。详细情况刊登在OpenCV的WWW站点上。
英特尔公司解释说,“速度更高的微处理器、廉价的数码相机以及USB 2等技术使高速视频捕获(Video Capture)成为可能,因此,基于普通个人电脑的实时计算机视觉将有望实现”。
OpenCV的最新版本为beta5。
二、OpenCV组成部分
目前OpenCV包含下面几个部分:
    cvcore:一些基本函数(各种数据类型的基本运算等)
    cv:    图像处理和计算机视觉功能(图像处理,结构分析,运动分析,物体跟踪,模式识别,摄像机定标)
    cvaux:一些实验性的函数(view morphing,三维跟踪,pca,hmm)
    highgui 用户交互部分(GUI,图像视频I/O,系统调用函数)
    另外还有cvcam,不过linux版本已经抛弃。windows版本中将directX支持加入highgui后,cvcam将被彻底去掉。
三、OpenCV特点
OpenCV是Intel公司开发的图像处理和计算机视觉函数库,它有以下特点:
1) 开放C源码
2) 基于Intel处理器指令集开发的优化代码
3) 统一的结构和功能定义
4) 强大的图像和矩阵运算能力
5) 方便灵活的用户接口
6)同时支持MS-WINDOWS、LINUX平台
四、下载OpenCV
    http://www.sourceforge.net/projects/opencvlibrary
五、参考资料
    =》源代码及文档下载:SOURCEFORGE.NET
http://sourceforge.net/projects/opencvlibrary/
    =》INTEL的OPENCV主页:
http://www.intel.com/research/mrl/research/opencv/
    =》YAHOO OPENCV 的邮件列表:
http://groups.yahoo.com/group/OpenCV/
    =》CMU(卡耐基-梅隆大学)的计算机视觉主页:
http://www-2.cs.cmu.edu/afs/cs/project/cil/ftp/html/vision.html
    =》OPENCV 更为详细的介绍
http://www.assuredigit.com//incoming/sourcecode/opencv/chinese_docs/index.htm
    =》OPENCV 的常用问题与解答
http://www.assuredigit.com//incoming/sourcecode/opencv/chinese_docs/faq.htm
    =》OPENCV 的安装指南
http://www.assuredigit.com//incoming/sourcecode/opencv/chinese_docs/install
    =》更多的最新资料,请访问
http://blog.csdn.net/hunnishhttp://rocee.bokee.com
六、创建一个 DeveloperStudio 项目来开始 OpenCV
1. 在 Developer Studio 中创建新的应用程序:
选择菜单 "File"->"New..."->"Projects" . 选择 "Win32 Application" 或 "Win32 console application" - 后者是更简单的方法。
键入项目名称,并且选择存储位置
可以为项目创建一个单独的 workspace ("Create new workspace") , 也可以将新的项目加入到当前的 workspace 中 ("Add to current workspace").
单击 "next" 
选择 "An empty project", 点击 "Finish", "OK".
经过以上步骤,Developer Studio 会创建一个项目目录 (缺省情况下,目录名就是项目名), .dsp 文件以及.dsw,.ncb ... ,如果你创建自己的workspace.
2添加文件到 project 中:
选择菜单"File"->"New..."->"Files" .
选择"C++ Source File", 键入文件名,点击"OK"
增加 OpenCV 相关的 头文件目录 #include :
        #include "cv.h"
        /* #inlcude "cvaux.h" // experimental stuff (if need) */
        #include "highgui.h"
     
或者你可以拷贝部分已有的文件 (如:opencv/samples/c/morphology.c) 到项目目录中,打开它,并且加入到项目中 (右键点击编辑器的视图 -> "Insert File into Project" -> ).
3配置项目:
选择菜单"Project"->"Settings..."以激活项目配置对话框 .
在左边选择你的项目.
调节设置,对 Release 和 Debug 配置都有效:
选择 "Settings For:"->"All Configurations"
选择 "C/C++" tab -> "Preprocessor" category -> "Additional Include Directories:". 加入用逗号分隔的相对路径 (对文件 .dsp 而言) 或绝对路径
 d:/opencv/cxcore/include,d:/opencv/cv/include,d:/opencv/otherlibs/highgui, d:/opencv/cvaux/include(optionally,)
选择 "Link" tab -> "Input" category -> "Additional library path:".
加入输入库所在的路径 (cxcore[d].lib cv[d].lib hihghui[d].lib cvaux[d].lib)
d:/opencv/lib
调节 "Debug" 配置:
选择 "Settings For:"->"Win32 Debug".
选择 "Link" tab -> "General" category -> "Object/library modules". 加入空格分隔的 cvd.lib,cxcored.lib highguid.lib,cvauxd.lib (optionally)
可以改变输出文件的名称和位置。如想把产生的 .exe 文件放置于项目目录而不是Debug/ 子目录下,可在 "Link" tab -> "General" category -> "Output file name:" 中键入 ./d.exe 
调节 "Release" 配置
选择 "Settings For:"->"Win32 Release".
选择 "Link" tab -> "General" category -> "Object/library modules". 加入空格分隔的cv.lib cxcore.lib highgui.lib cvaux.lib (optionally)
4增加从属性项目到 workspace 中:
选择菜单: "Project" -> "Insert project into workspace".
选择 opencv/cv/make/cv.dsp.
同样步骤对 opencv/cvaux/make/cvaux.dsp, opencv/otherlibs/highgui/highgui.dsp.
设置从属性:
选择菜单: "Project" -> "Dependencies..."
对 "cv" 选择 "cxcore",
对 "cvaux" 选择 "cv", "cxcore",
对 "highgui" 选择 "cxcore",
对你的项目,选择所有的: "cxcore", "cv", "cvaux", "highgui".
从属性配置保证了在源代码被改变的情况下,自动重新编译 opencv 库.
5就这么多。可以编译并且运行一切了。
七、库设置:
   静态库设置:
   Opencv程序需要静态库设置,其release版本的静态库在系统的lib目录下,其debug版本的静态库需要重新全编译所有的程序。
    动态库设置:
   OPenCV启动时需要一些动态库的支持,这些动态库必须放在系统目录下或者当前目录下。
    Cv097.dll,cvaux097.dll,cvcam097.dll,cxcore097.dll highguid097.dll,libguide40.dll


 

出租司机给"我"上的MBA课

Posted on 2006-03-15 01:14 刘润
我要从徐家汇赶去机场,于是匆匆结束了一个会议,在美罗大厦前搜索出租车。一辆大众发现了我,非常专业的、径直的停在我的面前。这一停,于是有了后面的这个让我深感震撼的故事,象上了一堂生动的MBA案例课。为了忠实于这名出租车司机的原意,我凭记忆尽量重复他原来的话。
“去哪里……好的,机场。我在徐家汇就喜欢做美罗大厦的生意。这里我只做两个地方。美罗大厦,均瑶大厦。你知道吗?接到你之前,我在美罗大厦门口兜了两圈,终于被我看到你了!从写字楼里出来的,肯定去的不近~~~”
“哦?你很有方法嘛!”我附和了一下。
“做出租车司机,也要用科学的方法。”他说。我一愣,顿时很有些兴趣“什么科学的方法?”
“要懂得统计。我做过精确的计算。我说给你听啊。我每天开17个小时的车,每小时成本34.5元……”
“怎么算出来的?”我追问。
“你算啊,我每天要交380元,油费大概210元左右。一天17小时,平均每小时固定成本22元,交给公司,平均每小时12.5元油费。这是不是就是34.5元?”,我有些惊讶。我打了10年的车,第一次听到有出租车司机这么计算成本。以前的司机都和我说,每公里成本0.3元,另外每天交多少钱之类的。
“成本是不能按公里算的,只能按时间算。你看,计价器有一个“检查”功能。你可以看到一天的详细记录。我做过数据分析,每次载客之间的空驶时间平均为7分钟。如果上来一个起步价,10元,大概要开10分钟。也就是每一个10元的客人要花17分钟的成本,就是9.8元。不赚钱啊!如果说做浦东、杭州、青浦的客人是吃饭,做10元的客人连吃菜都算不上,只能算是撒了些味精。”
强!这位师傅听上去真不象出租车司机,到象是一位成本核算师。“那你怎么办呢?”我更感兴趣了,继续问。看来去机场的路上还能学到新东西。
“千万不能被客户拉了满街跑。而是通过选择停车的地点,时间,和客户,主动地决定你要去的地方。”我非常惊讶,这听上去很有意思。“有人说做出租车司机是靠运气吃饭的职业。我以为不是。你要站在客户的位置上,从客户的角度去思考。”这句话听上去很专业,有点象很多商业管理培训老师说的“put yourself into others' shoes.”
“给你举个例子,医院门口,一个拿着药的,一个拿着脸盆的,你带哪一个。”我想了想,说不知道。
“你要带那个拿脸盆的。一般人病小痛的到医院看一看,拿点药,不一定会去很远的医院。拿着脸盆打车的,那是出院的。住院哪有不死人的?今天二楼的谁死了,明天三楼又死了一个。从医院出来的人通常会有一种重获新生的感觉,重新认识生命的意义,健康才最重要。那天这个说:走,去青浦。眼睛都不眨一下。你说他会打车到人民广场,再去做青浦线吗?绝对不会!”
我不由得开始佩服。
“再给你举个例子。那天人民广场,三个人在前面招手。一个年轻女子,拿着小包,刚买完东西。还有一对青年男女,一看就是逛街的。第三个是个里面穿绒衬衫的,外面羽绒服的男子,拿着笔记本包。我看一个人只要3秒钟。我毫不犹豫地停在这个男子面前。这个男的上车后说:延安高架、南北高架~~~还没说后面就忍不住问,为什么你毫不犹豫地开到我面前?前面还有两个人,他们要是想上车,我也不好意思和他们抢。我回答说,中午的时候,还有十几分钟就1点了。那个女孩子是中午溜出来买东西的,估计公司很近;那对男女是游客,没拿什么东西,不会去很远;你是出去办事的,拿着笔记本包,一看就是公务。而且这个时候出去,估计应该不会近。那个男的就说,你说对了,去宝山。”
“那些在超市门口,地铁口打车,穿着睡衣的人可能去很远吗?可能去机场吗?机场也不会让她进啊。”
有道理!我越听越有意思。
“很多司机都抱怨,生意不好做啊,油价又涨了啊,都从别人身上找原因。我说,你永远从别人身上找原因,你永远不能提高。从自己身上找找看,问题出在哪里。”这话听起来好熟,好像是“如果你不能改变世界,就改变你自己”,或者Steven Corvey的“影响圈和关注圈”的翻版。“有一次,在南丹路一个人拦车,去田林。后来又有一次,一个人在南丹路拦车,还是去田林。我就问了,怎么你们从南丹路出来的人,很多都是去田林呢?人家说,在南丹路有一个公共汽车总站,我们都是坐公共汽车从浦东到这里,然后搭车去田林的。我恍然大悟。比如你看我们开过的这条路,没有写字楼,没有酒店,什么都没有,只有公共汽车站,站在这里拦车的多半都是刚下公共汽车的,再选择一条最短路经打车。在这里拦车的客户通常不会高于15元。”
“所以我说,态度决定一切!”我听十几个总裁讲过这句话,第一次听出租车司机这么说。
“要用科学的方法,统计学来做生意。天天等在地铁站口排队,怎么能赚到钱?每个月就赚500块钱怎么养活老婆孩子?这就是在谋杀啊!慢性谋杀你的全家。要用知识武装自己。学习知识可以把一个人变成聪明的人,一个聪明的人学习知识可以变成很聪明的人。一个很聪明的人学习知识,可以变成天才。”
“有一次一个人打车去火车站,问怎么走。他说这么这么走。我说慢,上高架,再这么这么走。他说,这就绕远了。我说,没关系,你经常走你有经验,你那么走50块,你按我的走法,等里程表50块了,我就翻表。你只给50快就好了,多的算我的。按你说的那么走要50分钟,我带你这么走只要25分钟。最后,按我的路走,多走了4公里,快了25分钟,我只收了50块。乘客很高兴,省了10元钱左右。这4公里对我来说就是1块多钱的油钱。我相当于用1元多钱买了25分钟。我刚才说了,我一小时的成本34.5块,我多合算啊!”
“在大众公司,一般一个司机3、4千,拿回家。做的好的大概5千左右。顶级的司机大概每月能有7000。全大众2万个司机,大概只有2-3个司机,万里挑一,每月能拿到8000以上。我就是这2-3个人中间的一个。而且很稳定,基本不会大的波动。”
太强了!到此为止,我越来越佩服这个出租车司机。
“我常常说我是一个快乐的车夫。有人说,你是因为赚的钱多,所以当然快乐。我对他们说,你们正好错了。是因为我有快乐、积极的心态,所以赚的钱多。”
说的多好啊!
“要懂得体味工作带给你的美。堵在人民广场的时候,很多司机抱怨,又堵车了!真是倒霉。千万不要这样,用心体会一下这个城市的美,外面有很多漂亮的女孩子经过,非常现代的高楼大厦,虽然买不起,但是却可以用欣赏的眼光去享受。开车去机场,看着两边的绿色,冬天是白色的,多美啊。再看看里程表,100多了,就更美了!每一样工作都有她美丽的地方,我们要懂得从工作中体会这种美丽。”
“我10年前是强生公司的总教练。8年前在公司作过三个不同部门的部门经理。后来我不干了,一个月就3、5千块,没意思。就主动来做司机。我愿意做一个快乐的车夫。哈哈哈哈。”
到了机场,我给他留了一张名片,说:“你有没有兴趣这个星期五,到我办公室,给微软的员工讲一讲你怎么开出租车的?你就当打着表,60公里一小时,你讲多久,我就付你多少钱。给我电话。”
我迫不及待的在飞机上记录下他这堂生动的MBA课。
 
原创粉丝点击