1. optical_flow.cpp 稀疏光流
cpp
#include <iostream> // 引入输入输出流库
#include <opencv2/core.hpp> // 引入OpenCV的核心功能模块
#include <opencv2/highgui.hpp> // 引入OpenCV的高级GUI模块,提供显示图像的功能
#include <opencv2/imgproc.hpp> // 引入OpenCV的图像处理模块
#include <opencv2/videoio.hpp> // 引入OpenCV的视频处理模块
#include <opencv2/video.hpp> // 引入OpenCV的视频分析模块
using namespace cv; // 使用OpenCV命名空间
using namespace std; // 使用标准命名空间
int main(int argc, char **argv) // 主函数
{
const string about =
"This sample demonstrates Lucas-Kanade Optical Flow calculation.\n" // 介绍这个程序,说它展示了如何计算Lucas-Kanade光流
"The example file can be downloaded from:\n" // 提示样本文件可以从以下链接下载
" https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4";
const string keys =
"{ h help | | print this help message }" // 定义了一个帮助选项
"{ @image | vtest.avi | path to image file }"; // 定义了一个输入参数,路径到视频文件,默认值为"vtest.avi"
CommandLineParser parser(argc, argv, keys); // 使用命令行解析器来解析输入参数
parser.about(about); // 设置关于信息
if (parser.has("help")) // 如果有"help"参数
{
parser.printMessage(); // 打印帮助信息
return 0; // 并退出程序
}
string filename = samples::findFile(parser.get<string>("@image")); // 获取输入视频文件的路径
if (!parser.check()) // 检查解析的参数是否正确
{
parser.printErrors(); // 打印错误信息
return 0; // 并退出程序
}
VideoCapture capture(filename); // 创建一个VideoCapture对象来捕捉视频
if (!capture.isOpened()){ // 检查视频是否成功打开
//error in opening the video input
cerr << "Unable to open file!" << endl; // 如果没有打开成功,输出错误信息
return 0; // 并退出程序
}
// Create some random colors
vector<Scalar> colors; // 创建一个Scalar类型的向量,用于存储颜色
RNG rng; // 随机数生成器
for(int i = 0; i < 100; i++) // 生成100个随机颜色
{
int r = rng.uniform(0, 256); // 生成红色分量
int g = rng.uniform(0, 256); // 生成绿色分量
int b = rng.uniform(0, 256); // 生成蓝色分量
colors.push_back(Scalar(r,g,b)); // 存储到颜色向量中
}
Mat old_frame, old_gray; // 声明两个Mat对象,用于存储前一帧的图像及其灰度图
vector<Point2f> p0, p1; // 声明两个点的向量,用于存储角点的位置
// Take first frame and find corners in it
capture >> old_frame; // 从视频中获取第一帧图像
cvtColor(old_frame, old_gray, COLOR_BGR2GRAY); // 将图像转换成灰度图
goodFeaturesToTrack(old_gray, p0, 100, 0.3, 7, Mat(), 7, false, 0.04); // 寻找前100个强角点存入p0
// Create a mask image for drawing purposes
// 创建一个和视频帧相同大小的mask用于绘画目的
Mat mask = Mat::zeros(old_frame.size(), old_frame.type());
while(true){ // 循环处理视频的每帧图像
Mat frame, frame_gray; // 每一帧图像及其灰度图
capture >> frame; // 获取下一帧图像
if (frame.empty()) // 如果获取的帧为空,即视频结束了
break; // 跳出循环
cvtColor(frame, frame_gray, COLOR_BGR2GRAY); // 将获取的帧转换成灰度图
// calculate optical flow
vector<uchar> status; // 一个标记向量,标记对于每个点,是否找到了光流
vector<float> err; // 一个错误向量,存储了每一个点的误差
TermCriteria criteria = TermCriteria((TermCriteria::COUNT) + (TermCriteria::EPS), 10, 0.03); // 设置迭代搜索算法的终止准则(10次迭代或误差小于0.03)
calcOpticalFlowPyrLK(old_gray, frame_gray, p0, p1, status, err, Size(15,15), 2, criteria); // 计算光流,将结果存储于p1
vector<Point2f> good_new; // 存储"流过"后的点
for(uint i = 0; i < p0.size(); i++) // 遍历每一个点
{
// Select good points
if(status[i] == 1) { // 如果该点的光流是良好的
good_new.push_back(p1[i]); // 将其添加到good_new向量中
// draw the tracks
line(mask,p1[i], p0[i], colors[i], 2); // 在mask上画线追踪运动轨迹
circle(frame, p1[i], 5, colors[i], -1); // 在当前帧上对运动点进行标记
}
}
Mat img; // 声明一个Mat来存储结果图像
add(frame, mask, img); // 将原图像和mask相加得到最终的图像
imshow("Frame", img); // 显示图像
int keyboard = waitKey(30); // 等待30ms或者某个按键被按下
if (keyboard == 'q' || keyboard == 27) // 如果按下了'q'键或者Esc键
break; // 跳出循环
// Now update the previous frame and previous points
old_gray = frame_gray.clone(); // 更新前一帧的灰度图
p0 = good_new; // 更新角点
}
}
这段代码的主要功能是使用OpenCV库来实现Lucas-Kanade光流算法的计算。程序首先读取一个视频文件,然后在视频的第一帧中找到角点 ,使用这些角点计算每一帧之间的光流,从而追踪画面中的物体运动 。追踪的过程中,程序对每个有效的点在画面上绘制标记,并通过颜色区分不同的轨迹。最终显示在窗口中的是这些运动轨迹的叠加效果。当按下'q'键或Esc键时,程序结束。
calcOpticalFlowPyrLK(old_gray, frame_gray, p0, p1, status, err, Size(15,15), 2, criteria);
2. optical_flow_dense.cpp 稠密光流
cpp
#include <iostream> // 引入输入输出流库
#include <opencv2/core.hpp> // 引入OpenCV核心功能模块头文件
#include <opencv2/highgui.hpp> // 引入OpenCV高级用户界面模块头文件
#include <opencv2/imgproc.hpp> // 引入OpenCV图像处理模块头文件
#include <opencv2/videoio.hpp> // 引入OpenCV视频输入输出模块头文件
#include <opencv2/video.hpp> // 引入OpenCV视频分析模块头文件
using namespace cv; // 使用OpenCV命名空间
using namespace std; // 使用标准命名空间
int main() // 主函数
{
VideoCapture capture(samples::findFile("vtest.avi")); // 创建视频捕获对象并打开文件
if (!capture.isOpened()){ // 如果视频文件没有成功打开
cerr << "Unable to open file!" << endl; // 输出错误信息到标准错误
return 0; // 退出程序
}
Mat frame1, prvs; // 定义两个Mat对象,分别用于存储第一帧和前一帧的图像
capture >> frame1; // 从视频中读取第一帧
cvtColor(frame1, prvs, COLOR_BGR2GRAY); // 将第一帧转换为灰度图像,存储在prvs中
while(true){ // 循环处理视频中的每一帧
Mat frame2, next; // 定义两个Mat对象,用于存储当前帧和转换后的灰度帧
capture >> frame2; // 从视频中读取当前帧
if (frame2.empty()) // 如果当前帧为空,表示视频结束
break; // 跳出循环
cvtColor(frame2, next, COLOR_BGR2GRAY); // 将当前帧转换为灰度图像
Mat flow(prvs.size(), CV_32FC2); // 定义一个Mat对象,用于存储光流
calcOpticalFlowFarneback(prvs, next, flow, 0.5, 3, 15, 3, 5, 1.2, 0); // 计算前一帧和当前帧之间的光流
// 下面的代码块用于可视化光流
Mat flow_parts[2]; // 定义一个Mat数组,用于分割光流的x和y分量
split(flow, flow_parts); // 分割光流
Mat magnitude, angle, magn_norm; // 定义幅度、角度和归一化幅度的Mat对象
cartToPolar(flow_parts[0], flow_parts[1], magnitude, angle, true); // 将笛卡尔坐标转换为极坐标,获取幅度和角度
normalize(magnitude, magn_norm, 0.0f, 1.0f, NORM_MINMAX); // 将幅度归一化到0到1之间
angle *= ((1.f / 360.f) * (180.f / 255.f)); // 将角度转换到0到255之间的范围内(用于HSV颜色空间)
//构建HSV图像
Mat _hsv[3], hsv, hsv8, bgr; // 定向Mat对象,用于构建HSV图像和转换后的BGR图像
_hsv[0] = angle; // 角度作为H通道
_hsv[1] = Mat::ones(angle.size(), CV_32F); // 饱和度设置为1,全部是白色
_hsv[2] = magn_norm; // 归一化后的幅度作为V通道
merge(_hsv, 3, hsv); // 合并上面的三个通道来构建一个HSV图像
hsv.convertTo(hsv8, CV_8U, 255.0); // 将32位float型的HSV图像转换为8位unsigned char型
cvtColor(hsv8, bgr, COLOR_HSV2BGR); // 将HSV颜色空间的图像转换为BGR颜色空间
imshow("frame2", bgr); // 在窗口中显示BGR图像
int keyboard = waitKey(30); // 等待30ms的按键事件,若无事件则继续
if (keyboard == 'q' || keyboard == 27) // 如果按键是'q'或者ESC键
break; // 跳出循环,结束程序
prvs = next; // 更新前一帧的图像
}
}
该段代码是使用 OpenCV 图像处理库和视频分析库来实现对视频文件进行光流分析和可视化的程序。首先通过VideoCapture对象捕获视频文件中的帧,然后将每两帧之间的光流分析出来,并将光流的信息可视化为BGR颜色空间中的图像,最后在窗口中显示这些图像。通过键盘输入可以控制程序的结束。美观化显示的过程主要是将光流场的每个点的方向(角度)转换成颜色,速度(幅度)转换成颜色亮度来实现。这对于理解和分析视频中的运动模式非常有帮助。