主要是用Camshift算法实现,了解Camshift算法,必须先了解以下东西:
什么是直方图:应该先是一个直方结构体,然后用cvCreateHist()函数创建,再用CalcHist计算出相应的直方图。可能是一个色彩分布图吧.....
再者,反向投影cvCalcBackProject
根据获得的色彩直方图将原始图像转化成色彩概率分布图像,这个过程就被称作"Back Projection"。
下面是说明文档,看不懂,结合着慢慢看:
1、直方图:
CvHistogram 结构定义
多维直方图
typedef struct CvHistogram
{
int type;
CvArr* bins;
float thresh[CV_MAX_DIM][2]; /* for uniform histograms */
float** thresh2; /* for non-uniform histograms */
CvMatND mat; /* embedded matrix header for array histograms */
}CvHistogram;
2、CreateHist
创建直方图
创建直方图,函数说明文档,
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<cDims,译者注:cDims为直方图的维数,对于灰度图为1,彩色图为3)是包含两个元素的范围数组,包括直方图第i维的上界和下界。在第i维上的整个区域 [lower,upper]被分割成 dims[i] 个相等的块(译者注:dims[i]表示直方图第i维的块数),这些块用来确定输入象素的第 i 个值(译者注:对于彩色图像,i确定R, G,或者B)的对应的块;如果为0,则ranges[i]是包含dims[i]+1个元素的范围数组,包括lower0, upper0, lower1, upper1 == lower2, ..., upperdims[i]-1, 其中lowerj 和upperj分别是直方图第i维上第 j 个方块的上下界(针对输入象素的第 i 个值)。任何情况下,输入值如果超出了一个直方块所指定的范围外,都不会被 cvCalcHist 计数,而且会被函数 cvCalcBackProject 置零。
函数 cvCreateHist 创建一个指定尺寸的直方图,并且返回创建的直方图的指针。 如果数组的 ranges 是 0, 则直方块的范围必须由函数 cvSetHistBinRanges 稍后指定。虽然 cvCalcHist 和 cvCalcBackProject 可以处理 8-比特图像而无需设置任何直方块的范围,但它们都被假设等分 0..255 之间的空间。
3、CalcHist
计算图像image(s) 的直方图
void cvCalcHist( IplImage** image, CvHistogram* hist,
int accumulate=0, const CvArr* mask=NULL );
image
输入图像s (虽然也可以使用 CvMat** ).
hist
直方图指针
accumulate
累计标识。如果设置,则直方图在开始时不被清零。这个特征保证可以为多个图像计算一个单独的直方图,或者在线更新直方图。
mask
操作 mask, 确定输入图像的哪个象素被计数
函数 cvCalcHist 计算单通道或多通道图像的直方图。 用来增加直方块的数组元素可从相应输入图像的同样位置提取。 Sample. 计算和显示彩色图像的 2D 色调-饱和度图像
4、CalcBackProject
计算反向投影
void cvCalcBackProject( IplImage** image, CvArr* back_project, const CvHistogram* hist );
image
输入图像 (也可以传递 CvMat** ).
back_project
反向投影图像,与输入图像具有同样类型.
hist
直方图
函数 cvCalcBackProject 计算直方图的反向投影. 对于所有输入的单通道图像同一位置的象素数组,该函数根据相应的象素数组(RGB),放置其对应的直方块的值到输出图像中。用统计学术语,输出图像象素点的值是观测数组在某个分布(直方图)下的概率。 例如,为了发现图像中的红色目标,可以这么做:
对红色物体计算色调直方图,假设图像仅仅包含该物体。则直方图有可能有极值,对应着红颜色。
对将要搜索目标的输入图像,使用直方图计算其色调平面的反向投影,然后对图像做阈值操作。
在产生的图像中发现连通部分,然后使用某种附加准则选择正确的部分,比如最大的连同部分。
这是 Camshift 彩色目标跟踪器中的一个逼进算法,除了第三步,CAMSHIFT 算法使用了上一次目标位置来定位反向投影中的目标。
opencv之camshift(转自http://blog.csdn.net/houdy/category/56104.aspx)
看了几遍都不是很明白 -_-!!
1--Back Projection
CamShift算法,即"Continuously Apative Mean-Shift"算法,是一种运动跟踪算法。它主要通过视频图像中运动物体的颜色信息来达到跟踪的目的。我把这个算法分解成三个部分,便于理解:
1) Back Projection计算
2) Mean Shift算法
3) CamShift算法
在这里主要讨论Back Projection,在随后的文章中继续讨论后面两个算法。
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,on
//----------------------------------------------
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的例子。遗憾的是这个例子目标的跟踪是半自动的,即需要人手工选定一个目标。我正在努力尝试全自动的目标跟踪,希望可以和大家能在这方面与大家交流。
5.
运动目标跟踪与检测的源代码(CAMSHIFT 算法)
From http://blog.csdn.net/hunnish/archive/2004/09/07/97049.aspx
采用 CAMSHIFT 算法快速跟踪和检测运动目标的 C/C++ 源代码,OPENCV BETA 4.0 版本在其 SAMPLE 中给出了这个例子。算法的简单描述如下(英文):
This application demonstrates a fast, simple color tracking algorithm that can be used to track faces, hands . The CAMSHIFT algorithm is a modification of the Meanshift algorithm which is a robust statistical method of finding the mode (top) of a probability distribution. Both CAMSHIFT and Meanshift algorithms exist in the library. While it is a very fast and simple method of tracking, because CAMSHIFT tracks the center and size of the probability distribution of an object, it is on
In this application, we use on
算法的详细情况,请看论文:
http://www.assuredigit.com/incoming/camshift.pdf
实现彩色目标跟踪,就调用opencv中的camshift算法。
个人对camshift算法理解是:直方图,应该是颜色概率分布图,然后去概率小的颜色。
把直方图 与 被搜索目标的图像 进行反向投影运算,剩下颜色与直方图中概率颜色近似的像素
然后用camshift进行迭代运算,把目标框框不断逼近剩下的颜色区域
使得圈出的范围越来越逼近想要搜寻的目标对象。详细请看上两篇camshift算法的具体说明。
程序来自:opencv教程 例题 5 -22 (加上自己的注释)
书中的在VC6下控制台实现,我把它改到C++ builder 6下实现。
运行后效果图:

