c++实现运动目标的追踪

一、头文件

命名空间:using namespace cv;

一般来说OpenCV2,使用哪一模块的内容就添加哪一模块的头文件,例如:
​
1、如果使用了Mat类,属于core模块。
​
那么头文件就是#include <opencv2/core/core.hpp>
​
2、如果使用了imread()或者imshow()函数输入与显示图像进行图形用户交互。
​
那么头文件就是#include <opencv2/highgui/highgui.hpp>
​
但是有一种更直接的方法,就是只是用使用头文件#include <opencv2/opencv.hpp>,为什么可以这样子用呢?因为OpenCV2所有模块的hpp文件都是包含在了opencv2文件夹内的。
​
但是要注意的是,如果直接使用头文件#include <opencv2/opencv.hpp>,每次编译程序时,会加载所有模块,有些模块是你不需要的,浪费时间,降低代码性能。
​
因此,对于新手,建议先使用头文件#include <opencv2/opencv.hpp>,这样不会因为头文件的问题苦恼,代码能运行就好了,这点时间还不算什么大事。而对于使用了一段时间OpenCV的程序员,此时,需要提高程序的性能,节省代码的处理时间,建议使用各个模块的头文件,也方便熟悉opencv的各个模块内容。

二、相关知识

1.基础

    Mat img=imread("F:/picture/tp1.jpg");
    namedWindow("showimg", WINDOW_NORMAL);
    imshow("showimg", img);
    waitKey(0);

Mat是一个数据结构,用来存图像,应该是用于存储像素点。

imread是读取图像的命令 里面的地址可以是单“/”也可以是双“\\”。

imshow("窗口名",上面定义的Mat结构名)用来展示图片

但是会发现展示的图片闪现一瞬间就会消失,那么用waitKey(0)来暂停,里面的其它参数,据说是用来控制输入一个键盘命令之后等待多长时间后会响应,具体内容需要时再进行查询。

同时还会发现图片被放大了,面积比较大的图片显示的不全,那么就用到namedwindow("窗口名—和imshow中的相同",WINDOW_NORMAL);如此就可以显示全面。

2、opencv调用摄像头

(1)调用视频/摄像头

调用视频/摄像头也需要一个类:videocapture 名字(参数)

参数如果是数字那么就代表摄像头,0代表电脑的内部摄像头,如果电脑有外部摄像头有一个编号为1,两个为1、2依次类推,数字就代表了几号摄像头,还可以是地址,就代表了所输入地址的视频。

videocapture capture(0);//电脑内部摄像头
videpcapture capture(1);//电脑外接一号摄像头
videocapture capture("F:/视频/jilu.mp4");//当前位置的视频

(2)错误提醒

判断视频是否正确打开

if(!capture.isOpen())
{
    cout<<"video open is error"<<endl;
    return 0;
}

(3)将视频转化为一帧帧的图片

视频的呈现基础仍旧是图片,所以我们仍要定义图片的数据结构Mat,并将视频的帧逐次传入图片结构并用imshow呈现出来。

注意错误判断和设置退出,waitKey参数里的时间由自己设置即可,10不一定是10s应该是10个单位时间。

Mat frame;
while(true)//每循环一次传入一帧
{
    capture>>frame;//将视频中的一帧传入图片结构frame
    if(frame.empty())//判断帧传入是否正确
    {
        cout<<"frame is error"<<end;
        break;
    }
    imshow("video",frame);
   //每10个单位时间读取一次键盘操作,如果读取到esc(ascii=27)那么就退出
    if(waitKey(10)==27)
    {
        cout<<"esc come then exit"<<endl;
        break;
    }
}

参考链接:

没有任何技巧的OpenCV运动捕捉教程_哔哩哔哩_bilibili

3.鼠标回调函数

(1)函数原型:

 void setMousecallback(const string& winname, MouseCallback onMouse, void* userdata=0)

winname:窗口的名字 ​ onMouse:鼠标响应函数,回调函数。指定窗口里每次鼠标时间发生的时候,被调用的函数指针。 这个函数的原型应该为:

void on_Mouse(int event, int x, int y, int flags, void* param);

userdate:传给回调函数的参数 一般默认为NULL即可

(2)鼠标响应函数

函数原型:

void on_Mouse(int event, int x, int y, int flags, void* param);
event是 CV_EVENT_*变量之一,代表鼠标进行的操作事件
x和y是鼠标指针在图像坐标系的坐标(不是窗口坐标系) 
flags是CV_EVENT_FLAG的组合, param是用户定义的传递到setMouseCallback函数调用的参数。

鼠标操作事件:

event://简单鼠标操作
EVENT_MOUSEMOVE          0 //滑动
EVENT_LBUTTONDOWN        1 //左键点击
EVENT_RBUTTONDOWN        2 //右键点击
EVENT_MBUTTONDOWN        3 //中键点击
EVENT_LBUTTONUP          4 //左键放开
EVENT_RBUTTONUP          5 //右键放开
EVENT_MBUTTONUP          6 //中键放开
EVENT_LBUTTONDBLCLK      7 //左键双击
EVENT_RBUTTONDBLCLK      8 //右键双击
EVENT_MBUTTONDBLCLK      9 //中键双击
flags://也是鼠标操作事件更为复杂的操作
EVENT_FLAG_LBUTTON       10 //左鍵拖曳
EVENT_FLAG_RBUTTON       11 //右鍵拖曳
EVENT_FLAG_MBUTTON       12 //中鍵拖曳
EVENT_FLAG_CTRLKEY       13 //(8~15)按Ctrl不放事件
EVENT_FLAG_SHIFTKEY      14 //(16~31)按Shift不放事件
EVENT_FLAG_ALTKEY        15 //(32~39)按Alt不放事件

该项目中鼠标响应函数涉及的知识点:

①:Point 点的模板类

opencv中Point操作
​
为了描述图像中的点,opencv中提供了点的模板类,分为2维点模板类Point_和3维点模板类Point3_。Point_通过2维图像平面中的x和y坐标确定点的位置,Point3_通过3维立体图像中的x、y、z坐标确定点的位置。对于点的坐标的类型可以是int、double、float类型,下面是源代码中的定义:
​
typedef Point_ Point2i;
typedef Point2i Point;
typedef Point_ Point2f;
typedef Point_ Point2d;
typedef Point3_ Point3i;
typedef Point3_ Point3f;
typedef Point3_ Point3d;
​
由上面的定义我们可以发现Point_、Point2i、Point互相等价,所以为了方便我们在定义整型点的时候会直接使用Point。同样,Point_与 Point2f等价,Point_ 与Point2d等价,Point3_与 Point3i等价,Point3_ 与Point3f等价,Point3_与 Point3d等价,这样的定义会方便用户的使用。另外可以转换点的坐标到指定的类型,对于浮点型的点向整型的点进行转换采用的是四舍五入的方法。
实例化一个类也是非常方便的,用法如下(以2D整型坐标点为例):
​
Point point;//创建一个2D点对象
point.x = 10;//初始化x坐标值
point.y = 8;//初始化y坐标值
或者
Point point = Point(10, 8);
​
这些点的类可以实现运算操作(三维点还支持向量运算和比较运算):
​
pt1 = pt2 + pt3;
pt1 = pt2 - pt3;
pt1 = pt2 * a;
pt1 = a * pt2;
pt1 += pt2;
pt1 -= pt2;
pt1 *= a;

②:Rect矩形类:

//如果创建一个Rect对象rect(100, 50, 50, 100),那么rect会有以下几个功能:
rect.area();     //返回rect的面积 5000
rect.size();     //返回rect的尺寸 [50 × 100]
rect.tl();       //返回rect的左上顶点的坐标 [100, 50]
rect.br();       //返回rect的右下顶点的坐标 [150, 150]
rect.width();    //返回rect的宽度 50
rect.height();   //返回rect的高度 100
rect.contains(Point(x, y));  //返回布尔变量,判断rect是否包含Point(x, y)点
 
//还可以求两个矩形的交集和并集
rect = rect1 & rect2;
rect = rect1 | rect2;
//同样可以类似与+= *=等操作
//比如:
rect&=rect2;
rect|=rect2;
 
//还可以对矩形进行平移和缩放  
rect = rect + Point(-100, 100); //平移,也就是左上顶点的x坐标-100,y坐标+100
rect = rect + Size(-100, 100);  //缩放,左上顶点不变,宽度-100,高度+100
 
