OpenCV 4.x API 详解与C++实例-物体跟踪

第二节 物体跟踪

OpenCV的video模块提供了几种基于光流的物体跟踪方法。

1、cv::buildOpticalFlowPyramid、cv::calcOpticalFlowPyrLK


1)cv::buildOpticalFlowPyramid:构造可以传递给calcOpticalFlowPyrLK的图像金字塔。

int cv::buildOpticalFlowPyramid (InputArray img,OutputArrayOfArrays pyramid,Size winSize,int maxLevel,bool withDerivatives = true,int pyrBorder = BORDER_REFLECT_101,int derivBorder = BORDER_CONSTANT,bool tryReuseInputImage = true)

函数返回构造金字塔中的层数。可以小于maxLevel。参数如下:

参数名称 参数描述
img 8位输入图像
pyramid 输出金字塔
winSize 光流算法的窗口大小。 必须不少于calcOpticalFlowPyrLK的winSize参数。 需要计算金字塔级别所需的填充。
maxLevel 从0开始的最大金字塔等级编号。
withDerivatives 设置为每个金字塔等级预计算梯度。 如果金字塔是在没有梯度的情况下构建的,那么calcOpticalFlowPyrLK将在内部对其进行计算。
pyrBorder 金字塔图层的边框模式。
derivBorder 梯度边框模式。
tryReuseInputImage 如果可能,将输入图像的ROI放入金字塔中。 您可以传递false来强制复制数据。

2)cv::calcOpticalFlowPyrLK:使用带有金字塔的迭代Lucas-Kanade方法计算稀疏特征集的光流。

void cv::calcOpticalFlowPyrLK(InputArray prevImg,InputArray nextImg,InputArray prevPts,InputOutputArray nextPts,OutputArray status,OutputArray err,Size winSize = Size(21, 21),int maxLevel = 3,TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),int flags = 0,double minEigThreshold = 1e-4)

该函数在金字塔中实现了Lucas-Kanade光流的稀疏迭代版本。 参见[Jean-Yves Bouguet. Pyramidal implementation of the affine lucas kanade feature tracker description of the algorithm. Intel Corporation, 5, 2001.]。 该功能与TBB库并行化。参数如下:

参数名称 参数描述
prevImg 由buildOpticalFlowPyramid构造的第一个8位输入图像或金字塔。
nextImg 与上一个相同的大小和类型的第二个输入图像或金字塔。
prevPts 需要寻找流量的2D点向量; 点坐标必须是单精度浮点数。
nextPts 二维点的输出矢量(具有单精度浮点坐标),其中包含计算出的第二个图像中输入要素的新位置; 传递OPTFLOW_USE_INITIAL_FLOW标志时,向量的大小必须与输入中的大小相同。
status 输出状态向量(无符号字符); 如果找到了对应特征的流程,则向量的每个元素都将设置为1,否则将其设置为0。
err 错误的输出向量; 向量的每个元素针对相应特征均设置为错误,可以在flags参数中设置错误度量的类型; 如果未找到流,则未定义错误(使用status参数查找此类情况)。
winSize 每个金字塔级别的搜索窗口大小。
maxLevel 从0开始的最大金字塔等级数; 如果设置为0,则不使用金字塔(单个级别);如果设置为1,则使用两个级别,依此类推; 如果将金字塔传递给输入,则算法将使用与金字塔一样多的级别,但不超过maxLevel。
criteria 参数,指定迭代搜索算法的终止条件(在指定的最大迭代次数criteria.maxCount之后,或者搜索窗口移动的距离小于criteria.epsilon时。
flags 操作标志:OPTFLOW_USE_INITIAL_FLOW使用存储在nextPts中的初始估计值; 如果未设置该标志,则将prevPts复制到nextPts并视为初始估计。OPTFLOW_LK_GET_MIN_EIGENVALS使用最小特征值作为误差度量(请参见minEigThreshold描述); 如果未设置该标志,则L1原始点与移动点之间的色块之间的距离除以像素中像素的数量 窗口,用作错误度量。
minEigThreshold 该算法计算光流方程的2x2法线矩阵的最小特征值(此矩阵在[[Jean-Yves Bouguet. Pyramidal implementation of the affine lucas kanade feature tracker description of the algorithm. Intel Corporation, 5, 2001.]除以窗口中的像素数; 如果此值小于minEigThreshold,则将滤除相应的功能,并且不对其流程进行处理,因此它可以消除坏点并提高性能。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    cv::VideoCapture cap("videos/vtest.avi");
    if(!cap.isOpened()){
        cerr << "cannot open camera\n";
        return EXIT_FAILURE;
    }

    cv::Mat frame,gray,grayPre,framePre,status,err;
    const int maxLevel = 3;
    vector<cv::Point2f> prevPts, nextPts;
    vector<cv::Mat> pyramid1,pyramid2;



    cap >> frame;
    if(frame.empty()){
        cerr << "grab first frame error.\n";
        return EXIT_FAILURE;
    }
    cv::cvtColor(frame,grayPre,cv::COLOR_BGR2GRAY);
    cv::Size subPixWinSize(10,10);
    cv::TermCriteria termcrit(cv::TermCriteria::COUNT|cv::TermCriteria::EPS,20,0.03);
    while(cap.isOpened()){
        cap >> frame;
        if(frame.empty()){
            cerr << "cannot grab frame from camera.\n";
            break;
        }
        cv::cvtColor(frame,gray,cv::COLOR_BGR2GRAY);

        // 检测触点
        goodFeaturesToTrack(gray, nextPts, 100, 0.01, 2.0);
        cornerSubPix(gray, nextPts, subPixWinSize, cv::Size(-1,-1), termcrit);
        goodFeaturesToTrack(grayPre, prevPts, 100, 0.01, 2.0);
        cornerSubPix(gray, prevPts, subPixWinSize, cv::Size(-1,-1), termcrit);

        // 构造流光金字塔
        cv::buildOpticalFlowPyramid(gray,pyramid1,cv::Size(21,21),maxLevel);
        cv::buildOpticalFlowPyramid(grayPre,pyramid2,cv::Size(21,21),maxLevel);

        // 使用LK流光算法检测
        cv::calcOpticalFlowPyrLK(pyramid1,pyramid2,prevPts,nextPts,status,err);

        gray.copyTo(grayPre);

        size_t i, k;
        for( i = k = 0; i < nextPts.size(); i++ ){
            cv::circle( frame, nextPts[i], 3, cv::Scalar(0,0,255), -1, 8);
        }

        // 显示图像
        cv::imshow("frame",frame);

        if(cv::waitKey(10) == 27){
            break;
        }
    }

    return 0;
}

在这里插入图片描述

2、cv::calcOpticalFlowFarneback


使用Gunnar Farneback的算法计算密集的光流。

void cv::calcOpticalFlowFarneback(InputArray prev,InputArray next,InputOutputArray flow,double pyr_scale,int levels,int winsize,int iterations,int poly_n,double poly_sigma,int flags)

该函数使用[Gunnar Farnebäck. Two-frame motion estimation based on polynomial expansion. In Image Analysis, pages 363–370. Springer, 2003.]算法为每个上一个像素找到光流,因此:

扫描二维码关注公众号,回复: 12583275 查看本文章

prev ( y , x ) ∼ next ( y + flow ( y , x ) [ 1 ] , x + flow ( y , x ) [ 0 ] ) \texttt{prev} (y,x) \sim \texttt{next} ( y + \texttt{flow} (y,x)[1], x + \texttt{flow} (y,x)[0]) prev(y,x)next(y+flow(y,x)[1],x+flow(y,x)[0])

参数如下:

参数名称 参数描述
prev 第一个8位单通道输入图像。
next 与上一个尺寸和类型相同的第二个输入图像。
flow 计算的流图像,其大小与prev相同,类型为CV_32FC2。
pyr_scale 参数,指定图像比例(<1)以为每个图像构建金字塔; pyr_scale = 0.5表示经典金字塔,其中下一层比上一层小两倍。
levels 包括初始图像的金字塔层数; level = 1表示不创建额外的图层,仅使用原始图像。
winsize 平均窗口大小; 较大的值可提高算法对图像噪声的鲁棒性,并为快速运动检测提供更多机会,但会产生更多的运动场模糊。
iterations 算法在每个金字塔级别执行的迭代次数。
poly_n 用于查找每个像素中的多项式展开的像素邻域的大小; 较大的值表示将使用更平滑的表面近似图像,从而产生更鲁棒的算法和更模糊的运动场,通常poly_n = 5或7。
poly_sigma 高斯标准偏差,用于平滑用作多项式展开基础的导数; 对于poly_n = 5,可以设置poly_sigma = 1.1,对于poly_n = 7,好的值应该是poly_sigma = 1.5。
flags 操作标志可以是以下各项的组合:OPTFLOW_USE_INITIAL_FLOW使用输入流作为初始流近似值。OPTFLOW_FARNEBACK_GAUSSIAN使用高斯winsize×winsize过滤器代替相同大小的盒式过滤器进行光流 估计 通常,此选项使z流量比使用箱式过滤器更精确,但速度较低; 通常,应将高斯窗口的winsize设置为较大的值,以实现相同级别的鲁棒性。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;


int main(int argc, char* argv[])
{
    cv::Mat res, framePre, frameNext, frameNextOriginal, frameNextOriginalC;

    // 读取视频文件
    cv::VideoCapture cap;
    cap.open("videos/vtest.avi");
    if(!cap.isOpened()){
        cerr << "cannot open video.\n";
        return EXIT_FAILURE;
    }

    cv::namedWindow("video", cv::WINDOW_AUTOSIZE);

    // 读取第一帧图像
    cap >> framePre;

    // 转换成灰度图像
    cvtColor(framePre, framePre, COLOR_BGR2GRAY);
    cv::Size tamano((int)cap.get(cv::CAP_PROP_FRAME_WIDTH), (int)cap.get(cv::CAP_PROP_FRAME_HEIGHT));

    while(cap.isOpened()) {

        // 读取下一帧图像
        cap >> frameNext;
        if (frameNext.empty()) {
            cerr << "cannot grab frame from video.\n";
            break;
        }

        frameNext.copyTo(frameNextOriginalC);

        // 转换成灰度图像
        cvtColor(frameNext, frameNext, COLOR_BGR2GRAY);
        frameNext.copyTo(frameNextOriginal);

        // 计算流光
        cv::calcOpticalFlowFarneback(framePre, frameNext, res, .4, 1, 12, 2, 8, 1.2, 0);

        // 绘制物体运动方向
        for (int y = 0; y < frameNext.rows; y += 5) {
            for (int x = 0; x < frameNext.cols; x += 5)
            {
                // get the flow from y, x position * 3 for better visibility
                const Point2f flowatxy = res.at<Point2f>(y, x) * 1;
                // 绘制流光方向
                line(frameNextOriginalC, Point(x, y), Point(cvRound(x + flowatxy.x), cvRound(y + flowatxy.y)), Scalar(255, 0, 0));
                // 绘制原始点
                circle(frameNextOriginalC, Point(x, y), 1, Scalar(0, 0, 0), -1);
            }
        }
        frameNextOriginal.copyTo(framePre);
        imshow("video", frameNextOriginalC);
        if (cv::waitKey(1) == 27) {
            break;
        }
    }
    cap.release();
    return 0;
}

在这里插入图片描述

3、cv::CamShift


查找对象的中心,大小和方向。

RotatedRect cv::CamShift(InputArray probImage,Rect& window,TermCriteria criteria)

参数如下:

参数名称 参数描述
probImage 对象直方图的反向投影。 请参阅calcBackProject。
window 初始搜索窗口。
criteria 底层meanShift的停止条件。返回值(在旧的接口中)CAMSHIFT收敛所需的迭代次数该函数实现CAMSHIFT对象跟踪算法 。首先,它使用meanShift查找对象中心,然后调整窗口大小并找到最佳旋转角度。 该函数返回旋转后的矩形结构,其中包括对象的位置,大小和方向。 可以使用RotatedRect :: boundingRect()获得搜索窗口的下一个位置。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
        cv::VideoCapture cap("videos/football-in-motion.mp4");
//    cv::VideoCapture cap(0);
    if(!cap.isOpened()){
        cerr << "cannot open video\n";
        return EXIT_FAILURE;
    }

    cv::namedWindow("video");

    cv::Mat objectFrame,frame,imageHSV,calcBackImage,dstHist,objectHSV;
    bool inited = false;
    //保存目标轨迹
    std::vector<cv::Point> pt;

    //直方图
    int histSize = 200;
    float histR[] = { 0,255 };
    const float *histRange = histR;
    int channels[] = { 0,1,2 };

    // 终止条件
    cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER +
                              cv::TermCriteria::EPS, 10, 1);
    cv::Rect rect;
    while(true){
        cap >> frame;
        if(frame.empty()){
            break;
        }
        cv::imshow("video",frame);
        // 选择所要跟踪的对象
        if(!inited && cv::waitKey(10) == 's'){
            rect = cv::selectROI("video",frame);
            objectFrame = frame(rect).clone();
            cv::cvtColor(objectFrame,objectHSV,cv::COLOR_BGR2HSV);
            //直方图计算
            cv::calcHist(&objectHSV, 3, channels, cv::Mat(), dstHist, 1, &histSize, &histRange, true, false);
            cv::normalize(dstHist, dstHist, 0, 255, cv::NORM_MINMAX);
            inited = true;
        }

        if(!inited){
            continue;
        }

        // 转换HSV颜色空间
        cvtColor(frame, imageHSV,  cv::COLOR_BGR2HSV);
        //反向投影
        cv::calcBackProject(&imageHSV, 3, channels,
                            dstHist, calcBackImage, &histRange);

        // 跟踪
        cv::CamShift(calcBackImage, rect, criteria);
        objectHSV = imageHSV(rect);
        // 更新模板
        cv::Mat imageROI = imageHSV(rect);
        // 计算直方图
        cv::calcHist(&imageROI, 2, channels, cv::Mat(),
                     dstHist, 1, &histSize, &histRange);
        //归一化
        cv::normalize(dstHist, dstHist, 0.0, 1.0, cv::NORM_MINMAX);

        //目标绘制
        cv::rectangle(frame, rect, cv::Scalar(255, 0, 0), 3);

        // 绘制目标运动轨迹
        pt.push_back(cv::Point(rect.x + rect.width / 2,
                               rect.y + rect.height / 2));
        for (size_t i = 0; i < pt.size() - 1; i++)
        {
            cv::line(frame, pt[i], pt[i + 1], cv::Scalar(0, 0, 255), 2.5);
        }

        cv::imshow("video",frame);

        if(cv::waitKey(10) == 27){
            break;
        }
    }

    return 0;
}

在这里插入图片描述

4、cv::computeECC、cv::findTransformECC


1)cv::computeECC:计算两个图像之间的增强的相关系数值。

double cv::computeECC(InputArray templateImage,InputArray inputImage,InputArray inputMask = noArray())

参数名称 参数描述
templateImage 单通道模板图像; CV_8U或CV_32F阵列。
inputImage 扭曲单通道输入图像以提供类似于templateImage的图像,其类型与templateImage相同。
inputMask 一个可选的掩码,用于指示inputImage的有效值。

**2)cv::findTransformECC:**根据ECC准则[Georgios D Evangelidis and Emmanouil Z Psarakis. Parametric image alignment using enhanced correlation coefficient maximization. Pattern Analysis and Machine Intelligence, IEEE Transactions on, 30(10):1858–1865, 2008.]查找两个图像之间的几何变换(扭曲)。

double cv::findTransformECC(InputArray templateImage,InputArray inputImage,InputOutputArray warpMatrix,int motionType,TermCriteria criteria,InputArray inputMask,int gaussFiltSize)

函数根据ECC准则估算最佳变换(warpMatrix),即:

warpMatrix = arg ⁡ max ⁡ W ECC ( templateImage ( x , y ) , inputImage ( x ′ , y ′ ) ) \texttt{warpMatrix} = \arg\max_{W} \texttt{ECC}(\texttt{templateImage}(x,y),\texttt{inputImage}(x',y')) warpMatrix=argmaxWECC(templateImage(x,y),inputImage(x,y))

