OpenCV3使用meanshift实现目标跟踪

@[C++|OpenCV]


  • OpenCV3使用meanshift实现目标跟踪
  • 用到的基本函数
  • mixchannels()
  • inrange()
  • calcHist()
  • normalize()
  • calcBackProject()
  • 迭代终止结构体TermCriteria
  • 代码思路
  • 总体代码


用到的基本函数

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);
    }
}