HOG

十二月 14, 2010 3 条评论

中文的资料貌似之后两篇,一篇是类似翻译了HOG的来源,另一篇是百度博客里面HOG的matlab代码(有助于理解paper)

给出链接

http://hi.baidu.com/ykaitao_handsome/blog/item/d7a2c3156e368a0a4b90a745.html

http://hi.baidu.com/timehandle/blog/item/ca6e3cdfab738fe376c638a8.html/index/1#comment

HOG的思路正如paper所言来源于SIFT和Shape Context,将SIFT的sparse feature应用到dense feature,同时具有了目标的表象和形状(appearance and shape)。考虑到一张64*128的人体图像,将最后生成的3780(7*15*4*9)当成一个feature来处理,为的是考虑位置的关系

Code方面,前面提到的timehandle的白的博客里面有matlab代码,很容易看懂,对理解paper很有好处,其实Dalal的博士论文写得详细,实验设计的所有参数很明确,对不同的参数组合也给了实验结果。我把timehandle的matlab代码改成了C,发现果然很慢啊,检测一张320*240的图要好久,相比之下opencv的实现就比较快了。

最近在实现IHOG+ADABOOST

后来改进算法综述:

1.  IHOG

Zhu et al. Fast Human Detection Using a Cascade of Histograms of Oriented Gradients. CVPR 2006.该文主要的贡献在于将积分图像的概念引入到HOG中,其实积分图像在01年就有了,当然从HOG开篇到IHOG隔了一年,也算不错了。IHOG的思想是考虑到HOG不能在不同的尺度上进行运算(运算量太大),但是尺度空间scale space是必须要考虑的一个问题,于是对HOG算法进行改装,对每个像素点建立N个histogram bins的integral images,论文里面没有提到差值,不过别的论文提到了,原来的高斯加权三线性插值不要了,L2规整用L1代替。然后生成不同大小的blocks,在Dalal的论文中最好的实验结果是 8*8的cell,2*2的block,Zhang采用了不同大小的blocks,然后形成一个feature vector,相当可观,原来Dalal论文里面64*128的检测窗口产生了105个blocks,Zahang产生了5031个,但是随机抽取了一些。

该算法的优点:1。不同的目标可能在不同的scale里面,blocks越多越能获取更多的information 2。这5301个block里面可能包含了人体的语义信息,例如腿啊等。毕竟Dalal的block太小,不可能包含太大结构的特征。另外一种思路来理解IHOG是将它看成是基于人体部分检测的单一window。

因为feature vector 大小不一。

2.  SS-HOG (Scale space-HOG)

指的是通过scale space尺度空间理论获取原始图像的多尺度表示,然后按照HOG来提取feature,合成最后的feature。

实验过程:INRIA数据集,测试集是Dalal提供的2416个正类,负类是随机的从1218张训练集中随机选取了4872个(每张图4个检测窗口,由于内存和matlab实现的限制)。尺度空间选取了0 2 3 4四个尺度。和原始的Dalal的算法相比,本文的SVM训练没有采用bootstrap。

3.  3D-HOG

adaboost算法示例

十一月 3, 2010 1条评论

学习一个算法最好还是有示例来的比较快,对照代码和示例,很快。之前看Viola的算法流程图,搞不清楚weak classifier 是怎么来的,原来在04年的paper上才有详述,白浪费了时间了,还好在这期间看了一个adaboost的tutorial,结合Cuneyt Mertayak的代码,果然很容易就明白了,昨天和今天看了Viola的两篇paper,加上中文的几篇,加上请教师兄,总算对Robust Real-time Face Detection 搞了个明白,准备结合程序来看。

首先就是直观的理解adaboost,理论的东西就不讨论了,就是weak classifiers 可以组合成 strong classifer。具体算法流程就是,首先所有samples的权重相同(也有根据正负类的个数不同,权重不同的调整)对每个feature,寻找这个feature对应的分类器,来分类,由于是只用了一个feature,所以分类效果不好,当然只要分类错误小于50%就OK,遍历所有的feature,找到一个分类错误最小的feature以及对应的阈值theta,对分类错误的samples加权,即这些很错的samples很重要,比其他的重要。接着对这些权重不一的samples再分类,继续上面的过程直到T次,这个T就是最后弱分类器的个数。Viola的paper里面也说了阈值和最后的detection rate以及false positive有关系,但是具体有什么关系说不清楚。
附上网上找到的Cuneyt Mertayak的adaboost的代码(就不上传了,大家可以google)以及图形化说明。

分类:Adaboost

OpenCV学习笔记

十一月 2, 2010 留下评论

http://blog.csdn.net/byxdaz/archive/2009/11/30/4909452.aspx

一、OpenCV概述与功能介绍

OpenCV是Intel®开源计算机视觉库。它由一系列 C 函数和少量 C++ 类构成,实现了图像处理和计算机视觉方面的很多通用算法。OpenCV 拥有包括 300 多个C函数的跨平台的中、高层 API。它不依赖于其它的外部库——尽管也可以使用某些外部库。