其中, [ x ′ y ′ ] = W ⋅ [ x y 1 ] \begin{bmatrix} x' \\ y' \end{bmatrix} = W \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} [xy]=Wxy1

该方程具有单应性的齐次坐标。 它返回最终的增强相关系数,即模板图像和最终变形的输入图像之间的相关系数。 当给定的3×3矩阵的motionType = 0、1或2时,将忽略第三行。

与findHomography和estimateRigidTransform不同,函数findTransformECC实现了基于强度相似性的基于区域的对齐方式。 从本质上讲,该函数将更新初始转换,以使图像大致对齐。 如果缺少此信息,则将身份扭曲(统一矩阵)用作初始化。 注意,如果图像经历强的位移/旋转,则需要大致对准图像的初始变换(例如,简单的欧几里德/相似性变换,其允许图像近似地示出相同的图像内容)。 在第二张图像中使用反扭曲来拍摄接近第一张图像的图像,即,将标志WARP_INVERSE_MAP与warpAffine或warpPerspective一起使用。

参数名称 参数描述
templateImage 单通道模板图像; CV_8U或CV_32F阵列。
inputImage 单通道输入图像,应使用最终的warpMatrix进行变形,以提供类似于templateImage的图像,其类型与templateImage相同。
warpMatrix 浮点2×3或3×3映射矩阵(扭曲)。
motionType 参数,指定运动类型:MOTION_TRANSLATION设置平移运动模型; warpMatrix为2×3,其中前2×2部分为单位矩阵,其余两个参数为估算值。MOTION_EUCLIDEAN将欧几里德(刚性)变换设置为运动模型; 估计三个参数; warpMatrix为2×3。** MOTION_AFFINE 设置仿射运动模型(DEFAULT); 估计了六个参数; warpMatrix为2×3。 MOTION_HOMOGRAPHY **将单应性设置为运动模型; 估计八个参数;“ warpMatrix”为3×3。
criteria 参数,指定ECC算法的终止标准; criteria.epsilon定义了两次迭代之间相关系数增量的阈值(负准则。epsilon使criteria.maxcount成为唯一终止准则)。 默认值显示在上面的声明中。
inputMask 一个可选的掩码,用于指示inputImage的有效值。
gaussFiltSize 一个可选值,指示高斯模糊滤波器的大小; (默认值:5)
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main()
{
    //读取图像
    Mat img_01 = imread("images/tsucuba_right.png");
    Mat img_02 = imread("images/tsucuba_left.png");

    //定义运动模型
    const int warp_mode = MOTION_EUCLIDEAN;
    //建立变化矩阵
    Mat warp_matrix;
    if (warp_mode == MOTION_HOMOGRAPHY)
        warp_matrix = Mat::eye(3, 3, CV_32F);
    else
        warp_matrix = Mat::eye(2, 3, CV_32F);
    //最大迭代数
    int number_of_iterations = 5000;
    //迭代精度
    double termination_eps = 1e-10;
    //迭代标准
    TermCriteria criteria(TermCriteria::COUNT + TermCriteria::EPS,
                          number_of_iterations, termination_eps);
    // 分离通道
    vector<cv::Mat> img_01_channels,img_02_channels,results;
    split(img_01,img_01_channels);
    split(img_02,img_02_channels);


    //计算对齐后的图像
    Mat result;
    for(size_t i = 0;i < 3;i++){
        //计算变换矩阵
        findTransformECC(img_01_channels[i],img_02_channels[i],warp_matrix,warp_mode,criteria);
        Mat temp;
        if (warp_mode != MOTION_HOMOGRAPHY){
            warpAffine(img_02_channels[i], temp, warp_matrix, img_01.size(),
                    INTER_LINEAR + WARP_INVERSE_MAP);
        }
        else{
            warpPerspective(img_02_channels[i], temp, warp_matrix, img_01.size(),
                    INTER_LINEAR + WARP_INVERSE_MAP);
        }
        results.push_back(temp);
    }

    merge(results,result);

    imshow("image01",img_01);
    imshow("image02",img_02);
    imshow("result",result);
    waitKey();

    return 0;
}

