OpenCV3使用meanshift实现目标跟踪
@[C++|OpenCV]
用到的基本函数
mixchannels()
函数原型:void mixChannels(const Mat*src, size_t nsrcs, Mat* dst, size_t ndsts, const int* fromTo, size_t npairs)
用于拷贝输入图像的某通道到输出图像的某通道
- src输入图像矩阵的向量
- nsrcs输入图像矩阵个数
- dst输出图像矩阵的向量
- ndsts输出图像矩阵个数
- fromTo,是一个数组,比如说{0,0}就表示把输入的第0通道拷贝至输出的第0通道
- npairs,fromTo中的数组个数
inrange()
函数原型举例:inRange(rgb,Scalar(0,10,30),Scalar(180,256,256),mask);
函数将判断rgb图的三个通道B,G,R是否分别处于[0,180],[10,256],[30,256],如果是的话,mask图上的对应值为1,如果不是,则为0
calcHist()
函数原型: void calcHist(const Mat* arrays, int narrays, const int* channels, InputArray mask, OutputArray
hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=
false )
参数:
- arrays输入的图的指针,也就是说可以同时输入多个图
- narrays输入的图的个数
- channels每个图的通道数
- mask掩码,其中值为1的点对应的点将被用于计算
- hist计算出的直方图
- dims计算出的直方图的维度,一般为1
- histSize,计算出的直方图的维度上的直方图条数
- ranges用来进行统计的范围
normalize()
函数原型:void normalize(const InputArray src, OutputArray dst, double alpha=1, double beta=0,
int normType=NORM_MINMAX)
将图像归一化
参数:
- src输入图像
- dst输出图像
- alpha归一化最小值
- beta归一化最大值
calcBackProject()
函数原型:void cv::calcBackProject( const Mat * images, int nimages, const int * channels, InputArray hist, OutputArray backProject, const float ** ranges, double scale = 1, bool uniform=true )
参数:
- images: 输入图像,图像深度必须位CV_8U,CV_16U或CV_32F中的一种,尺寸相同,每一幅图像都可以有任意的通道数
- nimages: 输入图像的数量
- channels: 用于计算反向投影的通道列表,通道数必须与直方图维度相匹配,第一个数组的通道是从0到image[0].channels()-1,第二个数组通道从图像image[0].channels()到image[0].channels()+image[1].channels()-1计数
- hist: 输入的直方图,直方图的bin可以是密集(dense)或稀疏(sparse)
- backProject: 目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度
- ranges**: 直方图中每个维度bin的取值范围
- double scale=1: 可选输出反向投影的比例因子
迭代终止结构体TermCriteria
TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ))
示例意思:精度先达到1或者迭代次数先达到10次时,停止迭代
代码思路
读入一帧图像,如果无目标,则选中目标,计算目标直方图,求出反向投影图,Meanshift迭代,得到新的目标位置,输出图像并重新计算新的反向投影图
总体代码
结果对于我测试的视频不太好,但是在摄像头中识别自己的人脸还行,应该有地方仍存在问题,暂且贴在这,等到完美了再来改,个人感觉很依赖于滑动条调节的参数
#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<ctype.h>
using namespace std;
using namespace cv;
Mat image; //当前帧图像
Mat imageCopy; //用于拷贝的当前帧图像
Mat rectImage; //子图像
Point beginPoint; //矩形框起点
Point endPoint; //矩形框终点
bool leftButtonDownFlag = false; //左键单击后视频暂停播放的标志位
int frameCount = 0; //帧数统计
int trackCount = 0; //等于1时初始化直方图
void onMouse(int event, int x, int y, int flags, void* ustc); //鼠标回调函数
int main(int argc,char* argv[]) {
VideoCapture capture("C:\\Users\\14527\\Desktop\\Video\\emmm.AVI");
//VideoCapture capture(0);
int capture_fps = capture.get(CV_CAP_PROP_FPS); //获取视频帧率
int capture_count = capture.get(CV_CAP_PROP_FRAME_COUNT);
int capture_width = capture.get(CV_CAP_PROP_FRAME_WIDTH);
int capture_height = capture.get(CV_CAP_PROP_FRAME_HEIGHT);
cout << "视频帧率:" << capture_fps << endl;
cout << "视频帧数:" << capture_count << endl;
cout << "视频宽度:" << capture_width << endl;
cout << "视频高度:" << capture_height << endl;
int pauseTime = 1000 / capture_fps; //两幅画面中间间隔
VideoWriter writer("C:\\Users\\14527\\Desktop\\Video\\out.avi", CV_FOURCC('X', 'V', 'I', 'D'), capture_fps, Size(capture_width, capture_height));
namedWindow("Video");
setMouseCallback("Video", onMouse);
int vmin = 10, vmax = 256,smin = 30;//设置HSV中V和S的值
int hbinNum = 16;//灰度分级16
float hranges[] = { 40,250 };
const float* phranges = hranges;
bool backprojectMode = false;
namedWindow("Histogram", 0);
namedWindow("Video", 0);
createTrackbar("Vmin", "Video", &vmin, 256, 0);//createTrackbar函数的功能是在对应的窗口创建滑动条,滑动条Vmin,vmin表示滑动条的值,最大为256
createTrackbar("Vmax", "Video", &vmax, 256, 0);//最后一个参数为0代表没有调用滑动拖动的响应函数
createTrackbar("Smin", "Video", &smin, 256, 0);//vmin,vmax,smin初始值分别为10,256,30
Mat hsvImg;//HSV图像
capture >> image;
Mat hue, mask, hist, histImg = Mat::zeros(image.size(), image.type()),backproj;
Rect trackWindow;
while (true) {
if (!leftButtonDownFlag) //鼠标左键按下绘制矩形时,视频暂停播放
{
capture >> image;
frameCount++; //帧数
}
if (!image.data || waitKey(pauseTime + 30) == 27) //图像为空或Esc键按下退出播放
{
break;
}
if (trackCount>0) {
cvtColor(image, hsvImg, CV_BGR2HSV);
inRange(hsvImg, Scalar(0, smin, min(vmin, vmax)), Scalar(180, 256, max(vmin, vmax)), mask);
int ch[] = { 0,0 };
hue.create(hsvImg.size(), hsvImg.depth());//hue初始化为与hsv大小深度一样的矩阵
mixChannels(&hsvImg, 1, &hue, 1, ch, 1);//将hsv第一个通道(也就是色调)的数复制到hue中
if (trackCount == 1) {
histImg = Scalar::all(0);
Mat roi(hue, Rect(beginPoint, endPoint)), maskroi(mask, Rect(beginPoint, endPoint));
calcHist(&roi, 1, 0, maskroi, hist, 1, &hbinNum, &phranges);
normalize(hist, hist, 0, 255, CV_MINMAX);
trackCount++;
trackWindow = Rect(beginPoint, endPoint);
}
calcBackProject(&hue, 1, 0, hist, backproj, &phranges);
backproj &= mask;
meanShift(backproj, trackWindow, TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1));
if (backprojectMode) {
cvtColor(backproj, image, CV_GRAY2BGR);
}
rectangle(image, Point(trackWindow.x, trackWindow.y), Point(trackWindow.x + trackWindow.width, trackWindow.y + trackWindow.height), Scalar(0, 0, 255), 1, CV_AA);
trackCount++;
// writer << image;
}
imshow("Video", image);
}
waitKey(0);
return 0;
}
//鼠标回调函数
void onMouse(int event, int x, int y, int flags, void *ustc)
{
if (event == CV_EVENT_LBUTTONDOWN)
{
leftButtonDownFlag = true; //标志位
beginPoint = Point(x, y); //设置左键按下点的矩形起点
endPoint = beginPoint;
}
if (event == CV_EVENT_MOUSEMOVE && leftButtonDownFlag)
{
imageCopy = image.clone();
endPoint = Point(x, y);
if (beginPoint != endPoint)
{
//在复制的图像上绘制矩形
rectangle(imageCopy, beginPoint, endPoint, Scalar(0, 0, 255), 2);
}
imshow("Video", imageCopy);
}
if (event == CV_EVENT_LBUTTONUP)
{
leftButtonDownFlag = false;
Mat subImage = image(Rect(beginPoint, endPoint)); //子图像
rectImage = subImage.clone();
trackCount = 1;
//imshow("Sub Image", rectImage);
}
}