OpenCV 对非商业应用和商业应用都是免费(FREE)的。(细节参考 license)。代码下载地址:http://www.sourceforge.net/projects/opencvlibrary

OpenCV 为Intel® Integrated Performance Primitives (IPP) 提供了透明接口。 这意味着如果有为特定处理器优化的的 IPP 库, OpenCV 将在运行时自动加载这些库。 更多关于 IPP 的信息请参考: http://www.intel.com/software/products/ipp/index.htm

它有以下特点:
1) 开放的C/C++源码
2) 基于Intel处理器指令集开发的优化代码
3) 统一的结构和功能定义
4) 强大的图像和矩阵运算能力
5) 方便灵活的用户接口
6)同时支持MS-WINDOWS、LINUX平台
作为一个基本的计算机视觉、图像处理和模式识别的开源项目,OPENCV可以直接应用于很多领域,作为第二次开发的理想工具。

OpenCV功能介绍:

OpenCV包含如下几个部分:

Cxcore:一些基本函数(各种数据类型的基本运算等)。

Cv:图像处理和计算机视觉功能(图像处理,结构分析,运动分析,物体跟踪,模式识别,摄像机定标)

Ml:机器学习模块,目前内容主要为分类器。

Cvaux:一些实验性的函数(ViewMorphing,三维跟踪,PCA,HMM)

Highgui:用户交互部分,(GUI,图象视频I/O,系统调用函数)

 

二、OpenCV安装

OpenCV2.0刚刚发布,VC 2008 Express下安装OpenCV2.0请参考:

http://www.opencv.org.cn/index.php/VC_2008_Express%E4%B8%8B%E5%AE%89%E8%A3%85OpenCV2.0

 

三、基础知识:

1、opencv 数据类型转换操作小结

(1)图像中或矩阵数组中数据格式转换:
cvConvert( image, image_temp );

cvConvertScale( const CvArr* src, CvArr* dst, double scale CV_DEFAULT(1), double shift CV_DEFAULT(0) );

cvScale(src, dst);

// Converts CvArr (IplImage or CvMat,…) to CvMat.
cvGetMat( const CvArr* arr, CvMat* header, int* coi CV_DEFAULT(NULL), int allowND CV_DEFAULT(0));
cvCopy( const CvArr* src, CvArr* dst, const CvArr* mask ); //可以实现对不规制图形的提取

(2)多通道图像转成数组中数据
cvGetMat( const CvArr* array, CvMat* mat, int* pCOI, int allowND )

cvCopy(img,mat);

// Converts CvArr (IplImage or CvMat,…) to CvMat.
cvGetMat( const CvArr* arr, CvMat* header, int* coi CV_DEFAULT(NULL), int allowND CV_DEFAULT(0));

(3) 数组中数据转成多通道图像
cvCopy( const CvArr* src, CvArr* dst, const CvArr* mask=NULL );

cvGetMat( const CvArr* arr, CvMat* header, int* coi CV_DEFAULT(NULL), int allowND CV_DEFAULT(0));

 

2、二值化函数cvAdaptiveThreshold和cvThreshold的一些发现

自适应二值化计算像素的邻域的平均灰度,来决定二值化的值。如果整个区域几乎是一样灰度的,则无法给出合适的结果了。之所以看起来像边缘检测,是因为窗尺寸设置的小,可以改大一点试一试。
cvAdaptiveThreshold( src, dst, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 21); //窗设置为21
没有万能的二值化方法,具体问题具体分析,自适应二值化对于光照不均的文字,条码等,效果很好。窗口大小选择,考虑被检测物体尺寸。自适应阈值化中的阈值完全是由你所选择的邻域所确定的,如果你所选择的邻域非常小(比如3×3),那么很显然阈值的“自适应程度”就非常高,这在结果图像中就表现为边缘检测的效果。如果邻域选择的比较大(比如31×31),那么阈值的“自适应程度”就比较低,这在结果图像中就表现为二值化的效果。

 

3、用 gabor 和 AdaBoost (MultiBoost )做目标检测图像识别

http://www.opencv.org.cn/forum/viewtopic.php?f=10&t=7790

 

4、视频跟踪方法

跟踪的方法我知道的有KLMAN滤波.粒子滤波.camshift.meanshift。

基于Mean Shift的阈值分割:http://www.codesoso.com/code/mean_shift.aspx

http://arslan-ai.spaces.live.com/blog/cns!CAE7EF891A2218BA!123.entry

5、怎么访问图像像素

(坐标是从0开始的,并且是相对图像原点的位置。图像原点或者是左上角 (img->origin=IPL_ORIGIN_TL) 或者是左下角 (img->origin=IPL_ORIGIN_BL) )

假设有 8-bit 1-通道的图像 I (IplImage* img):

