1.理论部分
【参考文档】KCF目标跟踪方法分析与总结
概念
(1)判别式模型和生成式模型
- 判别式模型
根据训练数据得到分类函数和分界面,比如说根据SVM模型得到一个分界面,然后直接计算条件概率 P(y|x),我们将最大的 P(y|x)。
- 生成式模型
首先根据猫的特征学习出一个猫的模型,再根据狗的特征学习出狗的模型,之后分别计算新样本X跟三个类别的联合概率 P(y|x),然后根据贝叶斯公式,分别计算 P(y|x),选择三类中最大的 P(y|x)作为样本的分类
2.实战
2.1.在opencv中
2.1.1跟踪器结构
基本上所有的追踪方法都会继承cv::track,在使用的时候也是使用向上转型的技术(即创建父类类型的变量指向子类对象)
同时track类包含三个基本函数
①析构函数
②初始化函数init(虚函数)
③更新函数update(虚函数)
init和update都是虚函数,在相应的追踪器中实现
2.1.2. init
使用目标周围的已知边界框初始化跟踪器
参数
image 初始帧
boundingBox 初始边界框
2.1.2 update()
更新跟踪器,找到目标的新的最可能的边界框(返回到boundingBox中)
参数:
- image 当前帧
- boundingBox 表示新目标位置的边界框(如果返回为 true,则未以其他方式修改)
返回值:
True 表示目标已定位,false 表示跟踪器无法在当前帧中找到目标。请注意,后者并不意味着跟踪器已经失败,也许目标确实从框架中丢失(例如,看不见)
2.2 使用
【参考文档】opencv自带kcf算法实现目标跟踪
- 创建kcf追踪器
Ptr<Tracker> tracker = TrackerKCF::create()
- 选择目标roi(boundingbox)
这里是使用selectROI这个函数
- 功能:允许用户在给定图像上选择 ROI。
该功能创建一个窗口,并允许用户使用鼠标选择ROI。控件:使用或完成选择,使用键取消选择(函数将返回零 cv::Rect)
- 参数:
(1)windowName 窗口名
(2)img 用于选择roi的图像
(3)showCrosshair 果将显示选择矩形的真十字准线。
(4)fromCenter如果真正的选择中心将与初始鼠标位置匹配。相反情况下,选择矩形的一角将对应于初始鼠标位置。
- 初始化追踪器
tracker->init(frame, roi)
- 对每一帧进行追踪
tracker->update(frame, roi)
2.2.1 完整代码
#include <opencv2/core/utility.hpp>
#include <opencv2/tracking.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <cstring>
using namespace std;
using namespace cv;
int main() {
// declares all required variables
//! [vars]
Rect2d roi;
Mat frame;
//! [vars]
// create a tracker object
//Ptr<Tracker> tracker = Tracker::create("KCF");
Ptr<Tracker> tracker = TrackerKCF::create();
//! [create]
// set input video
//! [setvideo]
std::string video = "E:\\car.mp4";
VideoCapture cap(video);
//! [setvideo]
// get bounding box
//! [getframe]
cap >> frame;
//! [getframe]
//! [selectroi]选择目标roi以GUI的形式
roi = selectROI("tracker", frame);
//! [selectroi]
//quit if ROI was not selected
if (roi.width == 0 || roi.height == 0)
return 0;
// initialize the tracker
//! [init]
tracker->init(frame, roi);
//! [init]
// perform the tracking process
printf("Start the tracking process\n");
for (;; ) {
// get frame from the video
cap >> frame;
// stop the program if no more images
if (frame.rows == 0 || frame.cols == 0)
break;
// update the tracking result
//! [update]
tracker->update(frame, roi);
//! [update]
//! [visualization]
// draw the tracked object
rectangle(frame, roi, Scalar(255, 0, 0), 2, 1);
// show image with the tracked object
imshow("tracker", frame);
//! [visualization]
//quit on ESC button
if (waitKey(1) == 27)
break;
}
return 0;
}
3 在RM中的使用
以桂电的开源代码为例
3.1 跟踪器初始化armorTrackerInit
整体的逻辑:
3.1.1跟踪失败
//追踪失败
src(_trackerRect).copyTo(img);
if (!_tracker->update(_src, _trackerRect)) {
_trackerSuccess = false;
_searchRect.x = _trackerRect.x - _trackerRect.width;
_searchRect.y = _trackerRect.y - _trackerRect.height;
_searchRect.height = _trackerRect.height * 3;
_searchRect.width = _trackerRect.width * 3;
_searchRect &= cv::Rect2d(0, 0, _imgWidth, _imgHeight);
}
_searchRect &= cv::Rect2d(0, 0, _imgWidth, _imgHeight)
限制面积不越界
其他代码都挺简单的,就不细说了,下面给出完整片段
3.1.2 调用关系
在识别过程函数process中被调用
//识别过程函数
cv::RotatedRect ArmorDistinguish::process(const cv::Mat& src, EnemyColor enemyColor, CarType carType, bool isReset, DistinguishMode distinguishMode, float yawAngle, bool topRest) {
#ifdef USE_KCF
armorTrackerInit(src, enemyColor);
#else
imagePreprocess(src, enemyColor);
#endif
3.1.3 完整代码
#ifdef USE_KCF
//kcf追踪器
void ArmorDistinguish::armorTrackerInit(const cv::Mat& src, EnemyColor enemyColor) {
cv::Mat img;
_size = src.size();
_para.enemyColor = enemyColor;
_params.detect_thresh = 0.03f;
if (_isTrackerStart) {
//追踪失败
src(_trackerRect).copyTo(img);
if (!_tracker->update(_src, _trackerRect)) {
_trackerSuccess = false;
_searchRect.x = _trackerRect.x - _trackerRect.width;
_searchRect.y = _trackerRect.y - _trackerRect.height;
_searchRect.height = _trackerRect.height * 3;
_searchRect.width = _trackerRect.width * 3;
_searchRect &= cv::Rect2d(0, 0, _imgWidth, _imgHeight);
}
else {//_tracker->update(_src, _trackerRect)==True
//越界则需要从全图搜索
if ((_trackerRect & cv::Rect2d(0, 0, _imgWidth, _imgHeight)) != _trackerRect) {
_searchRect = cv::Rect2d(0, 0, _imgWidth, _imgHeight);
}
//搜索区域需要扩大两倍
else {
_trackerSuccess = true;
_searchRect.x = _trackerRect.x - _trackerRect.width / 2;
_searchRect.y = _trackerRect.y - _trackerRect.height / 2;
_searchRect.height = _trackerRect.height * 2;
_searchRect.width = _trackerRect.width * 2;
_searchRect &= cv::Rect2d(0, 0, _imgWidth, _imgHeight);
}//else
}//else
src(_searchRect).copyTo(_src);
imshow("img", img);
}
else {//_isTrackerStart==False
_trackerSuccess = false;
_searchRect = cv::Rect2d(0, 0, _imgWidth, _imgHeight);
_src = src;
}
imshow("_src", _src);
cv::waitKey(1);
}
#endif
3.2 识别过程函数process
仅包含kcf部分
3.2.1
3.2.2 包含kfc的代码
完整代码见https://github.com/freezing00/Baldr/blob/main/Src/armor/armorDistinguish.cpp
//识别过程函数
cv::RotatedRect ArmorDistinguish::process(const cv::Mat& src, EnemyColor enemyColor, CarType carType, bool isReset, DistinguishMode distinguishMode, float yawAngle, bool topRest) {
#ifdef USE_KCF
armorTrackerInit(src, enemyColor);
#else//正常图像的预处理
imagePreprocess(src, enemyColor);
#endif
//从轮廓中找出类似灯条矩形
//类似灯条矩形过滤器
//选择一个最优目标
if (_resultRect.size.width > 0) {
//中间点的坐标再加上restoreRect的坐标
#ifdef USE_KCF
_isReulstFind = true;
_isTrackerStart = true;
if (_isReulstFind) {
float lightBarWidthL = _leftLightBar.size.width < _leftLightBar.size.height ? _leftLightBar.size.width : _leftLightBar.size.height;
float lightBarHeightL = _leftLightBar.size.width > _leftLightBar.size.height ? _leftLightBar.size.width : _leftLightBar.size.height;
float lightBarWidthR = _rightLightBar.size.width < _rightLightBar.size.height ? _rightLightBar.size.width : _rightLightBar.size.height;
float lightBarHeightR = _rightLightBar.size.width > _rightLightBar.size.height ? _rightLightBar.size.width : _rightLightBar.size.height;
cv::Point2d recttl = cv::Point2d((double)(_leftLightBar.center.x - 2.0 * lightBarWidthL), (double)(_leftLightBar.center.y - 1.5 * lightBarHeightL));
cv::Point2d rectbr = cv::Point2d((double)(_rightLightBar.center.x + 2.0 * lightBarWidthR), (double)(_rightLightBar.center.y + 1.5 * lightBarHeightR));
recttl += cv::Point2d(_searchRect.x, _searchRect.y);
rectbr += cv::Point2d(_searchRect.x, _searchRect.y);
_trackerRect = cv::Rect2d(recttl, rectbr);
_trackerRect &= cv::Rect2d(0, 0, _imgWidth, _imgHeight);
//---------------创建KCF追踪器-----------------------------------------------
_tracker = cv::TrackerKCF::create();
_tracker->init(_src, _trackerRect);
}
resultRect.center += cv::Point2f((float)_searchRect.x, (float)_searchRect.y);
#else
_resultRect.center += cv::Point2f((float)_restoreRect.x, (float)_restoreRect.y);
#endif
_resultRect.points(_vertices);
//记录上一刻的检测区域
_resLast = _resultRect;
_lost_cnt = 0;
//记录上一刻的时间
_lastTime = (double)(cv::getTickCount());
}
else {
#ifdef USE_KCF
_isTrackerStart = false;
_isReulstFind = false;
return cv::RotatedRect();
#endif //USE_KCF
}
}
3.3 小结
在装甲板识别中使用KFC的整体逻辑