在这里插入图片描述

5、cv::estimateRigidTransform


计算两个2D点集之间的最佳仿射变换。

Mat cv::estimateRigidTransform(InputArray src,InputArray dst,bool fullAffine)

参数名称 参数描述
src 存储在std :: vector或Mat或存储在Mat。
dst 与A大小或类型相同的第二个输入2D点集,或另一个图像。
fullAffine 如果为true,则该函数将找到最佳仿射变换,而没有其他限制(6个自由度)。 否则,要选择的转换类别仅限于平移,旋转和均匀缩放(4个自由度)的组合。

该函数找到最佳仿射变换[A | b](一个2 x 3浮点矩阵),该仿射变换在以下情况之间最佳地近似仿射变换:

  • 两点套

  • 两个光栅图像。 在这种情况下,该函数首先在src映像中找到一些功能,然后 在dst图片中找到相应的特征。 在那之后,问题减少到第一个 案件。

对于点集,问题的表述如下:您需要找到2x2矩阵A和2x1向量b:

[ A ∗ ∣ b ∗ ] = a r g min ⁡ [ A ∣ b ] ∑ i ∥ dst [ i ] − A src [ i ] T − b ∥ 2 [A^*|b^*] = arg \min _{[A|b]} \sum _i \| \texttt{dst}[i] - A { \texttt{src}[i]}^T - b \| ^2 [Ab]=argmin[Ab]idst[i]Asrc[i]Tb2

