Mean Shift算法(2)在OpenCV上的实现目标跟踪——直方图反向投影

直方图反向投影

     直方图反向投影的结果是一个概率分布图,表示一个指定图像片段出现在特定位置的概率。假设我们已经知道图像中某个物体的大致位置,就可以用概率分布图找到物体的准确位置。最可能出现的位置就是窗口中概率最大的位置。如果从一个可能的初始位置开始,在该位置周围反复移动,就可能找到物体所在的准确位置,这个实现方法称为均值漂移算法Mean Shift。MeanShift算法在视频跟踪领域应用广泛,最早由D.Comaniciu和P.Meer于2002年在P.A.M.I上首次提出:Mean Shift: A robust approachtoward feature space analysis[J], IEEE Trans. on Pattern Analysis and MachineIntellligence, 2002,5(24).

         函数 cv::meanShift 在给定反向投影和初始搜索窗口位置的情况下,用迭代方法寻找目标中心。当搜索窗口中心的移动小于某个给定值时或者函数已经达到最大迭代次数时停止迭代。 函数返回迭代次数。

int meanShift( InputArray probImage, CV_OUT CV_IN_OUT Rect& window, TermCriteria criteria );

参数说明:
probImage:概率分布图像,可以是目标直方图的反向投影(见 cvCalcBackProject)
Window:初始搜索窗口,可以是使用Rect定义ROI,定义目标区域,求其颜色直方图
Criteria:确定窗口搜索停止的准则,OpenCV实现该算法时定义了两个停止条件:迭代次数达到设置的最大值;窗口中心的漂移值小于某个设定的限值。

程序实现步骤:

  • 读入参考图像
  • 设置感兴趣区域(ROI)
  • 获取感兴趣区域的色调直方图
  • 读取新图像计算色调直方图的反向投影:新建直方图反向投影类实例,将色调直方图输入该实例
  • 使用meanShift算法在反向投影图中定位物体:设置初始搜索区域,设置迭代终止条件

让我们一步一步来吧!首先要了解什么是直方图反向投影?

       反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的特征。反向投影在某一位置的值就是原图对应位置像素值在原图像中的总数目。例如,有一张含有人脸图像,我们选中人脸的区域,求其特征,我们求该特征在这幅图像上的反向投影,得到一张投影图,使用统计学的语言,该反向投影图中存储的数值代表了原始图像中该像素属于人脸区域的概率。亮的区域是人脸区域的可能性更大,而暗的区域则表示更低的可能性。

举个简单的例子来帮助理解这段话的意思。例如灰度图像的像素值如下 
                                                           gray=\begin{vmatrix} 1 & 1 & 3 &5 \\ 3& 8& 8& 7\\ 2& 7& 9& 1\\ 1& 8& 5&8 \end{vmatrix}
             对图像进行直方图统计(bin指定的区间为[0,2],[3,5],[6,8],[9,11])如下所示: Histogram=5 4 6 1 
根据上述的直方图进行反向投影,得到反向投影图像像素值如下: 
                                    backProjection=\begin{vmatrix} 5 & 5 & 4 &5 \\ 4& 6& 6& 6\\ 5& 6& 1& 5\\ 5& 6& 4&6 \end{vmatrix}


        例如位置(0,0)上的像素值为1,对应的bin为[0,2),所以反向直方图在该位置上的值这个bin的值5,而在位置(3,3)上的像素为9,其在直方图中的统计为1,故其反向投影图像中的像素为1。
关于反向投影想了解更多的话可参见:https://blog.csdn.net/keith_bb/article/details/70154219

通过求出反向投影后,我们就得到的meanshift函数的第一个参量“InputArray probImage”。

int meanShift( InputArray probImage, CV_OUT CV_IN_OUT Rect& window, TermCriteria criteria );

反向投影在OpenCV中如何实现?主要依赖两个函数:

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用来进行统计的范围
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: 可选输出反向投影的比例因子

OpenCV实现:

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

 

https://blog.csdn.net/weixin_38312031/article/details/79632521

https://blog.csdn.net/iracer/article/details/48955151

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自定义Meanshift跟踪算法实现步骤如下: 1. 获取视频帧,并选择要跟踪的目标区域。 2. 将目标区域转换为HSV色彩空间,并计算出该区域的直方图。 3. 对于每个后续帧,首先将其转换为HSV色彩空间,然后使用反向投影算法将其与目标直方图进行比较。 4. 对于每个像素,计算该像素的概率,即它属于目标区域的概率。 5. 使用MeanShift算法来计算下一个目标位置。在此算法中,计算出目标区域的质心,并将其用作新的目标位置。重复该过程,直到质心不再移动。 6. 将新的目标位置用矩形框标记在视频帧上,并将其显示出来。 下面是C++代码实现: ```c++ #include <iostream> #include <opencv2/opencv.hpp> using namespace cv; using namespace std; int main(int argc, char** argv) { VideoCapture cap(0); if (!cap.isOpened()) { cout << "Error opening video stream or file" << endl; return -1; } // 选择目标区域 Rect trackWindow(0, 0, 0, 0); bool init = false; Mat frame, hsv, mask, hist, backproj; // 设置终止条件 TermCriteria termcrit(TermCriteria::EPS | TermCriteria::COUNT, 10, 1); // 设置HSV颜色范围 int hmin = 0, smin = 0, vmin = 0; int hmax = 180, smax = 255, vmax = 255; while (true) { cap >> frame; if (frame.empty()) break; // 将帧转换为HSV颜色空间 cvtColor(frame, hsv, COLOR_BGR2HSV); // 如果已经选择了初始目标区域,则执行跟踪 if (init) { // 计算反向投影 calcBackProject(&hsv, 1, 0, hist, backproj, &ranges); // 应用CAMShift算法来计算新的目标位置 meanShift(backproj, trackWindow, termcrit); // 绘制矩形框 rectangle(frame, trackWindow, Scalar(0, 0, 255), 3); } // 显示图像 imshow("Frame", frame); // 按下空格键来选择目标区域 if (waitKey(1) == ' ') { init = false; trackWindow = selectROI("Frame", frame, false, false); if (trackWindow.area() > 0) { Mat roi(hsv, trackWindow); calcHist(&roi, 1, 0, mask, hist, 1, &histSize, &ranges); normalize(hist, hist, 0, 255, NORM_MINMAX); init = true; } } } cap.release(); destroyAllWindows(); return 0; } ``` 需要注意的是,这个算法的效果可能不如OpenCV中的MeanShift算法,因为OpenCV中的算法使用了更复杂的技术来提高跟踪的准确性。但是,通过自定义算法,可以更好地理解MeanShift算法的原理和实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值