//还可以对矩形进行对比,返回布尔变量
rect1 == rect2;
rect1 != rect2;
 
//OpenCV里貌似没有判断rect1是否在rect2里面的功能,所以自己写一个吧
bool isInside(Rect rect1, Rect rect2)
{
    return (rect1 == (rect1&rect2));
}
 
//OpenCV貌似也没有获取矩形中心点的功能,还是自己写一个
//cvRound是opencv里返回跟参数最接近的整数值的函数
Point getCenterPoint(Rect rect)
{
    Point cpt;
    cpt.x = rect.x + cvRound(rect.width/2.0);
    cpt.y = rect.y + cvRound(rect.height/2.0);
    return cpt;
}
 
//围绕矩形中心缩放
Rect rectCenterScale(Rect rect, Size size)
{
    rect = rect + size; 
    Point pt;
    pt.x = cvRound(size.width/2.0);
    pt.y = cvRound(size.height/2.0);
    return (rect-pt);
}

鼠标响应函数中尝试用的结构:

switch()case:break;

例如:

switch(event)
    {
    case CV_EVENT_LBUTTONDOWN:
        origin = Point(x, y);
        selection = Rect(x, y, 0, 0);
        selectobject = true;
        break;
    case CV_EVENT_LBUTTONUP:
        selectobject = false;
        if (selection.width > 0 && selection.height > 0)
        {
            trackobject = -1;
        }
        break;
    }

(3)鼠标响应函数setMouseCallback类型不匹配问题

添加转换函数,不直接使用类中的鼠标响应函数,而是在外面在加一层外壳

//capture_move.h   capture_move类
void on_MouseHandle(int event, int x, int y, int falg, void *param,Mat frame);//鼠标回调函数
//main.cpp
//转换函数
void mouese(int event, int x, int y, int falg, void* param)
{
    movement.on_MouseHandle(event, x, y, falg, param, frame);
}
int main()
{
    setMouseCallback("capture_movement",mouese, NULL);
}

4.追踪操作

(1)copyTo()

frame.copyTo(image);

将frame图像拷贝到image如果两个矩阵的大小本就相同,那么就直接把image变成frame的样子,直接复制,如果空间大小不同,则重新分配内存进行拷贝

(2)cvtColor()

函数原型:

void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0 );

src:原图像

dst:目标图像

code:转换标识

dstcn:目标图像通道数,如果取值为0,则由src和code决定,默认为0

函数功能:转换图片类型,图片类型都有啥参考知识集锦-图片色彩模式

cvtColor(image, frame, COLOR_BGR2HSV);
//将原图像image从BGR模式转化为HSV模式并存入frame中

code参数:原颜色空间2目标颜色空间