其中src [i]和dst [i]分别是src和dst中的第i个点,[A | b]可以是任意的(当fullAffine = true时)或具有以下形式

[ a 11 a 12 b 1 − a 12 a 11 b 2 ] \begin{bmatrix} a_{11} & a_{12} & b_1 \\ -a_{12} & a_{11} & b_2 \end{bmatrix} [a11a12a12a11b1b2]

当fullAffine = false时。

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main()
{
    //读取图像
    Mat img_01 = imread("images/tsucuba_right.png");
    Mat img_02 = imread("images/tsucuba_left.png");

    vector<Mat> img1_channels,img2_channels,results;
    split(img_01,img1_channels);
    split(img_02,img2_channels);

    Mat dst;
    for(size_t i = 0;i < 3;i++){
        Mat temp1;
        Mat m1 = estimateRigidTransform(img1_channels[i],img2_channels[i],false);
        warpAffine(img1_channels[i],temp1,m1,img_01.size());
        results.push_back(temp1);
    }
    merge(results,dst);

    imshow("src1",img_01);
    imshow("src2",img_02);
    imshow("dst",dst);

    waitKey();

    return 0;
}

在这里插入图片描述

当fullAffine=true时,结果如下:

在这里插入图片描述

6、cv::meanShift


在反投影图像上找到对象。

int cv::meanShift(InputArray probImage,Rect & window,TermCriteria criteria)

参数名称 参数描述
probImage 对象直方图的反向投影。 有关详细信息,请参见calcBackProject。
window 初始搜索窗口。
criteria 迭代搜索算法的停止条件。返回:CAMSHIFT收敛的迭代次数。该函数实现了迭代对象搜索算法。它接受对象的输入反投影和初始位置。计算背面投影图像的窗口中的质心,并且搜索窗口中心移至质心。重复该过程,直到完成指定的迭代次数criteria.maxCount或直到窗口中心偏移小于criteria.epsilon。该算法在CamShift内部使用,与CamShift不同,搜索窗口的大小或方向在搜索过程中不会改变。您可以简单地将calcBackProject的输出传递给此函数。但是,如果您对反向投影进行预滤波并消除噪点,则可以获得更好的结果。例如,您可以通过以下方式执行此操作:使用findContours检索连接的组件,丢弃面积较小的轮廓(ContourArea),并使用drawContours渲染其余轮廓。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
        cv::VideoCapture cap("videos/football-in-motion.mp4");
