项目实战笔记 | C++实现运动目标的追踪 实验楼项目
sudo apt-getupdate && sudo apt-get install gtk-recordmydesktop
首先安装屏幕录制工具
1)#include<opencv2/opencv.hpp>要使用opencv得这个头文件,
2)VideoCapture获取视频,有两个值,可以是路径或者是数字,数字表示使用哪个摄像头,通常只有一个摄像头的时候是0
3)要加cv::头
4)Mat是矩阵对象,开头大写,可以VideoCapture>>Mat,来将视频内容放入Mat对象里面,可以使用imshow("test",frame);来播放某个矩阵对象,
5)int key = cv::waitKey(1000/15);这个可以让视频停顿相应的时间来等待键盘输入,这里帧率是15,这里的输入单位是毫秒,这种做法能够使得视频播放更加的流畅
6)key==27表示获得的按键是esc按键
7)在代码结束的时候,要使用对象.release()来释放所有的对象,release貌似是opencv独有的函数,还需要使用destroyAllWindows();来消除掉播放框
8)
if( selectObject &&selection.width > 0 && selection.height > 0 ) {
cv::Mat roi(image, selection);
bitwise_not(roi, roi);
}
imshow( "CamShift atShiyanlou", image );
使用这两句可以实现选中区域的颜色反向,具体原理不清楚
9)selection &=cv::Rect(0, 0, image.cols, image.rows);
可以使用这一句来保证鼠标选中的区域不会超出绘画范围,关键在于 &=这一句
运行效果图
}红圈部分是鼠标选中的部分,可以看到自动追踪了绿色轨道上的的行星
//
// main.cpp
//
#include<opencv2/opencv.hpp> // OpenCV 头文件
int main() {
// 创建一个视频捕获对象
// OpenCV 提供了一个 VideoCapture 对象,它屏蔽了
// 从文件读取视频流和从摄像头读取摄像头的差异,当构造
// 函数参数为文件路径时,会从文件读取视频流;当构造函
// 数参数为设备编号时(第几个摄像头, 通常只有一个摄像
// 头时为0),会从摄像头处读取视频流。
cv::VideoCapturevideo("video.ogv"); // 读取文件
// cv::VideoCapture video(0); // 使用摄像头
// 捕获画面的容器,OpenCV 中的 Mat 对象
// OpenCV 中最关键的 Mat 类,Mat 是 Matrix(矩阵)
// 的缩写,OpenCV 中延续了像素图的概念,用矩阵来描述
// 由像素构成的图像。
cv::Mat frame;
while(true) {
// 将 video 中的内容写入到 frame 中,
// 这里 >> 运算符是经过 OpenCV 重载的
video >> frame;
// 当没有帧可继续读取时,退出循环
if(frame.empty()) break;
// 显示当前帧
cv::imshow("test", frame);
// 录制视频帧率为 15, 等待 1000/15 保证视频播放流畅。
// waitKey(int delay) 是 OpenCV 提供的一个等待函数,
// 当运行到这个函数时会阻塞 delay 毫秒的时间来等待键盘输入
int key = cv::waitKey(1000/15);
// 当按键为 ESC 时,退出循环
if (key == 27) break;
}
// 释放申请的相关内存
cv::destroyAllWindows();
video.release();
return 0;
}
来源: 实验楼
链接: https://www.shiyanlou.com/courses/560
MeanShift算法:
简单来说就是当圆圈的圆心和质心不重合的时候,将圆圈向质心的位置移动,这个算法可以计算局部点最密集的地方
这个算法貌似可以用来捕捉运动的物体,在捕捉物体的时候,使用反向投影直方图,这个直方图能够反映物体的运动
如图所示,把蓝色的圆圈标记为 C1,蓝色的矩形为圆心标记为 C1_o。但这个圆的质心却为 C1_r,标记为蓝色的实心圆。
当C1_o和C1_r不重合时,将圆 C1移动到圆心位于 C1_r的位置,如此反复。最终会停留在密度最高的圆 C2 上。
但是meanshift只能对恒定大小的物体进行追踪,在实际运中,可能物体并不是恒定大小的
Camshift算法能够改进这个问题,它在每一次meanshift收敛之后用一个椭圆来更新窗口,然后在新的窗口下再次应用meanshift算法
Opencv里面实现了这个算法:
RotatedRectCamShift(InputArray probImage, Rect& window, TermCriteria criteria)
其中第一个参数 probImage 为目标直方图的反向投影,第二个参数 window 为执行 Camshift 算法的搜索窗口,第三个参数为算法结束的条件。
接下来实现
1)这里通过event的值我们能够获得鼠标点击的具体是什么,比如CV_EVENT_LBUTTONDOWN是左键被按下,CV_EVENT_LBUTTONUP
是右键放开
2)&=被rect重载,可以用来计算两个rect的交集
3)Mat有两个属性,rows和cols,分别表示行数和列数
第一步:选择追踪目标区域的鼠标回调函数:
boolselectObject = false; // 用于标记是否有选取目标
int trackObject= 0; // 1 表示有追踪对象 0 表示无追踪对象 -1 表示追踪对象尚未计算Camshift 所需的属性
cv::Rectselection; // 保存鼠标选择的区域
cv::Matimage; // 用于缓存读取到的视频帧
// OpenCV 对所注册的鼠标回调函数定义为:
// voidonMouse(int event, int x, int y, int flag, void *param)
// 其中第四个参数 flag 为 event 下的附加状态,param 是用户传入的参数,我们都不需要使用
// 故不填写其参数名
void onMouse(int event, int x, int y, int, void* ) {
static cv::Point origin;
if(selectObject) {
// 确定鼠标选定区域的左上角坐标以及区域的长和宽
selection.x = MIN(x, origin.x);
selection.y = MIN(y, origin.y);
selection.width = std::abs(x -origin.x);
selection.height = std::abs(y -origin.y);
// & 运算符被 cv::Rect 重载
// 表示两个区域取交集, 主要目的是为了处理当鼠标在选择区域时移除画面外
selection &= cv::Rect(0, 0,image.cols, image.rows);
}
switch(event) {
// 处理鼠标左键被按下
case CV_EVENT_LBUTTONDOWN:
origin = cv::Point(x, y);
selection = cv::Rect(x, y, 0, 0);
selectObject = true;
break;
// 处理鼠标左键被抬起
case CV_EVENT_LBUTTONUP:
selectObject = false;
if( selection.width > 0&& selection.height > 0 )
trackObject = -1; // 追踪的目标还未计算 Camshift 所需要的属性
break;
}
}
第二步:从视频流中读取图像
下面是对main函数进一步细化:
1) 使用namedWindow(string)来注册一个窗口,接下来可能是在这个窗口里面播放内容
2) setMouseCallback(string,onMouse,NULL);第一个参数是类似于注释,第二个参数为回掉函数指针,第三个参数为用户提供给回调函数的
3) Mat.copyTo(Mat)将一个mat中的数据拷贝到另外一个Mat里面,这里一般是视频的一个帧或者是一副画面
4) 这里的Mat ort(image,selection),意思是初始化一个mat,使用image中的selection(rect类型)框住的一部分
5) bitwise_not(mat1,mat2),意思是将mat1的全部数据取反之后放入mat2中
int main() {
cv::VideoCapturevideo("video.ogv");
cv::namedWindow("CamShift atShiyanlou");
// 1. 注册鼠标事件的回调函数, 第三个参数是用户提供给回调函数的,也就是回调函数中最后的 param 参数
cv::setMouseCallback("CamShift atShiyanlou", onMouse, NULL);
cv::Mat frame; // 接收来自 video 视频流中的图像帧
// 2. 从视频流中读取图像
while(true) {
video >> frame;
if(frame.empty()) break;
// 将frame 中的图像写入全局变量 image 作为进行 Camshift 的缓存
frame.copyTo(image);
// 如果正在选择追踪目标,则画出选择框
if( selectObject &&selection.width > 0 && selection.height > 0 ) {
cv::Mat roi(image, selection);
bitwise_not(roi, roi); // 对选择的区域图像反色
}
imshow("CamShift atShiyanlou", image);
int key = cv::waitKey(1000/15.0);
if(key == 27) break;
}
// 释放申请的相关内存
cv::destroyAllWindows();
video.release();
return 0;
}
来源: 实验楼
链接:https://www.shiyanlou.com/courses/560
三步:实现 Camshift 过程
1) cvtColor(image,hsv,COLOR_BGR2HSV);这个函数可以将rgb颜色空间的矩阵转换成hsv颜色空间的颜色矩阵,将image转换后存储在hsv中
2) inRange(hsv,cv::Scalar(0,30,10),cv::Scalar(180,256,256),mask);这个函数的功能是将MatHsv中的符合三个通道的相应的范围复制到mask中
3) hue.create(hsv.size(),hsv.depth());这个函数功能是给hue创造矩阵,使用hsv的大小和hsv的像素深度!
4) mixChannels(&hsv,1.&hue,1,ch,1)这个函数的功能是将指定的矩阵的相应的通道复制给输出矩阵,这里的输入矩阵为hsv,只有一个输入矩阵,输出矩阵为hue,只有一个输出矩阵,标记对于个数为1,ch为{0,0}表示把hsv的第一个通道给hue,也就是h通道!
5) Cv::Mat roi(hue,selection);这个语句的功能是把hue的selection(这个为rect)部分复制给roi
6) 对calcHist的分析,calcHist(&roi,1,0,maskroi,hist,1,&hsize,&phranges)
第一个参数表示输入的矩阵,是constMat *类型,所以要输入地址,第二个参数表示输入图像的个数,第三个参数是const int *类型,表示输入图像使用哪些channels来计算直方图,一般是输入一个数组,这里输入一个0,表示空指针,这里应该是表示使用第一幅图像的第一个通道,也就是经过inRange过的H通道,第四个参数表示使用哪个掩码来过滤原图像,这里可以是矩阵要求和输入矩阵一样大,这里掩码有3个通道,但是输入图像只有一个通道,这里应该是使用默认的掩码的第一个通道来过滤输入图像的矩阵,过滤的方法为,当掩码的某个值为0的时候,输入图像的相应位置会被过滤,但是当值大于0的时候输入图像的相应值才会被用来计算直方图
Hist,表示输出直方图,这里用矩阵Mat来接受直方图数据,也不知道是怎么存储的,
第6个参数表示输出的直方图是几维的,这里当然是一维的
第7个参数表示在每一维上竖条的个数,这里为16,参数类型为const int *,可以是数组也可以是&int类型,这里只有1维所以是&int
第8个参数表示直方图统计的原图的值的范围,是一个const float **的类型,也就是二维数组,比如float a[]={0,10} float b[]={20,30},float * c[]={a,b};这里的c就是表示输入的范围为0~10,10~20,
这里是使用floathranges[] = {0,180},const float *phranges = hranges,来实现,这里需要输入指针的地址,这里可以直接输入&phranges来实现功能,
7) normalize(inputArray,outputArray,doublealpha,double beta,normalizeType)
最后一个参数可以为CV_MINMAX,这里表示把矩阵归一化到指定范围
int main() {
cv::VideoCapturevideo("video.ogv");
cv::namedWindow("CamShift atShiyanlou");
cv::setMouseCallback("CamShift atShiyanlou", onMouse, NULL);
cv::Mat frame;
cv::Mat hsv, hue, mask, hist, backproj;
cv::Rect trackWindow; // 追踪到的窗口
int hsize = 16; // 计算直方图所必备的内容
float hranges[] = {0,180}; // 计算直方图所必备的内容
const float* phranges = hranges; // 计算直方图所必备的内容
while(true) {
video >> frame;
if(frame.empty()) break;
frame.copyTo(image);
// 转换到 HSV 空间
cv::cvtColor(image, hsv,cv::COLOR_BGR2HSV);
// 当有目标时开始处理
if(trackObject) {
// 只处理像素值为H:0~180,S:30~256,V:10~256之间的部分,过滤掉其他的部分并复制给 mask
cv::inRange(hsv, cv::Scalar(0, 30,10), cv::Scalar(180, 256, 10), mask);
// 下面三句将 hsv 图像中的 H 通道分离出来
int ch[] = {0, 0};
hue.create(hsv.size(),hsv.depth());
cv::mixChannels(&hsv, 1,&hue, 1, ch, 1);
// 如果需要追踪的物体还没有进行属性提取,则对选择的目标中的图像属性提取
if( trackObject < 0 ) {
// 设置 H 通道和 mask 图像的 ROI
cv::Mat roi(hue, selection),maskroi(mask, selection);
// 计算 ROI所在区域的直方图
calcHist(&roi, 1, 0,maskroi, hist, 1, &hsize, &phranges);
// 将直方图归一
normalize(hist, hist, 0, 255,CV_MINMAX);
// 设置追踪的窗口
trackWindow = selection;
// 标记追踪的目标已经计算过直方图属性
trackObject = 1;
}
// 将直方图进行反向投影
calcBackProject(&hue, 1, 0,hist, backproj, &phranges);
// 取公共部分
backproj &= mask;
// 调用 Camshift 算法的接口
cv::RotatedRect trackBox =CamShift(backproj, trackWindow, cv::TermCriteria( CV_TERMCRIT_EPS |CV_TERMCRIT_ITER, 10, 1 ));
// 处理追踪面积过小的情况
if( trackWindow.area() <= 1 ) {
int cols = backproj.cols, rows= backproj.rows, r = (MIN(cols, rows) + 5)/6;
trackWindow =cv::Rect(trackWindow.x - r, trackWindow.y - r,
trackWindow.x + r, trackWindow.y + r) & cv::Rect(0, 0, cols, rows);
}
// 绘制追踪区域
ellipse( image, trackBox,cv::Scalar(0,0,255), 3, CV_AA );
}
if( selectObject &&selection.width > 0 && selection.height > 0 ) {
cv::Mat roi(image, selection);
bitwise_not(roi, roi);
}
imshow("CamShift atShiyanlou", image);
int key = cv::waitKey(1000/15.0);
if(key == 27) break;
}
cv::destroyAllWindows();
video.release();
return 0;
}
接下来是所有的代码总结
#include<opencv2/opencv.hpp>
boolselectObject = false;
int trackObject= 0;
cv::Rectselection;
cv::Mat image;
void onMouse(int event, int x, int y, int, void* ) {
static cv::Point origin;
if(selectObject) {
selection.x = MIN(x, origin.x);
selection.y = MIN(y, origin.y);
selection.width = std::abs(x -origin.x);
selection.height = std::abs(y -origin.y);
selection &= cv::Rect(0, 0,image.cols, image.rows);
}
switch(event) {
case CV_EVENT_LBUTTONDOWN:
origin = cv::Point(x, y);
selection = cv::Rect(x, y, 0, 0);
selectObject = true;
break;
case CV_EVENT_LBUTTONUP:
selectObject = false;
if( selection.width > 0&& selection.height > 0 )
trackObject = -1;
break;
}
}
int main( intargc, const char** argv )
{
cv::VideoCapturevideo("video.ogv");
cv::namedWindow( "CamShift atShiyanlou" );
cv::setMouseCallback( "CamShift atShiyanlou", onMouse, 0 );
cv::Mat frame, hsv, hue, mask, hist,backproj;
cv::Rect trackWindow;
int hsize = 16;
float hranges[] = {0,180};
const float* phranges = hranges;
while(true) {
video >> frame;
if( frame.empty() )
break;
frame.copyTo(image);
cv::cvtColor(image, hsv,cv::COLOR_BGR2HSV);
if( trackObject ) {
cv::inRange(hsv, cv::Scalar(0, 30,10), cv::Scalar(180, 256, 256), mask);
int ch[] = {0, 0};
hue.create(hsv.size(),hsv.depth());
cv::mixChannels(&hsv, 1,&hue, 1, ch, 1);
if( trackObject < 0 ) {
cv::Mat roi(hue, selection),maskroi(mask, selection);
calcHist(&roi, 1, 0,maskroi, hist, 1, &hsize, &phranges);
normalize(hist, hist, 0, 255,CV_MINMAX);
trackWindow = selection;
trackObject = 1;
}
calcBackProject(&hue, 1, 0,hist, backproj, &phranges);
backproj &= mask;
cv::RotatedRect trackBox =CamShift(backproj, trackWindow, cv::TermCriteria( CV_TERMCRIT_EPS |CV_TERMCRIT_ITER, 10, 1 ));
if( trackWindow.area() <= 1 ) {
int cols = backproj.cols, rows= backproj.rows, r = (MIN(cols, rows) + 5)/6;
trackWindow =cv::Rect(trackWindow.x - r, trackWindow.y - r,
trackWindow.x + r,trackWindow.y + r) &
cv::Rect(0, 0, cols, rows);
}
ellipse( image, trackBox,cv::Scalar(0,0,255), 3, CV_AA );
}
if( selectObject &&selection.width > 0 && selection.height > 0 ) {
cv::Mat roi(image, selection);
bitwise_not(roi, roi);
}
imshow( "CamShift atShiyanlou", image );
char c = (char)cv::waitKey(1000/15.0);
if( c == 27 )
break;
}
cv::destroyAllWindows();
video.release();
return 0;
}
来源: 实验楼
链接: https://www.shiyanlou.com/courses/560