程序代码:
Co
http://www.CodeHighlighter.com/
--> // ---------------------------------------------------------------------------
// --------------头文件---------------
#ifndef camshiftH
#define camshiftH
// ---------------------------------------------------------------------------
#include < Classes.hpp >
#include < Controls.hpp >
#include < StdCtrls.hpp >
#include < Forms.hpp >
#include " cv.h "
#include " highgui.h "
// ---------------------------------------------------------------------------
// ==================全局变量=====================
IplImage * image = 0 , * hsv = 0 , * hue = 0 ;
IplImage * mask = 0 , * backproject = 0 , * histimg = 0 ;
CvHistogram * hist = 0 ; // 直方
CvCapture * capture = 0 ;
int backproject_mode = 0 ; // 表示反向投影效果是否打开
int select_object = 0 ; // 表示是否已开始选
int track_object = 0 ; // 表示是否已选好区域
int show_hist = 0 ; // 表示是否显示直方图窗体
CvPoint origin; // 选中区域的原始点
CvRect selection; // 选中的矩形区域
CvRect track_window; //
CvBox2D track_box; // tracking 返回的区域box,带角度
CvConnectedComp track_comp;
int hdims = 48 ; // 划分HIST的个数,越高越精确
float hranges_arr[] = { 0 , 180 }; // 定义直方图内方块的范围,第i维上下界
float * hranges = hranges_arr;
int vmin = 10 , vmax = 256 , smin = 30 ; // 三个滚动条的初始值
bool closeCAM = false ;
// ------------全局函数--------------
// 定义鼠标回调函数,此处设为全局函数,便于回调
// 若想设为类成员函数,参考回调函数说明
// 后面加了个指针参数,不加出错,vc中确不会错,不知道为什么。
// 查看opencv中Callback函数定义,确实要5个参数
void on_mouse( int event , int x, int y, int flags, void * t = NULL)
{
if ( ! image) return ;
if (image -> origin) // windows的bitmap标准
y = image -> height - y;
if (select_object) // 鼠标选中且移出窗体时
{
selection.x = MIN(x,origin.x);
selection.y = MIN(y,origin.y);
selection.width = selection.x + CV_IABS(x - origin.x);
selection.height = selection.y + CV_IABS(y - origin.y);
selection.x = MAX(selection.x, 0 );
selection.y = MAX(selection.y, 0 );
selection.width = MIN(selection.width,image -> width);
selection.height = MIN(selection.height,image -> height);
selection.width -= selection.x;
selection.height -= selection.y;
}
switch ( event )
{
case CV_EVENT_LBUTTONDOWN:
origin = cvPoint(x,y);
selection = cvRect(x,y, 0 , 0 );
select_object = 1 ; // 开始选择区域
break ;
case CV_EVENT_LBUTTONUP:
select_object = 0 ; // 停止选择区域
if (selection.width > 0 && selection.height > 0 )
track_object = - 1 ; // 选中区域有效,则表示范围已选好
}
}
// ---------------------------------------------------------------------------
CvScalar hsv2rgb( float hue) // 直方图H值转为RGB,为什么是这样算的,不清楚
{
int rgb[ 3 ],p,sector;
static const int sector_da
{ 2 , 0 , 1 },{ 2 , 1 , 0 },{ 0 , 1 , 2 }};
hue *= 0.033333333333333333333f ;
sector = cvFloor(hue); // cvFloor 返回不大于参数的最大整数值
p = cvRound( 255 * (hue - sector)); // cvRound 返回和参数最接近的整数值
p ^= sector & 1 ? 255 : 0 ;
rgb[sector_da
rgb[sector_da
rgb[sector_da
return cvScalar(rgb[ 2 ],rgb[ 1 ],rgb[ 0 ], 0 );
}
// ---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TButton * Button1;
TButton * Button2;
TButton * Button3;
TButton * Button4;
TButton * Button5;
void __fastcall Button1Click(TObject * Sender);
void __fastcall Button3Click(TObject * Sender);
void __fastcall Button4Click(TObject * Sender);
void __fastcall Button5Click(TObject * Sender);
void __fastcall Button2Click(TObject * Sender);
private : // User declarations
public : // User declarations
__fastcall TForm1(TComponent * Owner);
};
// ---------------------------------------------------------------------------
extern PACKAGE TForm1 * Form1;
// ---------------------------------------------------------------------------
#endif
// ---------------------------------------------------------------------------
Co
http://www.CodeHighlighter.com/
--> // ---------------------------------------------------------------------------
// ----------------实现文件-----------------------
#include < vcl.h >
#pragma hdrstop
#include " camshift.h "
// ---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 * Form1;
// ---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent * Owner)
: TForm(Owner)
{
}
// ---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject * Sender)
{
IplImage * frame = 0 ;
capture = cvCaptureFromCAM( 0 );
if ( ! capture)
{
ShowMessage( " 启动摄像头失败! " );
return ;
}
cvNamedWindow( " 目标捕获 " , 1 );
// 设定鼠标回调事件
cvSetMouseCallback( " 目标捕获 " ,on_mouse,NULL);
// 创建滚动条
cvCreateTrackbar( " 掩码范围A " , " 目标捕获 " , & vmin, 256 , 0 );
cvCreateTrackbar( " 掩码范围B " , " 目标捕获 " , & vmax, 256 , 0 );
cvCreateTrackbar( " Smin " , " 目标捕获 " , & smin, 256 , 0 );
while (frame = cvQueryFrame(capture))
{
int i,bin_w,c;
if ( ! image)
{
// 分配内存
image = cvCreateImage(cvSize(frame -> width,frame -> height), 8 , 3 );
image -> origin = frame -> origin;
hsv = cvCreateImage(cvSize(frame -> width,frame -> height), 8 , 3 );
hue = cvCreateImage(cvSize(frame -> width,frame -> height), 8 , 1 );
mask = cvCreateImage(cvSize(frame -> width,frame -> height), 8 , 1 );
backproject = cvCreateImage(cvSize(frame -> width,frame -> height), 8 , 1 );
hist = cvCreateHist( 1 , & hdims,CV_HIST_ARRAY, & hranges, 1 );
histimg = cvCreateImage(cvSize( 320 , 200 ), 8 , 3 );
cvZero(histimg);
}
cvCopy(frame,image, 0 );
cvCvtColor(image,hsv,CV_BGR2HSV); // 彩色空间转换,从BGR到HSV
if (track_object)
{
int _vmin = vmin, _vmax = vmax;
// 对色彩空间在一定范围内二值化,得出掩码mask
cvInRangeS(hsv,cvScalar( 0 ,smin,MIN(_vmin,_vmax), 0 ),
cvScalar( 180 , 256 ,MAX(_vmin,_vmax), 0 ),mask);
cvSplit(hsv,hue, 0 , 0 , 0 ); // 只提取HUE分量
if (track_object < 0 ) // 区域已选择完成
{
float max_val = 0 .f;
cvSetImageROI(hue,selection); // 设置感兴趣区域
cvSetImageROI(mask,selection);
cvCalcHist( & hue,hist, 0 ,mask); // 用掩码计算出直方图
cvGetMinMaxHistValue(hist, 0 , & max_val, 0 , 0 ); // 找最大值
// 线性变换数组,缩放bin到[0,255]区间
cvConvertScale(hist -> bins,hist -> bins,
max_val ? 255 . / max_val : 0 ., 0 );
cvResetImageROI(hue); // 取消感兴趣区域
cvResetImageROI(mask);
track_window = selection;
track_object = 1 ;
cvZero(histimg);
bin_w = histimg -> width / hdims; // 直方图中每条柱子的宽度
// 画直方图
for (i = 0 ;i < hdims;i ++ )
{
// GetReal*D 返回单通道数组的指定元素
int val = cvRound(cvGetReal1D(hist -> bins,i) * histimg -> height / 255 );
CvScalar color = hsv2rgb(i * 180 .f / hdims);
cvRectangle(histimg,cvPoint(i * bin_w,histimg -> height),
cvPoint((i + 1 ) * bin_w,histimg -> height - val),
color, - 1 , 8 , 0 );
}
}
// 计算直方图的反向投影,backproject是想要的结果
cvCalcBackProject( & hue,backproject,hist);
// 逻辑相与,用掩码选出相应区域
cvAnd(backproject,mask,backproject, 0 );
// 调用Camshift算法模块
cvCamShift(backproject,track_window,cvTermCriteria(CV_TERMCRIT_EPS |
CV_TERMCRIT_ITER, 10 , 1 ), & track_comp, & track_box);
//把这次位置作为下次迭代的初始位置
track_window = track_comp.rect;
if (backproject_mode)
cvCvtColor(backproject,image,CV_GRAY2BGR); // 使用backproject灰度图像
if (image -> origin)
track_box.angle = - track_box.angle;
cvEllipseBox(image,track_box,CV_RGB( 255 , 0 , 0 ), 3 ,CV_AA, 0 );
}
if (select_object && selection.width > 0 && selection.height > 0 )
{
cvSetImageROI(image,selection);
cvXorS(image,cvScalarAll( 255 ),image, 0 );
cvResetImageROI(image);
}
cvShowImage( " 目标捕获 " ,image);
cvShowImage( " 直方图 " ,histimg);
if (cvWaitKey( 10 ) == 27 || closeCAM) break ;
}
cvReleaseCapture( & capture);
cvDestroyWindow( " 目标捕获 " );
}
// ---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject * Sender)
{
if (capture)
backproject_mode ^= 1 ;
else
ShowMessage( " 您还未启动摄像头 " );
}
// ---------------------------------------------------------------------------
void __fastcall TForm1::Button4Click(TObject * Sender)
{
if ( ! capture)
{
ShowMessage( " 您还未启动摄像头 " );
return ;
}
track_object = 0 ;
cvZero(histimg);
}
// ---------------------------------------------------------------------------
void __fastcall TForm1::Button5Click(TObject * Sender)
{
if ( ! capture)
{
ShowMessage( " 您还未启动摄像头 " );
return ;
}
show_hist ^= 1 ;
if ( ! show_hist)
{
cvDestroyWindow( " 直方图 " );
this -> Button5 -> Caption = " 打开直方图窗体 " ;
}
else
{
cvNamedWindow( " 直方图 " , 1 );
this -> Button5 -> Caption = " 关闭直方图窗体 " ;
}
}
// ---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject * Sender)
{
if ( ! capture)
{
ShowMessage( " 您还未启动摄像头 " );
return ;
}
closeCAM = true ;
}
// ---------------------------------------------------------------------------

1159

被折叠的 条评论
为什么被折叠?