//    cv::VideoCapture cap(0);
    if(!cap.isOpened()){
        cerr << "cannot open video\n";
        return EXIT_FAILURE;
    }

    cv::namedWindow("video");

    cv::Mat objectFrame,frame,imageHSV,calcBackImage,dstHist,objectHSV;
    bool inited = false;
    //保存目标轨迹
    std::vector<cv::Point> pt;

    //直方图
    int histSize = 200;
    float histR[] = { 0,255 };
    const float *histRange = histR;
    int channels[] = { 0,1,2 };

    // 终止条件
    cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER +
                              cv::TermCriteria::EPS, 10, 1);
    cv::Rect rect;
    while(true){
        cap >> frame;
        if(frame.empty()){
            break;
        }


        cv::imshow("video",frame);
        if(!inited && cv::waitKey(10) == 's'){
            rect = cv::selectROI("video",frame);
            objectFrame = frame(rect).clone();
            cv::cvtColor(objectFrame,objectHSV,cv::COLOR_BGR2HSV);
            //直方图计算
            cv::calcHist(&objectHSV, 3, channels, cv::Mat(), dstHist, 1, &histSize, &histRange, true, false);
            cv::normalize(dstHist, dstHist, 0, 255, cv::NORM_MINMAX);
            inited = true;
        }

        if(!inited){
            continue;
        }

        // 转换HSV颜色空间
        cvtColor(frame, imageHSV,  cv::COLOR_BGR2HSV);
        //反向投影
        cv::calcBackProject(&imageHSV, 3, channels,
                            dstHist, calcBackImage, &histRange);

        // 跟踪
        cv::meanShift(calcBackImage, rect, criteria);
        objectHSV = imageHSV(rect);
        // 更新模板
        cv::Mat imageROI = imageHSV(rect);
        // 计算直方图
        cv::calcHist(&imageROI, 2, channels, cv::Mat(),
                     dstHist, 1, &histSize, &histRange);
        //归一化
        cv::normalize(dstHist, dstHist, 0.0, 1.0, cv::NORM_MINMAX);

        //目标绘制
        cv::rectangle(frame, rect, cv::Scalar(255, 0, 0), 3);

        // 绘制目标运动轨迹
        pt.push_back(cv::Point(rect.x + rect.width / 2,
                               rect.y + rect.height / 2));
        for (size_t i = 0; i < pt.size() - 1; i++)
        {
            cv::line(frame, pt[i], pt[i + 1], cv::Scalar(0, 0, 255), 2.5);
        }

        cv::imshow("video",frame);

        if(cv::waitKey(10) == 27){
            break;
        }
    }

    return 0;
}

在这里插入图片描述

7、cv::readOpticalFlow、cv::writeOpticalFlow


**1)cv::readOpticalFlow:**函数readOpticalFlow从文件加载流字段,并将其作为单个矩阵返回。 结果Mat的类型为CV_32FC2-浮点2通道。 第一通道对应于水平方向(u)上的流,第二通道对应于垂直方向(v)上的流。

Mat cv::readOpticalFlow (const String & path)

2)cv::writeOpticalFlow:该函数将流字段存储在文件中,成功则返回true,否则返回false。 流场必须是2通道浮点矩阵(CV_32FC2)。 第一通道对应于水平方向(u)上的流,第二通道对应于垂直方向(v)上的流。

bool cv::writeOpticalFlow(const String & path,InputArray flow)

猜你喜欢

转载自blog.csdn.net/wujuxKkoolerter/article/details/113728449