———————————————————————

I(x,y) ~ ((uchar*)(img->imageData + img->widthStep*y))[x]

———————————————————————

假设有 8-bit 3-通道的图像 I (IplImage* img):

———————————————————————

I(x,y)blue ~ ((uchar*)(img->imageData + img->widthStep*y))[x*3]

I(x,y)green ~ ((uchar*)(img->imageData + img->widthStep*y))[x*3+1]

I(x,y)red ~ ((uchar*)(img->imageData + img->widthStep*y))[x*3+2]

——————————————————————————

例如,给点 (100,100) 的亮度增加 30 ,那么可以这样做:

——————————————————————————

CvPoint pt = {100,100};

((uchar*)(img->imageData + img->widthStep*pt.y))[pt.x*3] += 30;

((uchar*)(img->imageData + img->widthStep*pt.y))[pt.x*3+1] += 30;

((uchar*)(img->imageData + img->widthStep*pt.y))[pt.x*3+2] += 30;

—————————————————————————–

或者更高效地:

—————————————————————————–

CvPoint pt = {100,100};

uchar* temp_ptr = &((uchar*)(img->imageData + img->widthStep*pt.y))[pt.x*3];

temp_ptr[0] += 30;

temp_ptr[1] += 30;

temp_ptr[2] += 30;

—————————————————————————–

假设有 32-bit 浮点数, 1-通道 图像 I (IplImage* img):

—————————————————————————–

I(x,y) ~ ((float*)(img->imageData + img->widthStep*y))[x]

—————————————————————————–

现在,一般的情况下,假设有 N-通道,类型为 T 的图像:

—————————————————————————–

I(x,y)c ~ ((T*)(img->imageData + img->widthStep*y))[x*N + c]

—————————————————————————–

你可以使用宏 CV_IMAGE_ELEM( image_header, elemtype, y, x_Nc )

—————————————————————————–

I(x,y)c ~ CV_IMAGE_ELEM( img, T, y, x*N + c )

—————————————————————————–

也有针对各种图像(包括 4 通道图像)和矩阵的函数(cvGet2D, cvSet2D), 但是它们非常慢。

 

6、如何访问矩阵元素?

方法是类似的(下面的例子都是针对 0 起点的列和行)

 

设有 32-bit 浮点数的实数矩阵 M (CvMat* mat):

—————————————————————————-

M(i,j) ~ ((float*)(mat->data.ptr + mat->step*i))[j]

—————————————————————————-

设有 64-bit 浮点数的复数矩阵 M (CvMat* mat):

—————————————————————————-

Re M(i,j) ~ ((double*)(mat->data.ptr + mat->step*i))[j*2]

Im M(i,j) ~ ((double*)(mat->data.ptr + mat->step*i))[j*2+1]

—————————————————————————-

对单通道矩阵,有宏 CV_MAT_ELEM( matrix, elemtype, row, col ), 例如对 32-bit

浮点数的实数矩阵:

M(i,j) ~ CV_MAT_ELEM( mat, float, i, j ),

例如,这儿是一个 3×3 单位矩阵的初始化:

CV_MAT_ELEM( mat, float, 0, 0 ) = 1.f;

CV_MAT_ELEM( mat, float, 0, 1 ) = 0.f;

CV_MAT_ELEM( mat, float, 0, 2 ) = 0.f;

CV_MAT_ELEM( mat, float, 1, 0 ) = 0.f;

CV_MAT_ELEM( mat, float, 1, 1 ) = 1.f;

CV_MAT_ELEM( mat, float, 1, 2 ) = 0.f;

CV_MAT_ELEM( mat, float, 2, 0 ) = 0.f;

CV_MAT_ELEM( mat, float, 2, 1 ) = 0.f;

CV_MAT_ELEM( mat, float, 2, 2 ) = 1.f;

 

7、如何在 OpenCV 中处理我自己的数据

设你有 300×200 32-bit 浮点数 image/array, 也就是对一个有 60000 个元素的数组。

—————————————————————————-

int cols = 300, rows = 200;

float* myarr = new float[rows*cols];

// 第一步,初始化 CvMat 头

CvMat mat = cvMat( rows, cols,

CV_32FC1, // 32 位浮点单通道类型

myarr // 用户数据指针(数据没有被复制)

);

// 第二步,使用 cv 函数, 例如计算 l2 (Frobenius) 模

double norm = cvNorm( &mat, 0, CV_L2 );

delete myarr;

其它情况在参考手册中有描述。 见 cvCreateMatHeader,cvInitMatHeader,cvCreateImageHeader, cvSetData 等

 

8、如何读入和显示图像

—————————————————————————-

/* usage: prog <image_name> */

#include “cv.h”

#include “highgui.h”

 

int main( int argc, char** argv )

