C/C++开发,opencv光流法跟踪特征点

目录

一、Lucas-Kanade光流法

1.1cv::ORB特征点提取方法

[1.2 cv::calcOpticalFlowPyrLK函数](#1.2 cv::calcOpticalFlowPyrLK函数)

二、完整案例实现

[2.1 程序代码](#2.1 程序代码)

[2.2 程序编译及输出](#2.2 程序编译及输出)

[2.3 读取视频文件方式补充](#2.3 读取视频文件方式补充)


一、Lucas-Kanade光流法

在 OpenCV 中,使用 特征检测器(例如ORB ,Oriented FAST and Rotated BRIEF,更多的特征点提取方法见本栏博文:C/C++开发,opencv-features2d模块,SIFT等特征检测器应用-CSDN博客)与 cv::calcOpticalFlowPyrLK 光流跟踪器结合是一种有效的方法,用于跟踪视频序列中的关键点变化。

1.1cv::ORB特征点提取方法

在OpenCV中,ORB(Oriented FAST and Rotated BRIEF)是一种快速的特征点检测和描述符提取算法,它结合了FAST(Features from Accelerated Segment Test)特征点检测器和BRIEF(Binary Robust Independent Elementary Features)描述符的优点,并增加旋转不变性和尺度不变性。

在实际使用中,主要用到cv::ORB::create创建对象,然后使用detect*函数提取特征点。

cpp 复制代码
//函数原型,OpenCV 4.x为例
cv::Ptr<cv::ORB> cv::ORB::create(  
    int nfeatures = 500,         // 最大特征点数量  
    float scaleFactor = 1.2f,    // 金字塔图像间的尺度参数(每层图像之间的尺度比例)  
    int nlevels = 8,             // 金字塔层数(包括原始图像层)  
    int edgeThreshold = 31,      // 用于FAST检测的边缘阈值(边缘像素不会被认为是关键点)  
    int firstLevel = 0,          // 使用的金字塔的第一层(通常设置为0)  
    int WTA_K = 2,               // 生成描述子时用于局部比较的点数  
    int scoreType = cv::ORB::HARRIS_SCORE, // 角点检测评分类型(HARRIS_SCORE或FAST_SCORE)  
    int patchSize = 31,          // 用于描述子计算的邻域大小(以像素为单位)  
    int fastThreshold = 20        // FAST角点检测的阈值(低于此值的点不会被认为是角点)  
);

cv::ORB::create 是一个静态成员函数,用于创建并配置一个ORB(Oriented FAST and Rotated BRIEF)特征检测器对象。cv::ORB::create 函数允许你通过传递不同的参数来定制ORB检测器的行为。这个函数是可选的,因为cv::Ptr<cv::ORB>可以直接通过new关键字和构造函数来创建ORB对象,但使用create函数是一种更简洁、更现代的方式,它会自动处理内存管理(通过智能指针)。

cv::ORB::detectAndCompute 函数是ORB特征检测器的一个关键方法,它同时执行特征点检测和描述符计算两个步骤。该函数的参数允许用户指定输入图像、掩码(可选)、输出关键点向量和输出描述符矩阵。

cpp 复制代码
//函数原型
void cv::ORB::detectAndCompute(InputArray image,  
                               InputArray mask,  
                               std::vector<KeyPoint>& keypoints,  
                               OutputArray descriptors,  
                               bool useProvidedKeyPoints = false);
/*
参数:
    1)image (InputArray): 输入图像,可以是灰度图像或彩色图像。ORB算法通常在灰度图像上运行得更好,因此如果输入是彩色图像,ORB可能会在内部将其转换为灰度图。
    2)mask (InputArray, 可选): 一个与输入图像大小相同的掩码图像,用于指定哪些区域应该被考虑用于特征检测。掩码图像必须是单通道、8位深度的图像,其中非零像素表示要检测特征的区域,零像素表示要忽略的区域。如果掩码为空(即传递Mat()),则没有区域会被忽略。
    3)keypoints (std::vector<KeyPoint>&): 输出参数,用于存储检测到的关键点。每个关键点包含位置(x, y)、尺度(size)、方向(angle,对于ORB是可选的,因为ORB可能不计算方向)等信息。
    4)descriptors (OutputArray): 输出参数,用于存储关键点的描述符。ORB使用BRIEF或rBRIEF作为描述符,因此每个描述符是一个二进制字符串,通常表示为一个uchar类型的向量或矩阵。
    5)useProvidedKeyPoints (bool, 默认为false): 一个可选参数,指定是否使用用户提供的关键点来计算描述符。如果设置为true,则detectAndCompute函数将仅计算由keypoints参数提供的关键点的描述符,而不会执行特征点检测。这通常用于当你已经有了关键点位置,但需要计算这些关键点的描述符时。
*/

调整cv::ORB::create函数中的参数,如nfeatures(特征点数量)、scaleFactor(尺度因子)、nlevels(金字塔层数)等,可以控制ORB检测器的detectAndCompute 行为,以适应不同的应用场景和需求。

1.2 cv::calcOpticalFlowPyrLK函数

在C++中使用OpenCV库进行光流法(Lucas-Kanade)跟踪特征点是一种常见的技术,特别适用于视频测振处理、运动估计和物体跟踪等应用。OpenCV中的光流法实现是通过cv::calcOpticalFlowPyrLK函数来跟踪视频中的特征点。

cpp 复制代码
//函数原型
void cv::calcOpticalFlowPyrLK(InputArray prevImg, InputArray nextImg,  
                              InputArray prevPts, OutputArray nextPts,  
                              OutputArray status,  
                              OutputArray err,  
                              Size winSize = Size(21, 21),  
                              int maxLevel = 3,  
                              TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),  
                              int flags = 0,  
                              double minEigThreshold = 1e-4);
/*
参数说明:
    prevImg:前一帧图像,类型为 CV_8UC1 或 CV_32FC1。
    nextImg:当前帧图像,与 prevImg 类型相同。
    prevPts:前一帧中特征点的坐标,类型为 Point2f 的 vector 或 Mat。
    nextPts:输出参数,存储当前帧中对应特征点的坐标,类型与 prevPts 相同。
    status:输出状态数组,用于表示每个特征点是否成功找到对应点,类型为 uchar 的 vector 或 Mat。
    err:输出数组,存储每个特征点的光流误差估计,类型为 float 的 vector 或 Mat。
    winSize:搜索窗口的大小,默认为 Size(21, 21)。
    maxLevel:金字塔的最大层数,默认为 3。
    criteria:迭代算法的终止条件,默认为迭代 30 次或达到某个最小误差(0.01)。
    flags:操作标志,默认为 0。
    minEigThreshold:最小特征值阈值,用于检测跟踪质量,默认为 1e-4。
*/

通常是先读取视频或捕获摄像头,选用第一帧中检测 ORB 特征点,并将它们作为初始点集传递给 cv::calcOpticalFlowPyrLK。然后,在视频序列的每一帧中,计算这些点的光流,并更新它们的位置。通常,由于使用了光流跟踪,因此不需要在每帧中都重新检测特征点,除非跟踪失败或需要重新初始化。

在实际应用中,由于遮挡、快速运动或光照变化等原因,一些特征点可能会丢失或变得不可靠。因此,可能需要实现一些额外的逻辑来处理这些情况,比如重新检测丢失的特征点或删除不可靠的跟踪点。

由于 cv::calcOpticalFlowPyrLKprevPtsnextPts 需要在每次迭代后更新,因此确保在每次迭代开始时正确地设置了这些变量是很重要的。

二、完整案例实现

2.1 程序代码

main.cpp代码,使用OpenCV的VideoCapture类来从文件或摄像头捕获视频帧,使用ORB特征检测(cv::ORB::create,cv::ORB::detectAndCompute),使用光流法(cv::calcOpticalFlowPyrLK)跟踪特征点。获得了特征点的位置随时间的变化,进行了这些特征点绘制(在原图像上绘制圆点、变化线等),也可以进一步分析这些变化来估计振动参数,如频率、振幅等。

cpp 复制代码
#include <opencv2/opencv.hpp>  
#include <vector>
#include <iostream>

int main(int argc, char* argv[]) 
{  
    cv::VideoCapture cap; // 视频捕获对象 
    if(argc< 2){ 
        cap.open(0);        //0是摄像头的索引
	}else{        
        // 加载视频 
        cap.open(argv[1]);  //如果是视频文件,则传入文件路径  
    }
    // cv::VideoCapture cap(0); // 0是摄像头的索引,如果是视频文件,则传入文件路径  
    if (!cap.isOpened()) {  
        std::cerr << "Error opening video stream or file" << std::endl;  
        return -1;  
    }  
	
	cv::Mat frame, gray, prevGray;  
    cv::Ptr<cv::ORB> detector = cv::ORB::create(500); // 创建ORB检测器,这里设置最大特征点数为500  
  
    std::vector<cv::Point2f> prevPts, nextPts;  
    std::vector<uchar> status;  
    std::vector<float> err;  
  
    // 读取第一帧并检测ORB特征点  
    if (cap.read(frame)) {  
        cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);  
        std::vector<cv::KeyPoint> keypoints;  
        cv::Mat descriptors;  
        detector->detectAndCompute(gray, cv::Mat(), keypoints, descriptors);  
  
        // 将关键点坐标转换为Point2f  
        for (const auto& kp : keypoints) {  
            prevPts.push_back(kp.pt);  
        }   
    }  
	prevGray = gray.clone();  
    // 循环读取视频帧并计算光流  
    while (cap.read(frame)) {  
        cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);  
  
        // 使用金字塔Lucas-Kanade方法计算光流  
        cv::calcOpticalFlowPyrLK(prevGray, gray, prevPts, nextPts, status, err);  
  
        // 绘制跟踪点  
        for (size_t i = 0; i < nextPts.size(); i++) {  
            if (status[i]) {  
                // cv::circle(frame, prevPts[i], 2, cv::Scalar(0, 255, 0), -1); 
                cv::circle(frame, nextPts[i], 2, cv::Scalar(255, 0, 0), -1); 
                cv::line(frame, prevPts[i], nextPts[i], cv::Scalar(0, 0, 255), 1); 
                //可以存储nextPts点,形成时间序列信息
                //进一步分析prevPts, nextPts点变化来估计振动参数,如频率、振幅等。这通常涉及到信号处理技术,如傅里叶变换等。
            }  
        } 
        // 更新前一帧的跟踪点  
        prevPts = nextPts;  
        std::vector<cv::Point2f>().swap(nextPts); // 清空nextPts以准备下一帧使用  
        // 更新前一帧的灰度图  
        prevGray = gray.clone();  

        // 显示结果  
        cv::imshow("Frame", frame);  
        char c = (char)cv::waitKey(25);  
        if (c == 27) break; // 按ESC退出  
    }  
    cap.release();  
    cv::destroyAllWindows();  
    return 0;  
}
2.2 程序编译及输出

本文是采用win系统下,opencv采用MinGW编译的静态库(C/C++开发,win下OpenCV+MinGW编译环境搭建_opencv mingw-CSDN博客),建立makefile:

bash 复制代码
#/bin/sh
#win32
CX= g++ -DWIN32 
#linux
#CX= g++ -Dlinux 

BIN 		:= ./
TARGET      := Video_vibrometer.exe
FLAGS		:= -std=c++11 -static
SRCDIR 		:= ./
#INCLUDES
INCLUDEDIR 	:= -I"../../opencv_MinGW/include" -I"./"
#-I"$(SRCDIR)"
staticDir   := ../../opencv_MinGW/x64/mingw/staticlib/
#LIBDIR		:= $(staticDir)/libopencv_world460.a\
#			   $(staticDir)/libade.a \
#			   $(staticDir)/libIlmImf.a \
#			   $(staticDir)/libquirc.a \
#			   $(staticDir)/libzlib.a \
#			   $(wildcard $(staticDir)/liblib*.a) \
#			   -lgdi32 -lComDlg32 -lOleAut32 -lOle32 -luuid 
#opencv_world放弃前,然后是opencv依赖的第三方库,后面的库是MinGW编译工具的库

LIBDIR 	    := -L $(staticDir) -lopencv_world460 -lade -lIlmImf -lquirc -lzlib \
				-llibjpeg-turbo -llibopenjp2 -llibpng -llibprotobuf -llibtiff -llibwebp \
				-lgdi32 -lComDlg32 -lOleAut32 -lOle32 -luuid 
source		:= $(wildcard $(SRCDIR)/*.cpp) 

$(TARGET) :
	$(CX) $(FLAGS) $(INCLUDEDIR) $(source)  -o $(BIN)/$(TARGET) $(LIBDIR)

clean:
	rm  $(BIN)/$(TARGET)

编译如下:

程序运行输出如下,本测试没有指定视频图像,采用笔记本的摄像头抓取视频:

PS:读者可以调整参数、图像光影效果等验证测试。

2.3 读取视频文件方式补充

PS:如果读取视频,有些格式(如mp4)需要视频动态库支持,记得把相关的视频动态库拷贝进来。

mingw:

vc:

相关推荐
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
青花瓷5 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
幺零九零零6 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
捕鲸叉6 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
ctrey_7 小时前
2024-11-1 学习人工智能的Day20 openCV(2)
人工智能·opencv·学习
Dola_Pan7 小时前
C++算法和竞赛:哈希算法、动态规划DP算法、贪心算法、博弈算法
c++·算法·哈希算法
yanlou2337 小时前
KMP算法,next数组详解(c++)
开发语言·c++·kmp算法
小林熬夜学编程8 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法