//RGB和BGR(opencv默认的彩色图像的颜色空间是BGR)颜色空间的转换
cv::COLOR_BGR2RGB
cv::COLOR_RGB2BGR
cv::COLOR_RGBA2BGRA
cv::COLOR_BGRA2RGBA
//向RGB和BGR图像中增添alpha通道
cv::COLOR_RGB2RGBA
cv::COLOR_BGR2BGRA
//从RGB和BGR图像中去除alpha通道
cv::COLOR_RGBA2RGB
cv::COLOR_BGRA2BGR
//从RBG和BGR颜色空间转换到灰度空间
cv::COLOR_RGB2GRAY
cv::COLOR_BGR2GRAY
cv::COLOR_RGBA2GRAY
cv::COLOR_BGRA2GRAY
//从灰度空间转换到RGB和BGR颜色空间
cv::COLOR_GRAY2RGB
cv::COLOR_GRAY2BGR
cv::COLOR_GRAY2RGBA
cv::COLOR_GRAY2BGRA
//RGB和BGR颜色空间与BGR565颜色空间之间的转换
cv::COLOR_RGB2BGR565
cv::COLOR_BGR2BGR565
cv::COLOR_BGR5652RGB
cv::COLOR_BGR5652BGR
cv::COLOR_RGBA2BGR565
cv::COLOR_BGRA2BGR565
cv::COLOR_BGR5652RGBA
cv::COLOR_BGR5652BGRA
//灰度空间域BGR565之间的转换
cv::COLOR_GRAY2BGR555
cv::COLOR_BGR5552GRAY
//RGB和BGR颜色空间与CIE XYZ之间的转换
cv::COLOR_RGB2XYZ
cv::COLOR_BGR2XYZ
cv::COLOR_XYZ2RGB
cv::COLOR_XYZ2BGR
//RGB和BGR颜色空间与uma色度(YCrCb空间)之间的转换
cv::COLOR_RGB2YCrCb
cv::COLOR_BGR2YCrCb
cv::COLOR_YCrCb2RGB
cv::COLOR_YCrCb2BGR
//RGB和BGR颜色空间与HSV颜色空间之间的相互转换
cv::COLOR_RGB2HSV
cv::COLOR_BGR2HSV
cv::COLOR_HSV2RGB
cv::COLOR_HSV2BGR
//RGB和BGR颜色空间与HLS颜色空间之间的相互转换
cv::COLOR_RGB2HLS
cv::COLOR_BGR2HLS
cv::COLOR_HLS2RGB
cv::COLOR_HLS2BGR
//RGB和BGR颜色空间与CIE Lab颜色空间之间的相互转换
cv::COLOR_RGB2Lab
cv::COLOR_BGR2Lab
cv::COLOR_Lab2RGB
cv::COLOR_Lab2BGR
//RGB和BGR颜色空间与CIE Luv颜色空间之间的相互转换
cv::COLOR_RGB2Luv
cv::COLOR_BGR2Luv
cv::COLOR_Luv2RGB
cv::COLOR_Luv2BGR
//Bayer格式(raw data)向RGB或BGR颜色空间的转换
cv::COLOR_BayerBG2RGB
cv::COLOR_BayerGB2RGB
cv::COLOR_BayerRG2RGB
cv::COLOR_BayerGR2RGB
cv::COLOR_BayerBG2BGR
cv::COLOR_BayerGB2BGR
cv::COLOR_BayerRG2BGR
cv::COLOR_BayerGR2BGR

(3)inRange()

函数功能:

opencv中的inRange()函数可实现二值化功能,更关键的是可以同时针对多通道进行操作,使用起来非常方便!主要是将在两个阈值内的像素值设置为白色(255),而不在阈值区间内的像素值设置为黑色(0),该功能类似于之间所讲的双阈值化操作。
对于多通道数组每个通道的像素值都必须在规定的阈值范围内!
常用于提取具有某种色彩范围的区域。

函数原型:

void inRange(InputArray src, InputArray lowerb,InputArray upperb, OutputArray dst);

src:原图像

lowerb:包含下边界的数组或标量

upperb:包含上边界的数组或标量

dst:目标图像

例子:

inRange(hsv, Scalar(0, 30, 10), Scalar(180, 256, 256), mask);
//将hsv图像在这个范围内的设置为白色将不在这个范围内的设置为黑色并存入目标图像mask中,mask也被称为掩膜
//scalar是一个四个值的结构体,不足四个时后面值默认为0,长用于存储通道

应用参考知识集锦-HSV色彩空间提取颜色范围

(4)Mat的creat()

功能:构建特定的Mat图像

void Mat::create(int _rows, int _cols, int _type)
void Mat::create(Size _sz, int _type)
void Mat::create(int ndims, const int* sizes, int type)
1)ndims:新的数组维数
2)rows :新数组的行数
3)cols :列数
4)Size :新矩阵的尺寸
5)type :新的矩阵类型  //type(CV_8UC1,CV_16SC1,CV_32FC3)

例子:

hue.create(hsv.size(), hsv.depth());
//创建一个和hsv图像相同大小,相同深度/类型的hue图像

(5)mixChannels()

功能:实现复杂通道的组合以及图像之间通道的复制(不是互换)

函数原型:

void cv::mixChannels    (   
    InputArrayOfArrays  src,
    InputOutputArrayOfArrays    dst,
    const std::vector< int > &  fromTo 
)   
第一个参数:输入矩阵
第二个参数:输出矩阵
第三个参数:复制列表,表示第输入矩阵的第几个通道复制到输出矩阵的第几个通道
比如 {0,2,1,1,2,0}表示:
src颜色通道0复制到dst颜色通道2
src颜色通道1复制到dst颜色通道1
src颜色通道2复制到dst颜色通道0
void cv::mixChannels    
(   
    const Mat *src,
    size_t  nsrcs,
    Mat *dst,
    size_t  ndsts,
    const int *fromTo,
    size_t  npairs 
)   
第一个参数:输入矩阵
第二个参数:输入矩阵的数量
第三个参数:输出矩阵
第四个参数:输出矩阵的数量
第五个参数:复制列表
第六个参数:复制列表的数量
在这个函数原型中,如果输入矩阵和输出矩阵都写1,那么就跟第一种函数原型是一致的了。
如果不为1,就可以实现多个矩阵组合并或者一个矩阵拆分为多个复杂矩阵等功能。

例子:

ch[]={0,0};
mixChannels(&hsv, 1, &hue, 1, ch, 1);
//将hsv的第一通道复制到hue的第一通道

(6)Mat 设置roi(感兴趣的)区域

//指定ROI区域的两种方法
Mat D(A,Rect(10,10,100,100));
Mat D=A(Rect(10,10,100,100));
//第一种给定原图像和矩形区域来确定有两种表达方式
Mat E = A(Range(2,4),Range(1,3));//使用Range限定行列的边界来指定ROI区域 Range来限定roi区域的行和列  从第2行到第4行从第1列到第3列
//roi区域和原图像共享内存,事实上仍旧是在原图像上操作

(7)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);
​
void calcHist(const Mat* arrays, int narrays, const int*channels, InputArray mask, SparseMat &hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false);
​

参数:

arrays:输入图像的指针,可以是多幅图像但要具有相同的深度,一幅图像可以有多个通道
narrays:图像的个数
channels:维度通道序列代表要计算直方图的通道,如果是多个图像通道号按照照片的序列从0开始逐次增加比如图片一有三个通道,图片二有三个通道那么一共有6个通道编号是0到5,0、1、2是图片一的3、4、5是图片二的,channels可以是数组也可以是一个数
mask:是一个矩阵图像,如果该矩阵不空的话,它必须是一个8-bit的矩阵,与images[i]同尺寸。在图像中,只有被mask覆盖的区域的像素才参与直方图统计。如果这个参数想用默认值,输入Mat()就可以了。mask就是前面所创建的掩膜
hist:计算出来的直方图,也是Mat图像类型,它是一个稠密或稀疏矩阵,具有dims个维度
dims:直方图的维度,一定是正值,当前最大值是32
histSize:数组,即histSize[i]表示第i个维度上bin的个数,这里的维度可以理解为通道,也就是每一个通道上直方图(数条)的个数。
ranges:代表每个维数直方图的边界,解释看下面
uniform:标识,用于说明直方条是否均匀等宽,默认等宽
accumulate:如果该项设置,当直方图重新分配时,直方图再开始清零。这个特征可以使你通过几幅图像,累积计算一个简单的直方图,或者及时更新直方图,默认flase
​
ranges:
当uniform=true时,ranges是多个二元数组组成的数组;当uniform=false时,ranges是多元数组组成的数组。当在每个维度(或通道)上每个直方条等宽时,即uniform=true时,灰度值的有效统计范围的下界用L0表示,上界用UhistSize[i]-1表示,角标中的i表示第i个维度(或通道),上下界值可以表示为hrange[i]={ L0, UhistSize[i]-1}, 在统计时, L0和UhistSize[i]-1不在统计范围内。而ranges={ hrange[0], hrange[1], …… , hrange[dims]}。ranges的元素个数由参数dims决定。
其中,L0表示在该通道上第0个直方条(bin)的下边界,UhistSize[i]-1表示最后一个直方条histSize[i]-1的上边界。在该维度上直方条的个数为histSize[i],如hrange[0]={ L0, UhistSize[0]},hrange[1]={ L1, UhistSize[1]}, hrange[2]={ L2, UhistSize[2]}, …… , hrange[dims]={ L0, UhistSize[0]}。
当uniform=false时,ranges中的每个元素ranges[i]都是一个多元数组,元素个数为histSize[i]+1,它们分别是:L0 , U0=L1, U1= L2, …… ,UhistSize[i]-2 , LhistSize[i]-1 , UhistSize[i]-1 。所以,ranges[i]={ L0 , L1, L2, …… , LhistSize[i]-1 ,UhistSize[i]-1}

图像直方图:

图像直方图(Image Histogram)是用以表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像素数。这种直方图中,横坐标的左侧为纯黑、较暗的区域,而右侧为较亮、纯白的区域。因此一张较暗图片的直方图中的数据多集中于左侧和中间部分,而整体明亮、只有少量阴影的图像则相反。CV 领域常借助图像直方图来实现图像的二值化。
  直方图的意义如下:
  ● 直方图是图像中像素强度分布的图形表达方式。
  ● 它统计了每一个强度值所具有的像素个数。

(8)normalize()

功能:

归一化:
归一化就是要把需要处理的数据经过处理后(通过某种算法)限制在你需要的一定范围内。首先归一化是为了后面数据处理的方便,其次是保证程序运行时收敛加快。归一化的具体作用是归纳统一样本的统计分布性。归一化在0-1之间是统计的概率分布,归一化在某个区间上是统计的坐标分布。归一化有同一、统一和合一的意思。

函数原型:

void normalize(InputArray src,OutputArray dst, double alpha = 1, double beta = 0, intnorm_type = NORM_L2, int dtype =-1,InputArray mask = noArray() )

参数:

src:输入数组
dst:输出数组
alpha:模式的最小值
beta:模式的最大值,不用于norm normalization(范数归一化)模式
normType: 归一化的类型,可以有以下的取值:
    NORM_MINMAX:数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较常用。
    NORM_INF:此类型的定义没有查到,根据OpenCV 1的对应项,可能是归一化数组的C-范数(绝对值的最大值)
    NORM_L1 :  归一化数组的L1-范数(绝对值的和)
    NORM_L2: 归一化数组的(欧几里德)L2-范数
dtype:dtype为负数时,输出数组的type与输入数组的type(深度)相同;否则,输出数组与输入数组只是通道数相同,而tpye=CV_MAT_DEPTH(dtype).
mask:操作掩膜,用于指示函数是否仅仅对指定的元素进行操作

例如:

normalize(hist, hist, 0, 255, CV_MINMAX);
//将hist归一化到0-255范围内

(9)calcBackProject()

功能:对照直方图实现反向投影

反向投影:

简介:
   反向投影图中,某一位置(x,y)的像素值 = 原图对应位置(x,y)像素值在原图的总数目。 即若原图中(5,5)    位置上像素值为 200,而原图中像素值为 200 的像素点有 500 个,则反向投影图中(5,5)位置上的像素值就设    为500。
具体步骤:
   ①计算图像直方图:统计各像素值(或像素区间)在原图的总数量。
   ②将直方图数值归一化到 [0,255] 。
   ③对照直方图,实现反向投影。
反向投影的作用:
   反向投影用于在输入图像(通常较大)中查找与模板图像(通常较小甚至仅 1 个像素)最匹配的点或区域,也就是确    定模板图像在输入图像中的位置。
更详细了解参考链接:
https://blog.csdn.net/keith_bb/article/details/70154219

函数原型:

void calcBackProject(const Mat* images, int nimages, const int* channels, InputArray hist, OutputArray backProject, const float** ranges, double scale = 1, bool uniform = true);

参数:

images:输入的图像或图像集,要求图像集具有相同的深度和尺寸,但通道数任意
nimages:输入的通道的个数
channels:需要统计图像的通道索引即第几个通道,通道号的定义如上述calcHist()中的通道号
hist:输入的直方图
backProject:反向投影图像,需要为单通道,并且和输入的图像有相同的深度和尺寸
ranges:直方图中x轴的取值范围
scale:缩放因子,backProject [i][j] = scale * hist [images[k] [i][j]],有默认值 1。//不太懂!!
uniform:直方图是否均匀化的标识符,有默认值true。

例子:

calcBackProject(&hue, 1, 0, hist, backproj, &phranges);

(10)CamShift()

cameshift算法简介:

CamShift算法的全称是"Continuously Adaptive Mean-SHIFT",即:连续自适应的MeanShift算法。基本思想是对视频序列的所有图像帧都作MeanShift运算,并将上一帧的结果(即搜索窗口的中心位置和窗口大小)作为下一帧MeanShift算法的搜索窗口的初始值,如此迭代下去。简单点说,meanShift是针对单张图片寻找最优迭代结果,而camShift则是针对视频序列来处理,并对该序列中的每一帧图片都调用meanShift来寻找最优迭代结果。正是由于camShift针对一个视频序列进行处理,从而保证其可以不断调整窗口的大小,如此一来,当目标的大小发生变化的时候,该算法就可以自适应地调整目标区域继续跟踪。

opencv自带例子中,camShift算法是通过计算目标在HSV空间下的H分量直方图,利用直方图反向投影得到目标像素的概率分布,然后通过调用OpenCV的CAMSHIFT算法,自动跟踪并调整目标窗口的中心位置与大小。该算法对于简单背景下的单目标跟踪效果较好,但如果被跟踪目标与背景颜色或周围其它目标颜色比较接近,则跟踪效果较差。另外,由于采用颜色特征,所以它对被跟踪目标的形状变化有一定的抵抗能力。

算法过程主要分为三个部分:

①:计算彩色投影图(反向投影)

(1)RGB颜色空间对光照亮度变化较为敏感,为了减少此变化对跟踪效果的影响,首先将图像从RGB空间转换到HSV空间. (2)然后对其中的H分量(色调)作直方图,在直方图中代表了不同H分量值出现的概率或者像素个数,就是说可以查找出H分量大小为h的概率或者像素个数,即得到了颜色概率查找表。 (3)将图像中每个像素的值用其颜色出现的概率对替换,就得到了颜色概率分布图。这个过程就叫反向投影,颜色概率分布图是一个灰度图像。

②:meanshift:

meanshift算法是一种密度函数梯度估计的非参数方法,通过迭代寻优找到概率分布的极值来定位目标。

③:camshift:

将meanshift算法扩展到连续图像序列,就是camshift算法。它将视频的所有帧做meanshift运算,并将上一帧的结果,即搜索窗的大小和中心,作为下一帧meanshift算法搜索窗的初始值。如此迭代下去,就可以实现对目标的跟踪。

算法的过程为: (1)初始化搜索窗 (2)计算搜索窗的颜色概率分布(反向投影) (3)运行meanshift算法,获得搜索窗新的大小和位置。 (4)在下一帧视频图像中用(3)中的值重新初始化搜索窗的大小和位置,再跳转到(2)继续进行。

④:总结:

camshift能有效解决目标变形和遮挡的问题,对系统资源要求不高,时间复杂度低,在简单背景下能够取得良好的跟踪效果。但当背景较为复杂,或者有许多与目标颜色相似像素干扰的情况下,会导致跟踪失败。因为它单纯的考虑颜色直方图,忽略了目标的空间分布特性,所以这种情况下需加入对跟踪目标的预测算法。

函数原型:

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

参数:

RotatedRect:旋转矩形

RotatedRect(const Point2f& center, const Size2f& size, float angle);
//center 旋转质心   size:矩形的宽和高  angele:顺时针旋转角度
RotatedRect(const Point2f& point1, const Point2f& point2, const Point2f& point3);
//通过三个点也可以指定旋转矩形,但要注意顺序

probImage:反向投影图

window:要追踪目标的初始位置矩形框

TermCriteria critreia:算法结束条件,类型有CV_TERMCRIT_ITER、CV_TERMCRIT_EPS、CV_TERMCRIT_ITER|CV_TERMCRIT_EPS分别代表着迭代终止条件为达到最大迭代次数终止,迭代到阈值终止,或者两者都作为迭代终止条件

cv::TermCriteria::TermCriteria ( int  type, int maxCount, double epsilon)
1) type:The type of termination criteria, 判定迭代终止的条件类型,要么只按count算,要么只按EPS算,要么两个条件达到一个就算结束
COUNT:按最大迭代次数算
EPS:就是epsilon,按达到某个收敛的阈值作为求解结束标志
COUNT + EPS:要么达到了最大迭代次数,要么按达到某个阈值作为收敛结束条件。
2) maxCount:具体的最大迭代的次数是多少
3) epsilon:具体epsilon值是多少。
//count一般为10,收敛域值为1

例子:

RotatedRect trackBox = CamShift(backproj, trackwindlow, TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1));

(11)ellipse()

功能:画椭圆弧

函数原型:

void cvEllipse( CvArr* img, CvPoint center, CvSize axes, double angle,double start_angle, double end_angle, CvScalar color,int thickness=1, int line_type=8, int shift=0 );
​
void ellipse(Mat&img, Pointcenter, Size axes, doubleangle, double startAngle, double endAngle, const Scalar&color, intthickness=1, int lineType=8, intshift=0)
    
void ellipse(Mat&img, const RotatedRect&box, const Scalar& color, int thickness=1, intlineType=8)
​

img:图像 center:椭圆圆心坐标 axes:轴的长度。 angle:偏转的角度。 start_angle:圆弧起始角的角度。. end_angle:圆弧终结角的角度。 color:线条的颜色。 thickness:线条的粗细程度。 line_type:线条的类型,见CVLINE的描述。 shift:圆心坐标点和数轴的精度。

RotatedRect:旋转矩形

(12)bitwise_not()

功能:取反(非)操作,将图片里像素值按位反向

函数原型:

  void bitwise_not(InputArray src, OutputArray dst,InputArray mask = noArray());

src:输入图像

dst:目标图像

bitwise_or或运算

bitwise_xor异或运算

bitwise_and与运算

三、项目初步总结

1.创建鼠标回调函数

2.打开视频

3.将BGR转化为HSV颜色空间

4.如果已经有了选中的区域,那么设置掩膜,提取H通道

5.对区域进行属性提取,确定roi区域,绘制直方图,归一化

6.根据直方图绘制反向投影并选取需要的部分,进行camshift追踪,并绘制椭圆

7.对鼠标选中的区域进行取反操作

四、题目链接

C++实现运动目标的追踪_git1314的博客-CSDN博客_目标跟踪c++

  • 4
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 C++ 中使用 OpenCV 实现远距离目标追踪,你需要掌握以下几个步骤: 1. 获取视频流或图像。 2. 对图像进行预处理,包括调整大小、灰度化、平滑化等。 3. 使用 Haar 级联分类器或其他机器学习算法检测出目标物体。你可以使用 OpenCV 自带的 Haar 级联分类器或训练自己的分类器。 4. 根据检测到的目标物体,计算出目标物体的位置和跟踪矩形。 5. 根据目标物体的位置和跟踪矩形,在下一帧图像中寻找目标物体,并更新跟踪矩形。 6. 循环执行第 5 步,直到目标物体离开画面或停止运动。 下面是一个简单的示例代码,演示了如何使用 OpenCV 进行目标追踪: ``` #include <opencv2/opencv.hpp> using namespace cv; int main() { // 打开视频文件 VideoCapture cap("test.mp4"); if (!cap.isOpened()) return -1; // 创建 Haar 级联分类器 CascadeClassifier cascade; cascade.load("cascade.xml"); // 运行循环,处理每一帧图像 Mat frame; while (cap.read(frame)) { // 调整图像大小,提高处理速度 resize(frame, frame, Size(frame.cols / 2, frame.rows / 2)); // 灰度化和直方图均衡化,提高目标物体的对比度 Mat gray; cvtColor(frame, gray, COLOR_BGR2GRAY); equalizeHist(gray, gray); // 检测目标物体 std::vector<Rect> objects; cascade.detectMultiScale(gray, objects, 1.1, 3, 0, Size(30, 30)); // 更新目标物体的位置和跟踪矩形 for (auto& rect : objects) { rectangle(frame, rect, Scalar(0, 255, 0), 2); } // 显示图像并等待按键 imshow("frame", frame); if (waitKey(30) == 'q') break; } return 0; } ``` 其中,`cascade.xml` 文件是事先训练好的 Haar 级联分类器模型,你可以在网上找到现成的模型,也可以使用 OpenCV 自带的模型。在代码中,我们首先打开视频文件,然后创建 Haar 级联分类器,循环读取每一帧图像并进行预处理,最后使用分类器检测目标物体并更新跟踪矩形,同时显示图像并等待按键。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值