{

 

IplImage* img;

if( argc == 2 && (img = cvLoadImage( argv[1], 1)) != 0 )

{

 

cvNamedWindow( “Image view”, 1 );

cvShowImage( “Image view”, img );

cvWaitKey(0); // 非常重要,内部包含事件处理循环

cvDestroyWindow( “Image view” );

cvReleaseImage( &img );

return 0;

 

}

return -1;

 

}

 

9、图像的通道

描述一个像素点,如果是灰度,那么只需要一个数值来描述它,就是单通道。 如果一个像素点,有RGB三种颜色来描述它,就是三通道。4通道通常为RGBA,在某些处理中可能会用到。 2通道图像不常见,通常在程序处理中会用到,如傅里叶变换,可能会用到,一个通道为实数,一个通道为虚数,主要是编程方便。

 

10、HBITMAP 转换IplImage、IplImage转换为DIB

// HBITMAP 转换IplImage

IplImage* hBitmap2Ipl(HBITMAP hBmp)

{

BITMAP bmp;

::GetObject(hBmp,sizeof(BITMAP),&bmp);

int nChannels = bmp.bmBitsPixel == 1 ? 1 : bmp.bmBitsPixel/8 ;

int depth = bmp.bmBitsPixel == 1 ? IPL_DEPTH_1U : IPL_DEPTH_8U;

IplImage* img = cvCreateImageHeader( cvSize(bmp.bmWidth, bmp.bmHeight)

, depth, nChannels );

img->imageData =

(char*)malloc(bmp.bmHeight*bmp.bmWidth*nChannels*sizeof(char));

memcpy(img->imageData,(char*)(bmp.bmBits),bmp.bmHeight*bmp.bmWidth*nChannels);

return img;

}

 

void createDIB(IplImage* &pict){

IplImage * Red=cvCreateImage( cvSize(IMAGE_WIDTH,IMAGE_HEIGHT),

IPL_DEPTH_8U, 1 );

IplImage * Green=cvCreateImage( cvSize(IMAGE_WIDTH,IMAGE_HEIGHT),

IPL_DEPTH_8U, 1 );

IplImage * Blue=cvCreateImage( cvSize(IMAGE_WIDTH,IMAGE_HEIGHT),

IPL_DEPTH_8U, 1 );

cvSetImageCOI( pict, 3);

cvCopy(pict,Red);

cvSetImageCOI( pict, 2);

cvCopy(pict,Green);

cvSetImageCOI(pict, 1);

cvCopy(pict,Blue);

//Initialize the BMP display buffer

bmi = (BITMAPINFO*)buffer;

bmih = &(bmi->bmiHeader);

memset( bmih, 0, sizeof(*bmih));

bmih->biSize = sizeof(BITMAPINFOHEADER);

bmih->biWidth = IMAGE_WIDTH;

bmih->biHeight = IMAGE_HEIGHT; // -IMAGE_HEIGHT;

bmih->biPlanes = 1;

bmih->biCompression = BI_RGB;

bmih->biBitCount = 24;

palette = bmi->bmiColors;

for( int i = 0; i < 256; i++ ){

palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed =

(BYTE)i;

palette[i].rgbReserved = 0;

}

cvReleaseImage(&Red);

cvReleaseImage(&Green);

cvReleaseImage(&Blue);

}

 

// HBITMAP转换DIB

HBITMAP plIamgeToDIB(IplImage *pImg,int Size)

{

HDC hDC = ::CreateCompatibleDC(0);

BYTE tmp[sizeof(BITMAPINFO)+255*4];

BITMAPINFO *bmi = (BITMAPINFO*)tmp;

HBITMAP hBmp;

int i;

memset(bmi,0,sizeof(BITMAPINFO));

bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

bmi->bmiHeader.biWidth = pImg->width;

bmi->bmiHeader.biHeight = -pImg->height;

bmi->bmiHeader.biPlanes = Size;

bmi->bmiHeader.biBitCount = pImg->nChannels * pImg->depth;

 

bmi->bmiHeader.biCompression = BI_RGB;

bmi->bmiHeader.biSizeImage = pImg->width*pImg->height*1;

bmi->bmiHeader.biClrImportant =0 ;

switch(pImg->nChannels * pImg->depth)

{

case 8 :

for(i=0 ; i < 256 ; i++)

{

bmi->bmiColors[i].rgbBlue = i;

bmi->bmiColors[i].rgbGreen= i;

bmi->bmiColors[i].rgbRed= i;

}

break;

case 32:

case 24:

((DWORD*) bmi->bmiColors)[0] = 0x00FF0000; /* red mask */

((DWORD*) bmi->bmiColors)[1] = 0x0000FF00; /* green mask */

((DWORD*) bmi->bmiColors)[2] = 0x000000FF; /* blue mask */

break;

}

hBmp = ::CreateDIBSection(hDC,bmi,DIB_RGB_COLORS,NULL,0,0);

SetDIBits(hDC,hBmp,0,pImg->height,pImg->imageData,bmi,DIB_RGB_COLORS);

::DeleteDC(hDC);

return hBmp;

 

}

 

11、图像分割

做分水岭图像分割:cvWatershed

meanshift图像分割:PyrMeanShiftFiltering

用金字塔实现图像分割:cvPyrSegmentation

http://blog.csdn.net/gnuhpc/archive/2009/06/21/4286186.aspx

大津算法阈值分割:http://hi.baidu.com/lazycat3611/blog/item/491febde06bc605d94ee37e8.html

最大熵阈值分割算法:http://www.aiseminar.cn/html/00/t-700.html

 

12、边缘检测

cvCanny:采用 Canny 算法做边缘检测

cvLaplace:laplace边缘检测

http://www.mvonline.com.cn/bbs/simple/index.php?t2421.html

cvSobel:Sobel边缘检测

cvCornerHarris:哈里斯(Harris)边缘检测

 

13、匹配

cvCalcEMD2:两个加权点集之间计算最小工作距离

cvMatchShapes:比较两个形状

cvMatchTemplate:比较模板和重叠的图像区域

基于opencv的sift图像匹配算法vc++源码:http://codechina.net/source/620393

 

14、分类器

boosted分类器:分类器的boosting技术有四种: Discrete Adaboost, Real Adaboost, Gentle Adaboost and Logitboost。

HAAR分类器,自于haar小波运算。

神经网络分类器

SVM分类器,SVM是一个分类器,原始的SVM是一个两类分类的分类器。可以通过1:1或者1:n的方式来组合成一个多类分类的分类器。天生通过核函数的使用支持高维数据的分类。从几何意义上讲,就是找到最能表示类别特征的那些向量(支持向量SV),然后找到一条线,能最大化分类的 Margin。libSVM是一个不错的实现。

http://blog.csdn.net/byxdaz/archive/2009/11/28/4893935.aspx

 

15、如何用OpenCV训练自己的分类器

http://blog.csdn.net/byxdaz/archive/2009/11/30/4907211.aspx

 

16、运动目标跟踪与检测

CamShift:

MeanShift:

http://blog.csdn.net/xauatnwpu/archive/2009/10/29/4743058.aspx

 

17、目标检测

目标检测:http://wenjuanhe.blog.163.com/blog/static/745017252009102101728454/

人脸检测的代码分析:

http://wenjuanhe.blog.163.com/blog/static/74501725200910391512151/

基于Haar-like特征的层叠推进分类器快速目标检测:

http://wenjuanhe.blog.163.com/blog/static/7450172520091039180911/

 

18、检测直线、圆、矩形

 

检测直线:cvHoughLines,cvHoughLines2

检测圆:cvHoughCircles

检测矩形:opencv中没有对应的函数,下面有段代码可以检测矩形,是通过先找直线,然后找到直线平行与垂直的四根线。

http://blog.csdn.net/byxdaz/archive/2009/12/01/4912136.aspx

 

 

19、直方图

typedef struct CvHistogram
{
int     type;
CvArr* bins; //用于存放直方图每个灰度级数目的数组指针,数组在cvCreateHist 的时候创建,其维数由cvCreateHist 确定
float   thresh[CV_MAX_DIM][2]; // for uniform histograms
float** thresh2; // for non-uniform histograms
CvMatND mat; // embedded matrix header for array histograms
}CvHistogram;

创建直方图 CvHistogram* cvCreateHist( int dims, int* sizes, int type,float** ranges=NULL, int uniform=1 );
dims 直方图维数的数目
sizes 直方图维数尺寸的数组
type 直方图的表示格式: CV_HIST_ARRAY 意味着直方图数据表示为多维密集数组 CvMatND; CV_HIST_TREE 意味着直方图数据表示为多维稀疏数组 CvSparseMat.
ranges 图中方块范围的数组. 它的内容取决于参数 uniform 的值。这个范围的用处是确定何时计算直方图或决定反向映射(backprojected ),每个方块对应于输入图像的哪个/哪组值。
uniform 归一化标识。 如果不为0,则ranges[i](0<=i

OpenCV统计应用-CvHistogram直方图资料:http://blog.csdn.net/koriya/archive/2008/11/21/3347369.aspx

20、物体跟踪
http://blog.csdn.net/gnuhpc/category/549384.aspx?PageNumber=4

21、在opencv中暂时无法打开二值图像,它里面至少是8位的图像。0,表示黑点;255,表示白点。
22、cvcanny   Canny 算法做边缘检测
void cvCanny( const CvArr* image, CvArr* edges, double threshold1,double threshold2, int aperture_size=3 );
一般threshold=threshol2*0.4 (经验值)。
23、cvCopy与cvCloneImage的区别
/* Copies source array to destination array */
CVAPI(void)  cvCopy( const CvArr* src, CvArr* dst,
const CvArr* mask CV_DEFAULT(NULL) );

/* Creates a copy of IPL image (widthStep may differ) */
CVAPI(IplImage*) cvCloneImage( const IplImage* image );

如果设定了ROI等参数的时候,cvCopy只是复制被设定的区域,复制到一个和所设定参数相吻合的新的IplImage中
而cvCloneImage则是将整个IplImage结构复制到新的IplImage中,其中的ROI等参数也会一同复制。新的IplImage将会和原来的一模一样。

cvCopy的原型是:
void cvCopy( const CvArr* src, CvArr* dst, const CvArr* mask=NULL );
在使用这个函数之前,你必须用cvCreateImage()一类的函数先开一段内存,然后传递给dst。cvCopy会把src中的数据复制到dst的内存中。如果mask(x,y)=0,则不对src/dst的(x,y)操作操作;如果mask(x,y)!=0, 则操作。

cvCloneImage的原型是:
IplImage* cvCloneImage( const IplImage* image );
在使用函数之前,不用开辟内存。该函数会自己开一段内存,然后复制好image里面的数据,然后把这段内存中的数据返回给你。

clone是把所有的都复制过来,也就是说不论你是否设置Roi,Coi等影响copy的参数,clone都会原封不动的克隆过来。
copy就不一样,只会复制ROI区域等。用clone复制之后,源图像在内存中消失后,复制的图像也变了,而用copy复制,源图像消失后,复制的图像不变。

使用cvCopy实现对不规制图形的提取:http://artificialwistom.spaces.live.com/blog/cns!C4334BEEE0193F50!191.entry
24、图像形态学操作
http://blog.csdn.net/byxdaz/archive/2010/07/30/5775717.aspx

 

四、书籍推荐:

《opencv教程基础篇》大部分为OpenCV帮助手册的翻译,原创性内容不是很多。

《Learning OpenCV》深入浅出讲OpenCV函数背后的原理,比课堂教材生动且实用,极具实战功能。

http://download.csdn.net/source/1860888

 

五、相关资料:

项目主页:http://sf.net/projects/opencvlibrary

邮件列表:http://groups.yahoo.com/group/OpenCV

中文网站:http://www.opencv.org.cn

中文论坛:http://www.opencv.org.cn/forum

 

分类:OpenCV

图像处理-仿射变换 AffineTransform

十一月 2, 2010 2 条评论

http://fairywangyutang.blog.sohu.com/146834554.html

AffineTransform类描述了一种二维仿射变换的功能,它是一种二维坐标到二维坐标之间的线性变换,保持二维图形的“平直性”(译注:straightness,即变换后直线还是直线不会打弯,圆弧还是圆弧)和“平行性”(译注:parallelness,其实是指保二维图形间的相对位置关系不变,平行线还是平行线,相交直线的交角不变。大二学过的复变,“保形变换/保角变换”都还记得吧,数学就是王道啊!)。仿射变换可以通过一系列的原子变换的复合来实现,包括:平移(Translation)、缩放(Scale)、翻转(Flip)、旋转(Rotation)和剪切(Shear)。

此类变换可以用一个3×3的矩阵来表示,其最后一行为(0, 0, 1)。该变换矩阵将原坐标(x, y)变换为新坐标(x’, y’),这里原坐标和新坐标皆视为最末一行为(1)的三维列向量,原列向量左乘变换矩阵得到新的列向量:

[x’]    [m00 m01 m02] [x]    [m00*x+m01*y+m02]
[y’] = [m10 m11 m12] [y] = [m10*x+m11*y+m12]
[1 ]   [ 0      0      1   ] [1]    [              1             ]
几种典型的仿射变换:

public static AffineTransform getTranslateInstance(double tx, double ty)

平移变换,将每一点移动到(x+tx, y+ty),变换矩阵为:
[   1    0    tx ]
[   0    1    ty ]
[   0    0    1   ]
(译注:平移变换是一种“刚体变换”,rigid-body transformation,中学学过的物理,都知道啥叫“刚体”吧,就是不会产生形变的理想物体,平移当然不会改变二维图形的形状。同理,下面的“旋转变换”也是刚体变换,而“缩放”、“错切”都是会改变图形形状的。)

public static AffineTransform getScaleInstance(double sx, double sy)

缩放变换,将每一点的横坐标放大(缩小)至sx倍,纵坐标放大(缩小)至sy倍,变换矩阵为:
[   sx   0    0   ]
[   0    sy   0   ]
[   0    0    1   ]

 

public static AffineTransform getShearInstance(double shx, double shy)

剪切变换,变换矩阵为:
[   1   shx   0   ]
[ shy   1    0   ]
[   0     0    1   ]
相当于一个横向剪切与一个纵向剪切的复合
[   1      0    0   ][   1   shx   0   ]
[ shy   1    0   ][   0     1     0   ]
[   0      0    1   ][   0    0     1   ]
(译注:“剪切变换”又称“错切变换”,指的是类似于四边形不稳定性那种性质,街边小商店那种铁拉门都见过吧?想象一下上面铁条构成的菱形拉动的过程,那就是“错切”的过程。)

public static AffineTransform getRotateInstance(double theta)

旋转变换,目标图形围绕原点顺时针旋转theta弧度,变换矩阵为:
[   cos(theta)    -sin(theta)    0   ]
[   sin(theta)     cos(theta)    0   ]
[       0                0             1   ]

 

public static AffineTransform getRotateInstance(double theta, double x, double y)

旋转变换,目标图形以(x, y)为轴心顺时针旋转theta弧度,变换矩阵为:
[   cos(theta)    -sin(theta)    x-x*cos+y*sin]
[   sin(theta)     cos(theta)    y-x*sin-y*cos ]
[       0                 0                  1             ]
相当于两次平移变换与一次原点旋转变换的复合:
[1 0 -x][cos(theta) -sin(theta) 0][1 0 x]
[0 1 -y][sin(theta)   cos(theta) 0][0 1 y]
[0 0 1 ][     0                0        1 ][0 0 1]

 

分类:Image Processing

随机抽样一致性算法RANSAC源程序和教程

十一月 2, 2010 留下评论

什么是RANSAC?

RANSAC是RANdom SAmple Consensus(随机抽样一致性)的缩写。它是从一个观察数据集合中,估计模型参数(模型拟合)的迭代方法。它是一种随机的不确定算法,每次运算求出的结果可能不相同,但总能给出一个合理的结果,为了提高概率必须提高迭代次数。

RANSAC很强大。如图1所示,RANSAC用于在强干扰环境中寻找以某种模型(如直线)出现的数据。图中黄色点代表坐标原点,蓝色点代表输入的数据点集合(其中包含20个真实点和40个干扰点),红色点代表RANSAC算法在强干扰环境中,经过500次左右的抽样迭代,能找出的和直线拟合最好的13点(之所以不等于20,是因为真实直线也受到轻微干扰,所以并不笔直)。

图1,随机抽样一致性算法示例图

RANSAC常常用于计算机视觉,例如求解图像对应点和估计立体视觉的基本矩阵。作者曾将本算法用于视觉测量(点击查看该文)。更多的应用参见文献[1](RANSAC25年)。本文来自:http: //www.shenlejun.cn.

那里能找到源程序?本文来自:蜜蜂电脑.

该算法简单实用,Ziv Yaniv曾经写了一个不错的C++程序。推荐理由1:没有使用任何其他的函数库,省去您更多负担。如解线性方程、随机数产生器。理由2:唯一觉得可惜的是他的算法中过多的使用了C++的特性,如vector,虚函数,设计模式。如果您刚好是C++高手,正是您阅读的好程序。理由3:作者的注解详细,每2行就有1行注释,容易看懂。偶自愧不如(汗~~~)。

可惜源程序在原作者的主页上已经无法下载[3],本站未做任何修改,将Ziv Yaniv的源程序提供给各位同行。
点击下载此文件(17KB)

入门教程呢?

本来想把文献[2]翻译一下,贴到这里。请让我偷个懒吧。能找到这里的读者,应该都是E文好手,我翻译得不清不楚,反而给大家增添烦恼了。 ^_^
推荐文献[2]的理由1:文字少且简洁易懂;理由2:伪代码只有10多行,却清楚明白,比市面上某些教材好多了。
本文来自:shenlejun.cn.

参考文献

[1]RANSAC25年,http://cmp.felk.cvut.cz/ransac-cvpr2006/
[2]RANSAC名词解释,http://en.wikipedia.org/wiki/RANSAC
[3]RANSAC源代码下载,www.cs.huji.ac.il/~zivy/software/ransac.tar.gz

 

 

分类:Recognition

简单的人脸检测程序

十一月 2, 2010 留下评论

#include “stdafx.h”

 

#include “cv.h”

#include “highgui.h”

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <assert.h>

#include <math.h>

#include <float.h>

#include <limits.h>

#include <time.h>

#include <ctype.h>

 

#ifdef _EiC

#define WIN32

#endif

 

#pragma  comment(lib,”cv210d.lib”)

#pragma  comment(lib,”cxcore210d.lib”)

#pragma  comment(lib,”highgui210d.lib”)

 

static CvMemStorage* storage = 0;

static CvHaarClassifierCascade* cascade = 0;

 

void detect_and_draw( IplImage* image );

 

const char* cascade_name =

“haarcascade_frontalface_alt.xml”;

/* “haarcascade_profileface.xml”;*/

 

int main( int argc, char** argv )

{

CvCapture* capture = 0;

IplImage *frame, *frame_copy = 0;

int optlen = strlen(“–cascade=”);

const char* input_name;

 

if( argc > 1 && strncmp( argv[1], “–cascade=”, optlen ) == 0 )

{

cascade_name = argv[1] + optlen;

input_name = argc > 2 ? argv[2] : 0;

}

else

{

cascade_name = “D://OpenCV2.1//data//haarcascades//haarcascade_frontalface_alt2.xml”;

input_name = argc > 1 ? argv[1] : 0;

}

 

cascade = (CvHaarClassifierCascade*)cvLoad( cascade_name, 0, 0, 0 );

 

if( !cascade )

{

fprintf( stderr, “ERROR: Could not load classifier cascade\n” );

fprintf( stderr,

“Usage: facedetect –cascade=\”\” [filename|camera_index]\n” );

return -1;

}

storage = cvCreateMemStorage(0);

 

if( !input_name || (isdigit(input_name[0]) && input_name[1] == ”) )

capture = cvCaptureFromCAM( !input_name ? 0 : input_name[0] – ‘0’ );

else

capture = cvCaptureFromAVI( input_name );

 

cvNamedWindow( “result”, 1 );

 

if( capture )

{

for(;;)

{

if( !cvGrabFrame( capture ))

break;

frame = cvRetrieveFrame( capture );

if( !frame )

break;

if( !frame_copy )

frame_copy = cvCreateImage( cvSize(frame->width,frame->height),

IPL_DEPTH_8U, frame->nChannels );

if( frame->origin == IPL_ORIGIN_TL )

cvCopy( frame, frame_copy, 0 );

else

cvFlip( frame, frame_copy, 0 );

 

detect_and_draw( frame_copy );

 

if( cvWaitKey( 10 ) >= 0 )

break;

}

 

cvReleaseImage( &frame_copy );

cvReleaseCapture( &capture );

}

else

{

const char* filename = input_name ? input_name : (char*)”lena.jpg”;

IplImage* image = cvLoadImage( filename, 1 );

 

if( image )

{

detect_and_draw( image );

cvWaitKey(0);

cvReleaseImage( ℑ );

}

else

{

/* assume it is a text file containing the

list of the image filenames to be processed – one per line */

FILE* f = fopen( filename, “rt” );

if( f )

{

char buf[1000+1];

while( fgets( buf, 1000, f ) )

{

int len = (int)strlen(buf);

while( len > 0 && isspace(buf[len-1]) )

len–;

buf[len] = ”;

image = cvLoadImage( buf, 1 );

if( image )

{

detect_and_draw( image );

cvWaitKey(0);

cvReleaseImage( ℑ );

}

}

fclose(f);

}

}

 

}

 

cvDestroyWindow(“result”);

 

return 0;

}

 

void detect_and_draw( IplImage* img )

{

static CvScalar colors[] =

{

{{0,0,255}},

{{0,128,255}},

{{0,255,255}},

{{0,255,0}},

{{255,128,0}},

{{255,255,0}},

{{255,0,0}},

{{255,0,255}}

};

 

double scale = 1.3;

IplImage* gray = cvCreateImage( cvSize(img->width,img->height), 8, 1 );

IplImage* small_img = cvCreateImage( cvSize( cvRound (img->width/scale),

cvRound (img->height/scale)),

8, 1 );

int i;

 

cvCvtColor( img, gray, CV_BGR2GRAY );

cvResize( gray, small_img, CV_INTER_LINEAR );

cvEqualizeHist( small_img, small_img );

cvClearMemStorage( storage );

 

if( cascade )

{

double t = (double)cvGetTickCount();

CvSeq* faces = cvHaarDetectObjects( small_img, cascade, storage,

1.1, 2, 0/*CV_HAAR_DO_CANNY_PRUNING*/,

cvSize(30, 30) );

t = (double)cvGetTickCount() – t;

printf( “detection time = %gms\n”, t/((double)cvGetTickFrequency()*1000.) );

for( i = 0; i < (faces ? faces->total : 0); i++ )

{

CvRect* r = (CvRect*)cvGetSeqElem( faces, i );

CvPoint center;

int radius;

center.x = cvRound((r->x + r->width*0.5)*scale);

center.y = cvRound((r->y + r->height*0.5)*scale);

radius = cvRound((r->width + r->height)*0.25*scale);

cvCircle( img, center, radius, colors[i%8], 3, 8, 0 );

}

}

 

cvShowImage( “result”, img );

cvReleaseImage( &gray );

cvReleaseImage( &small_img );

}

 

分类:OpenCV

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

十一月 2, 2010 留下评论

 

一、预备知识:
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:字符串的长度(不计算”)。如果参数为负数,函数就计算该字符串的长度。
函数 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= (例如, 对于视频会议的图像区域).
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>”中缺省为24×24), 如果scale=2, 使用的窗口是上面的两倍 (在face cascade中缺省值是48×48 )。 这样尽管可以将检测速度提高四倍,但同时尺寸小于48×48的人脸将不能被检测到。
函数 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( ℑ );
}
return 0;
}
关键代码很简单,装载分类器,对输入图像进行金字塔采样,然后用cv的函数进行检测目标,最后输出检测到的目标矩形

 

 

分类:OpenCV