原理
CamShift算法,全称是 Continuously AdaptiveMeanShift,顾名思义,它是对Mean Shift 算法的改进,能够自动调节搜索窗口大小来适应目标的大小,可以跟踪视频中尺寸变化的目标。它也是一种半自动跟踪算法,需要手动标定跟踪目标。基本思想是以视频图像中运动物体的颜色信息作为特征,对输入图像的每一帧分别作 Mean-Shift 运算,并将上一帧的目标中心和搜索窗口大小(核函数带宽)作为下一帧 Mean shift 算法的中心和搜索窗口大小的初始值,如此迭代下去,就可以实现对目标的跟踪。因为在每次搜索前将搜索窗口的位置和大小设置为运动目标当前中心的位置和大小,而运动目标通常在这区域附近,缩短了搜索时间;另外,在目标运动过程中,颜色变化不大,故该算法具有良好的鲁棒性。已被广泛应用到运动人体跟踪,人脸跟踪等领域。
opencv封装应用过程
实质其实是获取目标图像的直方图,在视频每一帧图像上做反向映射,通过meanshift算法计算得出匹配度最高的区域,以此实现对物体的追踪
相关api
函数功能描述
mixChannels
主要就是把输入的矩阵(或矩阵数组)的某些通道拆分复制给对应的输出矩阵(或矩阵数组)的某些通道中,其中的对应关系就由fromTo
参数制定
通道分离函数api
void mixChannels ( const Mat* src ,
int nsrc ,
Mat* dst ,
int ndst ,
const int* fromTo ,
size_t npairs );
参数介绍
- src:输入矩阵,可以为一个也可以为多个,但是矩阵必须有相同的大小和深度.
- nsrcs:输入矩阵的个数。
- dst:输出矩阵,可以为一个也可以为多个,但是所有的矩阵必须事先分配空间(如用create),大小和深度须与输入矩阵等同.
- ndsts:输出矩阵的个数。
- fromTo:设置输入矩阵的通道对应输出矩阵的通道,规则如下:首先用数字标记输入矩阵的各个通道。输入矩阵个数可能多于一个并且每个矩阵的通道可能不一样,第一个输入矩阵的通道标记范围为:
0 ~src[0].channels()-1
,第二个输入矩阵的通道标记范围为:src[0].channels() ~ src[0].channels()
+src[1].channels()-1
,以此类推;其次输出矩阵也用同样的规则标记,第一个输出矩阵的通道标记范围为:0 ~dst[0].channels()-1
,第二个输入矩阵的通道标记范围为:dst[0].channels()~ dst[0].channels()+dst[1].channels()-1
,以此类推;最后,数组fromTo
的第一个元素即fromTo[0]
应该填入输入矩阵的某个通道标记,而fromTo
的第二个元素即fromTo[1]
应该填入输出矩阵的某个通道标记,这样函数就会把输入矩阵的fromTo[0]
通道里面的数据复制给输出矩阵的fromTo[1]
通道。fromTo
后面的元素也是这个道理,总之就是一个输入矩阵的通道标记后面必须跟着个输出矩阵的通道标记。 - npairs:即参数fromTo中的有几组输入输出通道关系(一共几对),其实就是参数
fromTo
的数组元素个数除以2
.
绘制椭圆函数api
void ellipse( InputOutputArray img,
Point center,
Size axes,
double angle,
double startAngle,
double endAngle,
const Scalar& color,
int thickness = 1,
int lineType = LINE_8,
int shift = 0);
参数介绍
ellipse函数将椭圆画到图像 lmg 上, 椭圆中心为点center,并且大小位于矩形 axes 内,椭圆旋转角度为 angle, 扩展的弧度从 0 度到 360 度,
- img:绘制目标图像。
- center:椭圆圆心坐标。
- axes:轴的长度。
- angle:偏转的角度。
- start_angle:圆弧起始角的角度。.
- end_angle:圆弧终结角的角度。
- color:线条的颜色。
- thickness:线条的粗细程度。
- line_type:线条的类型,见CVLINE的描述。
- shift:圆心坐标点和数轴的精度。
直方图计算API
calcHist( const Mat* images,//输入图像指针
int images,// 图像数目
const int* channels,// 通道数
InputArray mask,// 输入mask,可选,不用
OutputArray hist,//输出的直方图数据
int dims,// 维数
const int* histsize,// 直方图级数
const float* ranges,// 值域范围
bool uniform,// true by default
bool accumulate// false by defaut
)
参数介绍
- const Mat images*:输入图像
- int nimages:输入图像的个数
- const int channels*:需要统计直方图的第几通道
- InputArray mask:掩膜,,计算掩膜内的直方图 …Mat()
- OutputArray hist:输出的直方图数组
- int dims:需要统计直方图通道的个数
- const int histSize*:指的是直方图分成多少个区间,就是 bin的个数
- *const float ranges: 统计像素值得区间
- bool uniform=true::是否对得到的直方图数组进行归一化处理
- bool accumulate=false:在多个图像时,是否累计计算像素值得个数
反向投影api
void cv::calcBackProject( const Mat *images,
int nimages,
const int *channels,
InputArray hist,
OutputArray backProject,
const float **ranges,
double scale = 1,
bool uniform = true
)
参数介绍
- const Mat images*:输入图像,图像深度必须位CV_8U,CV_16U或CV_32F中的一种,尺寸- 相同,每一幅图像都可以有任意的通道数
- int nimages:输入图像的数量
- const int channels*:用于计算反向投影的通道列表,通道数必须与直方图维度相匹配,第一个数组的通道是从
0
到image[0].channels()-1
,第二个数组通道从图像image[0]. channels()
到image[0].channels()+image[1].channels()-1
计数 - InputArray hist:输入的直方图,直方图的bin可以是密集(dense)或稀疏(sparse)
- OutputArray backProject:目标反向投影输出图像,是一个单通道图像,与原图像有相 同的尺寸和深度
- const float ranges**:直方图中每个维度bin的取值范围
- double scale=1:可选输出反向投影的比例因子
- bool uniform=true:直方图是否均匀分布(uniform)的标识符,有默认值true
目标追踪函数api
RotatedRect CamShift(InputArray probImage,
Rect& window,
TermCriteria criteria)
参数介绍
- probImage:为输入图像直方图的反向投影图,
- window:为要跟踪目标的初始位置矩形框,
- criteria:为算法结束条件。
- 返回值:返回一个有方向角度的矩阵。该函数的实现首先是利用meanshift算法计算出要跟踪的中心,然后调整初始窗口的大小位置和方向角度。在camshift内部调用了meanshift算法计算目标的重心。
代码
using namespace std;
using namespace cv;
int main(void)
{
VideoCapture cap;
namedWindow("inputvideo",0);
resizeWindow("inputvideo",272,480);
namedWindow("dstvideo",0);
resizeWindow("dstvideo",272,480);
namedWindow("hist imag",WINDOW_AUTOSIZE);
if(!cap.open("/work/opencv_video/knef.mp4"))
{
cout << "video is err" << endl;
return -1;
}
cout << "video is open" << endl;
bool first_read = true; //第一次选定roi
Rect selection;
Mat frame; //原始画面
Mat hsv; //色彩空间转换
Mat hue; //h通道
Mat hist; //直方图
Mat mask; //
Mat backproject;
Mat draw_img = Mat(300,300,CV_8UC3);
int bins = 16;
float hrange[]={0,180};
const float *hranges = hrange;
while(true)
{
cap>>frame;
if(frame.empty())
{
break;
}
if(first_read) //选定roi区域
{
//opencv 提供的选定roi api
Rect2d first = selectROI("inputvideo",frame);
//小数转整数 方便后面函数调用
selection.x = first.x;
selection.y = first.y;
selection.width = first.width;
selection.height = first.height;
}
//颜色空间转换 rgb->hsv
cvtColor(frame,hsv,COLOR_BGR2HSV);
//剥离h通道
hue = Mat(hsv.size(),hsv.depth());
int channles[]={0,0}; //通道对应
//将hsv图片的h通道数据复制到hue的第一通道
mixChannels(&hsv,1,&hue,1,channles,1);
//获取roi区域的直方图 用于反向
if(first_read)
{
//ROI区域截取
Mat roi(hue,selection);
//计算直方图
calcHist(&roi,1,0,Mat(),hist,1,&bins,&hranges);
//直方图数据归一化处理
normalize(hist,hist,0,255,NORM_MINMAX);
//绘制直方图
int binw = draw_img.cols/bins; //0-180的范围一共分为16个区间 确定每个区间占多宽
Mat colorindex = Mat(1,bins,CV_8UC3); //创建1×16 数组 存储每个区间的直方图颜色
for(int i=0;i<bins;i++)
{
colorindex.at<Vec3b>(0,i) = Vec3b((i*180/bins),255,255); //随机颜色生成
}
cvtColor(colorindex,colorindex,COLOR_HSV2BGR);
for(int i=0;i<bins;i++)
{
//每个区间对应数值占图片高度的比例(前面对数据进行0-255归一化处理了所以这里除255)
int val = hist.at<float>(i*draw_img.rows/255);
//绘制直方图
rectangle(draw_img,Point(i*binw,draw_img.rows),Point((i+1)*binw,draw_img.rows-val),
Scalar(colorindex.at<Vec3b>(0,i)),-1,8,0);
}
}
//直方图反向映射
calcBackProject(&hue,1,0,hist,backproject,&hranges);
//通过meanshift算法计算 匹配重心
RotatedRect trackbox = CamShift(backproject,selection,
TermCriteria((TermCriteria::EPS | TermCriteria::COUNT),10,1));
//依据带角度的矩形 绘制椭圆
ellipse(frame,trackbox,Scalar(0,0,255),3,8);
if(first_read)
{
imshow("hist imag",draw_img);
first_read = false;
}
imshow("dstvideo",frame);
if(waitKey(30)>0)
{
cout << "video is close" << endl;
break;
}
}
cap.release();
destroyAllWindows();
return 0;
}
## 效果
选定ROI
实现追踪