目录
一、Lucas-Kanade光流法
1.1cv::ORB特征点提取方法
1.2 cv::calcOpticalFlowPyrLK函数
二、完整案例实现
2.1 程序代码
2.2 程序编译及输出
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*函数提取特征点。
//函数原型,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特征检测器的一个关键方法,它同时执行特征点检测和描述符计算两个步骤。该函数的参数允许用户指定输入图像、掩码(可选)、输出关键点向量和输出描述符矩阵。
//函数原型
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
函数来跟踪视频中的特征点。
//函数原型
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::calcOpticalFlowPyrLK
的 prevPts
和 nextPts
需要在每次迭代后更新,因此确保在每次迭代开始时正确地设置了这些变量是很重要的。
二、完整案例实现
2.1 程序代码
main.cpp代码,使用OpenCV的VideoCapture
类来从文件或摄像头捕获视频帧,使用ORB特征检测(cv::ORB::create,cv::ORB::detectAndCompute),使用光流法(cv::calcOpticalFlowPyrLK)跟踪特征点。获得了特征点的位置随时间的变化,进行了这些特征点绘制(在原图像上绘制圆点、变化线等),也可以进一步分析这些变化来估计振动参数,如频率、振幅等。
#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:
#/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: