之前的单目标跟踪算法已经调试成功,在这基础上想把之前的单目标跟踪改进成多目标算法。在这中间碰了些许此壁,最后成功了。首选我列举出来找到的主要三种“说法”:
- 利用MultiTracker来写,但是用这个写的时候我遇到了一个问题。就是这个方式必须要打开两个window,在一个window中实现截取,在另一个window中显示。当时因为我执着于在当前的框架上添加的原因,最后这个办法没有采用。(最后利用下面的方法调试成功了以后还没有回过来重新测试这个方法,这个工作留到下次再做,今天先趁热写个博客。)
相关博客请参见以下地址:
opencv tracking(3) 多目标跟踪示例
opencv学习笔记四十五:扩展模块的单目标、多目标跟踪
再利用这个方法来进行改进自己的程序的时候,如果不利用两个窗口的话会出现只能跟踪一个目标的情况。(其实在学习后一个方法的时候,找到了可能导致这个的一个原因,下次再尝试。)
- 还没有开始研究的看到的算法,就是sort和deep sort算法。
前面那些单目标跟踪的算法是只针对单目标的算法,就算是实现成了多目标,但是究其原理还是通过对单目标的跟踪来实现的,但是sort和deep sort看样子是专门的多目标跟踪算法。有空去好好学习了以后来补充。附上目前找到的链接:
https://www.cnblogs.com/kekeoutlook/p/11179522.html
https://blog.csdn.net/weixin_38106878/article/details/100009101
https://blog.csdn.net/zgcr654321/article/details/90446526
https://zhuanlan.zhihu.com/p/97449724?from_voters_page=true
- 第二个方法是利用Ptr来管理MultiTracker实现
参考博客:
[OpenCV实战]16 使用OpenCV实现多目标跟踪
在这个方法中有一个很明显的区别就是他利用Ptr模板类来管理MultiTracker类。其他很多方面都是类似的,比如单目标跟踪用的是selectROI()函数,但是这里用的是selectROIs(),还有单目标用init这里不用init()方法,而是用add()方法代替了,其他的update()等方法都是大同小异的。详情见代码,跟之前的单目标的对比就知道啦。
在这中间学到了一个个人感觉很有用、很值得深究的东西,就是Ptr模板类。初步了解,他是一个智能指针,智能指针有shared_ptr,unique_ptr,weak_ptr,scoped_ptrd等,Ptr看了一眼源代码貌似是shared_ptr的子类。智能指针的一种通用实现技术是使用引用计数(reference count)。直白的说就是在OpenCV中会利用Ptr模板类来实现对一些类的资源管理,比如释放之类的。链接如下:
https://www.jianshu.com/p/0642948df118
在写这个程序的时候遇到了一个问题,就是我再选择跟踪算法的时候,比如
Ptr<Tracker> tracker; tracker = TrackerKCF::create();
然后要将他添加到多目标跟踪里面的时候需要用以下代码:
multiTracker->add(tracker, frame, Rect2d(rois[i]));
在这个add()方法中第一个传入的参数就是tracker,此时我最初的实现方法是,程序刚开始时候提示用户输入,并利用上一个代码对应的进行create(),但是这样以来,他总是会跟踪失败。(也就是跟我的单目标跟踪实例里面的一样)
我看人家博客里面并不是这么实现的,他是写了个方法进行选择跟踪算法,然后只在调用add的时候才去调用那个方法。(给人的直观感觉就像是我第一个实现方法他实现的太早了,也就是饭出锅的太早了,add()的时候都凉透了,必须在add()时候才出锅,那样才热乎)在这里可把我整懵逼了,因为没想到会因为这个报错,看了一下午都不知道为什么,差点心态大蹦,明明两个程序一模一样,但是一个却可以另一个却不行。后来忘了在这个博客
https://blog.csdn.net/lingyunxianhe/article/details/80380970
写道Ptr是个模板类,然后附了个链接,点进去才开始怀疑应该是在这里除了问题,有可能是因为他自动管理的时候释放了还是怎么样,还不太清楚。应该是为了泛型编程和安全考虑的吧。
本人参考的文章作者的观点是:CSRT精度最高,KCF速度精度综合最好,MOSSE速度最快。
我也会进行一个测试,并附上感受的。
附上我的代码:#include <iostream> #include <opencv2/opencv.hpp> #include <opencv2/tracking.hpp> #include <opencv2/core/ocl.hpp> using namespace cv; using namespace std; vector<string> trackerTypes = { "BOOSTING", "MIL", "KCF", "TLD", "MEDIANFLOW", "MOSSE", "CSRT" }; /** * @brief Create a Tracker By Name object 根据设定的类型初始化跟踪器 * * @param trackerType * @return Ptr<Tracker> */ Ptr<Tracker> createTrackerByName(string trackerType) { Ptr<Tracker> tracker; if (trackerType == trackerTypes[0]) tracker = TrackerBoosting::create(); else if (trackerType == trackerTypes[1]) tracker = TrackerMIL::create(); else if (trackerType == trackerTypes[2]) tracker = TrackerKCF::create(); else if (trackerType == trackerTypes[3]) tracker = TrackerTLD::create(); else if (trackerType == trackerTypes[4]) tracker = TrackerMedianFlow::create(); else if (trackerType == trackerTypes[5]) tracker = TrackerMOSSE::create(); else if (trackerType == trackerTypes[6]) tracker = TrackerCSRT::create(); return tracker; } void getRandomColors(vector<Scalar> &colors, int numColors) { RNG rng(0); for (int i = 0; i < numColors; i++) { colors.push_back(Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255))); } } int main() { vector<vector<Point>> vecPoint; Mat frame; vector<Rect> rois; string trackerType; string videoName; while (true) { vecPoint.clear(); while (true) { cout << "\n\n------------------------------------------------------------------\n"; cout << "\n\n>> 可测试算法有 BOOSTING MIL KCF TLD MEDIANFLOW MOSSE CSRT" "\n>> 请输入要测试的算法并按回车,退出输入exit。" "\n>> "; cin >> trackerType; if (trackerType == "exit") { return 0; } if (trackerType == "BOOSTING") { cout << "\n>> 选择BOODSTING成功!\n"; break; } else if (trackerType == "MIL") { cout << "\n>> 选择MIL成功!\n"; break; } else if (trackerType == "KCF") { cout << "\n>> 选择KCF成功!\n"; break; } else if (trackerType == "TLD") { cout << "\n>> 选择TLD成功!\n"; break; } else if (trackerType == "MEDIANFLOW") { cout << "\n>> 选择MEDIANFLOW成功!\n"; break; } else if (trackerType == "MOSSE") { cout << "\n>> 选择MOSSE成功!\n"; break; } else if (trackerType == "CSRT") { cout << "\n>> 选择CSRT成功!\n"; break; } else { cout << "\n>> 选择失败\n"; continue; } } cout << "\n>> 输入1选择本地视频进行播放" "\n>> 输入2选择实时摄像头播放" "\n>> "; int judgement; cin >> judgement; if (1 == judgement) { while (true) { cout << "\n +--------------+" "\n | 1.步行的人_1 |" "\n | 2.步行的人_2 |" "\n | 3.步行的人_3 |" "\n | 4.车 |" "\n | 5.超车 |" "\n | 6.大卫 |" "\n | 7.跳绳 |" "\n | 8.摩托越野 |" "\n | 9.熊猫 |" "\n | 10.大众汽车 |" "\n +--------------+" "\n\n>> 请输入要播放视频的序列号(例如4)" "\n>> "; int videoNo; cin >> videoNo; if (videoNo == 1) { videoName = "pedestrian1.mpg"; cout << "\n>> 选择《步行的人 1》成功!"; break; } else if (videoNo == 2) { videoName = "pedestrian2.mpg"; cout << "\n>> 选择《步行的人 2》成功!"; break; } else if (videoNo == 3) { videoName = "pedestrian3.mpg"; cout << "\n>> 选择《步行的人 3》成功!"; break; } else if (videoNo == 4) { videoName = "car.mpg"; cout << "\n>> 选择《车》成功!"; break; } else if (videoNo == 5) { videoName = "carchase.mpg"; cout << "\n>> 选择《超车》成功!"; break; } else if (videoNo == 6) { videoName = "david.mpg"; cout << "\n>> 选择《大卫》成功!"; break; } else if (videoNo == 7) { videoName = "jumping.mpg"; cout << "\n>> 选择《跳绳》成功!"; break; } else if (videoNo == 8) { videoName = "motocross.mpg"; cout << "\n>> 选择《摩托越野》成功!"; break; } else if (videoNo == 9) { videoName = "panda.mpg"; cout << "\n>> 选择《熊猫》成功!"; break; } else if (videoNo == 10) { videoName = "volkswagen.mpg"; cout << "\n>> 选择《大众汽车》成功!"; break; } else { cout << "\n>> 序列号错误,请重新输入!"; continue; } } // Read video 读视频 VideoCapture video("D:\\targetTracking\\datasets\\" + videoName); // Exit if video is not opened 如果没有视频文件 if (!video.isOpened()) { cout << "\n>> 读取视频失败"; return -1; } cout << "\n +----------------------------+" "\n | 点击 c 逐帧播放视频 |" "\n | 点击 q 开始选择目标 |" "\n | 点击空格开始播放并追踪目标 |" "\n | 播放期间按q退出播放 |" "\n +----------------------------+\n"; video >> frame; //! [getframe] while (1) { char key = waitKey(1); if (key == 'c') // 按c键跳帧 { video >> frame; } if (key == 'q') // 按q键退出跳帧 { break; } imshow("Tracker", frame); } cv::destroyWindow("Tracker"); //end added //! [selectroi]选择目标roi以GUI的形式 cv::selectROIs("tracker", frame, rois, false); //! [selectroi] if (rois.size() < 1) { return 0; } vector<Scalar> colors; getRandomColors(colors, rois.size()); Ptr<MultiTracker> multiTracker = MultiTracker::create(); for (int i = 0; i < rois.size(); i++) { multiTracker->add(createTrackerByName(trackerType), frame, Rect2d(rois[i])); vector<Point> temp; vecPoint.push_back(temp); } cout << "\n>> 开始播放\n"; while (video.isOpened()) { // get frame from the video video >> frame; if (frame.empty()) { break; } // update the tracking result //! [update] bool ok = multiTracker->update(frame); //! [update] //! [visualization] // draw the tracked object for (unsigned i = 0; i < multiTracker->getObjects().size(); i++) { rectangle(frame, multiTracker->getObjects()[i], colors[i], 2, 1); } //! [visualization] for (int i = 0; i < multiTracker->getObjects().size(); i++) { Point presentPoint; presentPoint.x = (multiTracker->getObjects()[i].x + (multiTracker->getObjects()[i].width*0.5)) * 2; presentPoint.y = (multiTracker->getObjects()[i].y + (multiTracker->getObjects()[i].height*0.5)) * 2; vecPoint[i].push_back(presentPoint); } if (vecPoint.size() > 1) { for (int j = 0; j < vecPoint.size(); j++) { for (int k = 1; k < vecPoint[j].size(); k++) { line(frame, vecPoint[j][k - 1], vecPoint[j][k], colors[j], 1, 8, 1); } } } // show image with the tracked object imshow("tracker", frame); //! [visualization] if (char(waitKey(30)) == 'q') { break; } } cv::destroyWindow("tracker"); cout << "\n>> 播放完毕\n"; } else if (2 == judgement) { VideoCapture video(0, CAP_DSHOW); if (!video.isOpened()) { cout << "\n>> 读取视频失败"; continue; } cout << "\n>> 请按空格开始截取图片\n\n"; while (1) { video >> frame; //读取当前帧 //若视频完成播放,退出循环 if (frame.empty()) { cout << "发生错误,请检查摄像头是否已断开!"; break; } imshow("tracker", frame); //显示当前帧 if (char(waitKey(30)) == 32) { break; } //waitKey(30); //延时30ms } //! [selectroi]选择目标roi以GUI的形式 cv::selectROIs("tracker", frame, rois, false); //! [selectroi] if (rois.size() < 1) { return 0; } vector<Scalar> colors; getRandomColors(colors, rois.size()); Ptr<MultiTracker> multiTracker = MultiTracker::create(); for (int i = 0; i < rois.size(); i++) { multiTracker->add(createTrackerByName(trackerType), frame, Rect2d(rois[i])); vector<Point> temp; vecPoint.push_back(temp); } cout << "\n>> 开始播放\n"; cout << "\n>> 如需退出请按q退出播放\n"; while (video.isOpened()) { // get frame from the video video >> frame; if (frame.empty()) { break; } // update the tracking result //! [update] bool ok = multiTracker->update(frame); //! [update] //! [visualization] // draw the tracked object for (unsigned i = 0; i < multiTracker->getObjects().size(); i++) { rectangle(frame, multiTracker->getObjects()[i], colors[i], 2, 1); } //! [visualization] for (int i = 0; i < multiTracker->getObjects().size(); i++) { Point presentPoint; presentPoint.x = (multiTracker->getObjects()[i].x + (multiTracker->getObjects()[i].width*0.5)) * 2; presentPoint.y = (multiTracker->getObjects()[i].y + (multiTracker->getObjects()[i].height*0.5)) * 2; vecPoint[i].push_back(presentPoint); } if (vecPoint.size() > 1) { for (int j = 0; j < vecPoint.size(); j++) { for (int k = 1; k < vecPoint[j].size(); k++) { line(frame, vecPoint[j][k - 1], vecPoint[j][k], colors[j], 1, 8, 1); } } } // show image with the tracked object imshow("tracker", frame); //! [visualization] if (char(waitKey(30)) == 'q') { break; } } cv::destroyWindow("tracker"); cout << "\n>> 播放完毕\n"; } else { cout << "输入有误!"; } } return 0; }
2020.03.26号的更新
如果需要添加文字标签,那么只需要将下面代码加入到画线的代码下面即可,也就是line()方法下面。记得两处位置都要添加。
putText(frame,
"id_" + to_string(j + 1),
Point(multiTracker->getObjects()[j].x, multiTracker->getObjects()[j].y - 3),
FONT_HERSHEY_PLAIN,
1,
colors[j],
1);
下一步需要将这两个单目标和多目标的跟踪的程序移植到python上去,估计难度不大,将会在后续文章中更新。
2020.04.08号的更新
今天我需要添加将用摄像头读取的视频保存到本地,此时我心里产生了疑惑,画出的框框和轨迹能不能保存呢?然后就开始着手写代码,最开始就试着在处理画线之前输出一个视频 videoNoTrack.avi,画完线在输出一个视频 videoWithTrack.avi,最终结果是果然可以保存轨迹和框框。
代码如下:(从上面代码中的第二个if开始贴上)(所有解释和经验都在代码注释里)
else if (2 == judgement)
{
//VideoCapture video(0, CAP_DSHOW);
VideoCapture video(0);
///这里必须是下面的,如果加了CAP_DSHOW的话会出现保存视频是0KB的错误
if (!video.isOpened())
{
cout << "\n>> 发生错误,请检查摄像头是否已断开!";
continue;
}
///下面这个是为了给文件夹起不同的名字而获取当前的时间作为文件夹名字
time_t timep;
struct tm *p;
time(&timep);
p = localtime(&timep);
string outDir = to_string(1900 + p->tm_year) + "-" +
to_string(1 + p->tm_mon) + "-" +
to_string(p->tm_mday) + " " +
to_string(p->tm_hour) + ":" +
to_string(p->tm_min) + ":" +
to_string(p->tm_sec);
///这里要么用 / 要么用 \\ 。
outDir = "D:/targetTracking/MultiTrack/" + outDir;
///这里必须将string转换为const char[]才行
int res = mkdir(outDir.c_str());
string outFile_1 = outDir + "\\videoNoTrack.avi";
string outFile_2 = outDir + "\\videoWithTrack.avi";
int w = static_cast<int>(video.get(CAP_PROP_FRAME_WIDTH));
int h = static_cast<int>(video.get(CAP_PROP_FRAME_HEIGHT));
Size S(w, h);
double r = video.get(CAP_PROP_FPS);
///在这里浪费了最多的时间,OpenCV 4中VideoWriter的第二个参数用数字来表达了,参考的博客写的是-1,出现了0KB的错误,百度找到了CAP_OPENCV_MJPEG,也不行,这个估计跟-1是一样的吧?不知道
///后来找到有人说.avi是用0,换成0就可以了,浪费了一下午
///这里可以声明时候直接赋这些值,也可以用默认构造函数声明以后用write_1.open(),在open里写上这些参数赋值也可以
VideoWriter write_1(outFile_1, 0, r, S, true);
VideoWriter write_2(outFile_2, 0, r, S, true);
//write_1.open(outFile_1, CAP_OPENCV_MJPEG, r, S, true);
//write_2.open(outFile_2, -1, r, S, true);
if (!video.isOpened())
return 1;
cout << "\n>> 请按空格开始截取图片\n\n";
while (1)
{
///感觉下面两个意思是一样的
video >> frame; //读取当前帧
//video.read(frame);
//若视频完成播放,退出循环
if (frame.empty())
{
cout << "发生错误,请检查摄像头是否已断开!";
break;
}
///这两种方法应该也是一样的
//write_1.write(frame);
//write_2.write(frame);
write_1 << frame;
write_2 << frame;
imshow("tracker", frame); //显示当前帧
if (char(waitKey(30)) == 32) {
break;
}
}
//! [selectroi]选择目标roi以GUI的形式
cv::selectROIs("tracker", frame, rois, false);
//roi = selectROI("tracker", frame, false, false);
//! [selectroi]
if (rois.size() < 1)
{
return 0;
}
vector<Scalar> colors;
getRandomColors(colors, rois.size());
Ptr<MultiTracker> multiTracker = MultiTracker::create();
for (int i = 0; i < rois.size(); i++)
{
multiTracker->add(createTrackerByName(trackerType), frame, Rect2d(rois[i]));
vector<Point> temp;
vecPoint.push_back(temp);
}
cout << "\n>> 开始播放\n";
cout << "\n>> 如需退出请按q退出播放\n";
while (video.isOpened())
{
// get frame from the video
video >> frame;
if (frame.empty())
{
break;
}
///画框和轨迹之前保存第一个视频videoNoTrack.avi
write_1 << frame;
//write_2 << frame;
// update the tracking result
//! [update]
bool ok = multiTracker->update(frame);
//tracker->update(frame, roi);
//! [update]
//! [visualization]
// draw the tracked object
for (unsigned i = 0; i < multiTracker->getObjects().size(); i++)
{
rectangle(frame, multiTracker->getObjects()[i], colors[i], 2, 1);
}
//! [visualization]
for (int i = 0; i < multiTracker->getObjects().size(); i++)
{
Point presentPoint;
presentPoint.x = (multiTracker->getObjects()[i].x + (multiTracker->getObjects()[i].width*0.5)) * 2;
presentPoint.y = (multiTracker->getObjects()[i].y + (multiTracker->getObjects()[i].height*0.5)) * 2;
vecPoint[i].push_back(presentPoint);
}
if (vecPoint.size() > 0)
{
for (int j = 0; j < vecPoint.size(); j++)
{
for (int k = 1; k < vecPoint[j].size(); k++)
{
line(frame, vecPoint[j][k - 1], vecPoint[j][k], colors[j], 1, 8, 1);
putText(frame, "id_" + to_string(j + 1), Point(multiTracker->getObjects()[j].x, multiTracker->getObjects()[j].y - 3), FONT_HERSHEY_PLAIN, 1, colors[j], 1);
}
}
}
// show image with the tracked object
imshow("tracker", frame);
//! [visualization]
///画完框和线以后在保存第二个视频 videoWithTrack.avi
//write_1 << frame;
write_2 << frame;
if (char(waitKey(30)) == 'q') {
break;
}
}
///有网友说对videoWriter不进行release()的话会出现0KB的现象,但是我没遇到,至于videoCaptrue需不需要release()不太清楚
//video.release();
write_1.release();
write_2.release();
cout << ">> 视频保存完毕。";
cv::destroyWindow("tracker");
cout << "\n>> 播放完毕\n";
}
期间遇到的最大的困难就是0KB问题了,都在代码注释里解释了。
现在遇到的一个问题是保存的视频帧数太快,能不能用正常速率播放的问题,以后解决了再来更新。