概述
理论
在本教程中,我们将使用它们来比较AKAZE和ORB局部特征,以查找视频帧和跟踪对象移动之间的匹配。
算法如下:
- 检测并描述第一帧上的关键点,手动设置对象边界
- 对于每个下一帧:
- 检测并描述关键点
- 使用强力匹配器匹配它们
- 使用RANSAC估计单应性转换
- 过滤所有匹配的内部因素
- 将单应变换应用于边界框以查找对象
- 绘制边界框和内部,计算内部比率作为评估指标
代码
#include <opencv2/features2d.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
#include <iomanip>
#include "stats.h" // Stats structure definition
#include "utils.h" // Drawing and printing functions
using namespace std;
using namespace cv;
const double akaze_thresh = 3e-4; // AKAZE detection threshold set to locate about 1000 keypoints
const double ransac_thresh = 2.5f; // RANSAC inlier threshold
const double nn_match_ratio = 0.8f; // Nearest-neighbour matching ratio
const int bb_min_inliers = 100; // Minimal number of inliers to draw bounding box
const int stats_update_period = 10; // On-screen statistics are updated every 10 frames
class Tracker
{
public:
Tracker(Ptr<Feature2D> _detector, Ptr<DescriptorMatcher> _matcher) :
detector(_detector),
matcher(_matcher)
{}
void setFirstFrame(const Mat frame, vector<Point2f> bb, string title, Stats& stats);
Mat process(const Mat frame, Stats& stats);
Ptr<Feature2D> getDetector() {
return detector;
}
protected:
Ptr<Feature2D> detector;
Ptr<DescriptorMatcher> matcher;
Mat first_frame, first_desc;
vector<KeyPoint> first_kp;
vector<Point2f> object_bb;
};
void Tracker::setFirstFrame(const Mat frame, vector<Point2f> bb, string title, Stats& stats)
{
first_frame = frame.clone();
detector->detectAndCompute(first_frame, noArray(), first_kp, first_desc);
stats.keypoints = (int)first_kp.size();
drawBoundingBox(first_frame, bb);
putText(first_frame, title, Point(0, 60), FONT_HERSHEY_PLAIN, 5, Scalar::all(0), 4);
object_bb = bb;
}
Mat Tracker::process(const Mat frame, Stats& stats)
{
vector<KeyPoint> kp;
Mat desc;
detector->detectAndCompute(frame, noArray(), kp, desc);
stats.keypoints = (int)kp.size();
vector< vector<DMatch> > matches;
vector<KeyPoint> matched1, matched2;
matcher->knnMatch(first_desc, desc, matches, 2);
for(unsigned i = 0; i < matches.size(); i++) {
if(matches[i][0].distance < nn_match_ratio * matches[i][1].distance) {
matched1.push_back(first_kp[matches[i][0].queryIdx]);
matched2.push_back(
kp[matches[i][0].trainIdx]);
}
}
stats.matches = (int)matched1.size();
Mat inlier_mask, homography;
vector<KeyPoint> inliers1, inliers2;
vector<DMatch> inlier_matches;
if(matched1.size() >= 4) {
homography = findHomography(Points(matched1), Points(matched2),
RANSAC, ransac_thresh, inlier_mask);
}
if(matched1.size() < 4 || homography.empty()) {
Mat res;
hconcat(first_frame, frame, res);
stats.inliers = 0;
stats.ratio = 0;
return res;
}
for(unsigned i = 0; i < matched1.size(); i++) {
if(inlier_mask.at<uchar>(i)) {
int new_i = static_cast<int>(inliers1.size());
inliers1.push_back(matched1[i]);
inliers2.push_back(matched2[i]);
inlier_matches.push_back(DMatch(new_i, new_i, 0));
}
}
stats.inliers = (int)inliers1.size();
stats.ratio = stats.inliers * 1.0 / stats.matches;
vector<Point2f> new_bb;
perspectiveTransform(object_bb, new_bb, homography);
Mat frame_with_bb = frame.clone();
if(stats.inliers >= bb_min_inliers) {
drawBoundingBox(frame_with_bb, new_bb);
}
Mat res;
drawMatches(first_frame, inliers1, frame_with_bb, inliers2,
inlier_matches, res,
Scalar(255, 0, 0), Scalar(255, 0, 0));
return res;
}
int main(int argc, char **argv)
{
if(argc < 4) {
cerr << "Usage: " << endl <<
"akaze_track input_path output_path bounding_box" << endl;
return 1;
}
VideoCapture video_in(argv[1]);
VideoWriter
video_out(argv[2],
(int)video_in.get(CAP_PROP_FOURCC),
(int)video_in.get(CAP_PROP_FPS),
Size(2 * (int)video_in.get(CAP_PROP_FRAME_WIDTH),
2 * (int)video_in.get(CAP_PROP_FRAME_HEIGHT)));
if(!video_in.isOpened()) {
cerr << "Couldn't open " << argv[1] << endl;
return 1;
}
if(!video_out.isOpened()) {
cerr << "Couldn't open " << argv[2] << endl;
return 1;
}
vector<Point2f> bb;
FileStorage fs(argv[3], FileStorage::READ);
if(fs["bounding_box"].empty()) {
cerr << "Couldn't read bounding_box from " << argv[3] << endl;
return 1;
}
fs["bounding_box"] >> bb;
Stats stats, akaze_stats, orb_stats;
Ptr<AKAZE> akaze = AKAZE::create();
akaze->setThreshold(akaze_thresh);
Ptr<ORB> orb = ORB::create();
orb->setMaxFeatures(stats.keypoints);
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");
Tracker akaze_tracker(akaze, matcher);
Tracker orb_tracker(orb, matcher);
Mat frame;
video_in >> frame;
akaze_tracker.setFirstFrame(frame, bb, "AKAZE", stats);
orb_tracker.setFirstFrame(frame, bb, "ORB", stats);
Stats akaze_draw_stats, orb_draw_stats;
int frame_count = (int)video_in.get(CAP_PROP_FRAME_COUNT);
Mat akaze_res, orb_res, res_frame;
for(int i = 1; i < frame_count; i++) {
bool update_stats = (i % stats_update_period == 0);
video_in >> frame;
akaze_res = akaze_tracker.process(frame, stats);
akaze_stats += stats;
if(update_stats) {
akaze_draw_stats = stats;
}
orb->setMaxFeatures(stats.keypoints);
orb_res = orb_tracker.process(frame, stats);
orb_stats += stats;
if(update_stats) {
orb_draw_stats = stats;
}
drawStatistics(akaze_res, akaze_draw_stats);
drawStatistics(orb_res, orb_draw_stats);
vconcat(akaze_res, orb_res, res_frame);
video_out << res_frame;
cout << i << "/" << frame_count - 1 << endl;
}
akaze_stats /= frame_count - 1;
orb_stats /= frame_count - 1;
printStatistics("AKAZE", akaze_stats);
printStatistics("ORB", orb_stats);
return 0;
}
解释
跟踪器类:该类使用给定的特征检测器和描述符匹配器实现abobve描述的算法。
- 设置第一帧
- 我们计算并存储第一帧中的关键点和描述符,并为输出做好准备。
- 我们需要保存检测到的关键点数量,以确保两个探测器的位置大致相同。
- 处理帧
- 找到关键点和计算描述符:要找到帧之间的匹配,我们必须首先找到关键点。在本教程中,检测器设置为在每个帧上找到大约1000个关键点。
- 使用2-nn匹配器查找对应关系:如果最接近的匹配是nn_match_ratio比第二个最接近的匹配更近,那么它就是匹配。
- 使用RANSAC估计单应性变换:如果至少有4个匹配,我们可以使用随机样本共识来估计图像变换。
- 保存内部:由于findHomography计算内点,我们只需保存选定的点和匹配。
- 项目对象边界框:如果有合理数量的内点,我们可以使用估计变换来定位对象。
效果
最后
以上就是俏皮音响为你收集整理的Opencv2D特征框架---AKAZE和ORB平面跟踪理论代码解释效果的全部内容,希望文章能够帮你解决Opencv2D特征框架---AKAZE和ORB平面跟踪理论代码解释效果所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复