第二节 物体跟踪
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.]算法为每个上一个像素找到光流,因此:
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} [x′y′]=W⋅⎣⎡xy1⎦⎤
该方程具有单应性的齐次坐标。 它返回最终的增强相关系数,即模板图像和最终变形的输入图像之间的相关系数。 当给定的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 [A∗∣b∗]=argmin[A∣b]∑i∥dst[i]−Asrc[i]T−b∥2
其中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} [a11−a12a12a11b1b2]
当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)