opencv系列文章目录
文章目录
- opencv系列文章目录
- 前言
- 一、光流法是什么?
- 二、光流法实例
- 1.C的
- 2.C++版本
- 3.python版本
- 总结
前言
随着计算机视觉技术的迅猛发展,运动目标检测在图像处理领域中扮演着至关重要的角色。在现实世界中,我们常常需要追踪视频中的运动目标,例如交通摄像头中的车辆、安防监控中的行人,甚至是自动驾驶领域中的车辆和行人。为了实现高效准确的运动目标检测,研究者们提出了各种各样的方法。
在众多运动目标检测方法中,光流法(Optical Flow)是一种经典且有效的技术。光流法通过追踪图像中像素点的运动轨迹,可以帮助我们实时了解运动目标的位置和速度信息。它不仅在实时性上具有优势,还在处理各种环境和运动类型下都表现出色。
一、光流法是什么?
光流法(Optical Flow)是计算机视觉领域中一种经典的技术,用于描述图像序列中像素点在时间上的运动轨迹。光流法的基本思想源于图像中的像素在相邻帧之间随时间的变化,通过检测像素点的移动,我们可以获得关于场景中物体运动的重要信息。这种技术在目标跟踪、运动分析、视觉导航等领域得到广泛应用。
光流法的基本假设是,相邻帧之间相邻像素的灰度值在短时间内基本保持不变。根据这个假设,光流法通过分析图像序列中相邻帧之间的灰度值变化,推断出像素点的运动信息。它可以帮助我们了解运动目标的速度、方向和轨迹,甚至可以用于运动目标的跟踪和分割。
在实际应用中,光流法的算法有多种。其中,Lucas-Kanade方法是最常见的一种,它假设了图像中的运动是局部的,即在图像上某一点附近的像素具有相似的运动。该方法通过计算像素点周围区域的梯度和时间上的灰度变化,求解一个线性方程组,从而估计出光流向量。另外,Horn-Schunck方法则假设了整幅图像的运动是平滑的,并且在整个图像上求解一个全局的光流场。
光流法的应用非常广泛。在视频压缩中,光流信息可以用于帧内预测,提高压缩效率。在自动驾驶领域,光流法可以用于车辆和行人的运动轨迹预测,帮助车辆规划路径。在医学图像分析中,光流法可以用于心脏和血流的运动分析,帮助医生诊断心脏疾病。
总的来说,光流法作为一种经典的计算机视觉技术,为我们提供了探测运动目标、分析动态场景的重要手段。它的应用不仅帮助我们更好地理解图像序列中的运动信息,也推动了计算机视觉领域的发展。
几种光流估计算法的简介
1) 基于梯度的方法
基于梯度的方法又称为微分法,它是利用时变图像灰度(或其滤波形式)的时空微分(即时空梯度函数)来计算像素的速度矢量。
由于计算简单和较好的结果,该方法得到了广泛应用和研究。典型的代表是Horn-Schunck算法与Lucas-Kanade(LK)算法。
Horn-Schunck算法在光流基本约束方程的基础上附加了全局平滑假设,假设在整个图像上光流的变化是光滑的,即物体运动矢量是平滑的或只是缓慢变化的。
基于此思想,大量的改进算法不断提出。Nagel采用有条件的平滑约束,即通过加权矩阵的控制对梯度进行不同平滑处理;Black和Anandan针对多运动的估计问题,提出了分段平滑的方法。
- 基于匹配的方法
基于匹配的光流计算方法包括基于特征和区域的两种。
基于特征的方法不断地对目标主要特征进行定位和跟踪,对目标大的运动和亮度变化具有鲁棒性。存在的问题是光流通常很稀疏,而且特征提取和精确匹配也十分困难。
基于区域的方法先对类似的区域进行定位,然后通过相似区域的位移计算光流。这种方法在视频编码中得到了广泛的应用。然而,它计算的光流仍不稠密。另外,这两种方法估计亚像素精度的光流也有困难,计算量很大。
3)基于能量的方法
基于能量的方法又称为基于频率的方法,在使用该类方法的过程中,要获得均匀流场的准确的速度估计,就必须对输入的图像进行时空滤波处理,即对时间和空间的整合,但是这样会降低光流的时间和空间分辨率。基于频率的方法往往会涉及大量的计算,另外,要进行可靠性评价也比较困难。
4)基于相位的方法
基于相位的方法是由Fleet和Jepson提出的,Fleet和Jepson最先提出将相位信息用于光流计算的思想。当我们计算光流的时候,相比亮度信息,图像的相位信息更加可靠,所以利用相位信息获得的光流场具有更好的鲁棒性。基于相位的光流算法的优点是:对图像序列的适用范围较宽,而且速度估计比较精确,但也存在着一些问题:第一,基于相位的模型有一定的合理性,但是有较高的时间复杂性;第二,基于相位的方法通过两帧图像就可以计算出光流,但如果要提高估计精度,就需要花费一定的时间;第三,基于相位的光流计算法对图像序列的时间混叠是比较敏感的。
5)神经动力学方法
神经动力学方法是利用神经网络建立的视觉运动感知的神经动力学模型,它是对生物视觉系统功能与结构比较直接的模拟。
尽管光流计算的神经动力学方法还很不成熟,然而对它的研究却具有极其深远的意义。随着生物视觉研究的不断深入,神经方法无疑会不断完善,也许光流计算乃至计算机视觉的根本出路就在于神经机制的引入。神经网络方法是光流技术的一个发展方向。
3.稠密光流与稀疏光流
除了根据原理的不同来区分光流法外,还可以根据所形成的光流场中二维矢量的疏密程度将光流法分为稠密光流与稀疏光流两种。
二、光流法实例
1.C的
设置参数放到C#版本里面,效果好像没有C++效果来的明显,可能是一些参数的设置导致的。
代码如下(示例):
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Windows.Forms;namespace WindowsFormsApp
{public partial class Form1 : Form{// 当前图片public Mat gray = new Mat(); // 预测图片public Mat gray_prev = new Mat();// point1为特征点的原来位置,point2为特征点的新位置public Point2f[] points1;public Point2f[] points2;// 初始化跟踪点的位置public Point2f[] initial;// 检测的最大特征数public int maxCount = 500; // 特征检测的等级public double qLevel = 0.01; // 两特征点之间的最小距离public double minDist = 10.0; // 跟踪特征的状态,特征的流发现为1,否则为0public byte[] status; public float[] err;public Form1(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){var capture = new VideoCapture(@"1.avi");// 计算帧率int sleepTime = (int)Math.Round(1000 / capture.Fps);// 声明实例 Mat类Mat image = new Mat();// 进入读取视频每镇的循环while (true){capture.Read(image);//判断是否还有没有视频图像 if (image.Empty())break;Mat result = tracking(image);// 在pictureBox1中显示效果图pictureBox1.Image = BitmapConverter.ToBitmap(result);Cv2.WaitKey(sleepTime);}}//--------------------------------------// function: tracking// brief: 跟踪// parameter: frame 输入的视频帧// output 有跟踪结果的视频帧// return: void//--------------------------------------public Mat tracking(Mat frame){Mat output = new Mat();Cv2.CvtColor(frame, gray, ColorConversionCodes.BGR2GRAY);frame.CopyTo(output);// 添加特征点if (addNewPoints()){// 只用这个好像也没啥区别points1 = Cv2.GoodFeaturesToTrack(gray, maxCount, qLevel, minDist, new Mat(), 10, true, 0.04);initial = points1; 像素级检测特征点//Point2f[] po = Cv2.GoodFeaturesToTrack(gray, maxCount, qLevel, minDist, new Mat(), 3, true, 0.04); 亚像素级检测//points1 = Cv2.CornerSubPix(gray, po, new Size(5, 5), new Size(-1, -1), new TermCriteria());}if (gray_prev.Empty()){gray.CopyTo(gray_prev);}//光流金字塔,输出图二的特征点points2 = new Point2f[points1.Length];Cv2.CalcOpticalFlowPyrLK(gray_prev, gray, points1, ref points2, out status, out err);// 去掉一些不好的特征点int k = 0;for (int i = 0; i < points2.Length; i++){if (acceptTrackedPoint(i)){initial[k] = initial[i];points2[k++] = points2[i];}}// 显示特征点和运动轨迹for (int i = 0; i < k; i++){Cv2.Line(output, (Point)initial[i], (Point)points2[i],new Scalar(0, 0, 255));Cv2.Circle(output, (Point)points2[i], 3,new Scalar(0, 255, 0), -1);}// 把当前跟踪结果作为下一此参考Swap(ref points2, ref points1);Swap(ref gray_prev, ref gray);return output;}static void Swap<T>(ref T a, ref T b){T t = a;a = b;b = t;}//-------------------------------------// function: addNewPoints// brief: 检测新点是否应该被添加// parameter:// return: 是否被添加标志//-------------------------------------public bool addNewPoints(){if (points1 == null) return true;// 这个实际上是限制了点数,最好别开//return points1.Length <= 10;//System.Diagnostics.Debug.WriteLine(points1.Length);return true;}//--------------------------------------// function: acceptTrackedPoint// brief: 决定哪些跟踪点被接受// parameter:// return://-------------------------------------bool acceptTrackedPoint(int i){return status[i] == 1 && ((Math.Abs(points1[i].X - points2[i].X) + Math.Abs(points1[i].Y - points2[i].Y)) > 5);}}
}
这段代码是一个基于OpenCVSharp的C#程序,实现了光流法(Optical Flow)在视频中的运动目标跟踪。下面对代码进行逐行解释:
using OpenCvSharp; 和 using OpenCvSharp.Extensions;:导入OpenCVSharp库的命名空间,该库是OpenCV的C#封装。public partial class Form1 : Form:定义一个名为Form1的Windows窗体类,该类继承自基类Form。类中声明了一系列变量,如gray和gray_prev是用于存储灰度图像的Mat对象,points1和points2是特征点的原始和新位置,initial是特征点的初始位置,status和err是用于存储光流算法的状态和误差的数组。public Form1():构造函数,初始化窗体。private void button1_Click(object sender, EventArgs e):按钮点击事件的处理函数,该函数实现了视频的读取和光流法运动目标跟踪。var capture = new VideoCapture(@"1.avi");:打开名为"1.avi"的视频文件。Mat image = new Mat();:创建一个Mat对象用于存储视频帧。while (true):无限循环,用于处理视频的每一帧。capture.Read(image);:读取视频的一帧。Mat result = tracking(image);:调用tracking函数进行运动目标跟踪。pictureBox1.Image = BitmapConverter.ToBitmap(result);:将跟踪结果显示在Windows窗体中。public Mat tracking(Mat frame):跟踪函数,接受一个视频帧作为输入,返回一个Mat对象,其中包含了运动目标的跟踪结果。Cv2.CvtColor(frame, gray, ColorConversionCodes.BGR2GRAY);:将彩色图像转换为灰度图像。addNewPoints():检测是否需要添加新的特征点。Cv2.CalcOpticalFlowPyrLK(gray_prev, gray, points1, ref points2, out status, out err);:使用Lucas-Kanade光流法计算特征点的新位置。acceptTrackedPoint(int i):根据状态和位置信息,决定哪些跟踪点被接受。将跟踪结果画在output图像上,返回output。Swap(ref points2, ref points1); 和 Swap(ref gray_prev, ref gray);:交换points2和points1、gray_prev和gray的引用。
总的来说,这段代码实现了一个简单的视频运动目标跟踪系统。它使用Lucas-Kanade光流法计算特征点的运动轨迹,将跟踪结果实时显示在Windows窗体中。需要注意的是,该代码仅实现了基本的光流跟踪功能,实际应用中可能需要更多的优化和改进,例如处理光照变化、遮挡等问题。
2.C++版本
代码如下(示例):
#include <opencv2/video/video.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <iostream>
#include <cstdio>using namespace std;
using namespace cv;void tracking(Mat& frame, Mat& output);
bool addNewPoints();
bool acceptTrackedPoint(int i);string window_name = "optical flow tracking";
Mat gray; // 当前图片
Mat gray_prev; // 预测图片
vector<Point2f> points[2]; // point0为特征点的原来位置,point1为特征点的新位置
vector<Point2f> initial; // 初始化跟踪点的位置
vector<Point2f> features; // 检测的特征
int maxCount = 500; // 检测的最大特征数
double qLevel = 0.01; // 特征检测的等级
double minDist = 10.0; // 两特征点之间的最小距离
vector<uchar> status; // 跟踪特征的状态,特征的流发现为1,否则为0
vector<float> err;int main()
{Mat frame;Mat result;VideoCapture capture("1.avi");if (capture.isOpened()) // 摄像头读取文件开关{capture >> frame;imshow(window_name, frame);waitKey(0);while (true){capture >> frame;if (frame.empty()) break;tracking(frame, result);imshow(window_name, result);if ((char)waitKey(50) == 27){break;}}}return 0;
}//--------------------------------------
// function: tracking
// brief: 跟踪
// parameter: frame 输入的视频帧
// output 有跟踪结果的视频帧
// return: void
//--------------------------------------
void tracking(Mat& frame, Mat& output)
{//此句代码的OpenCV3版为:cvtColor(frame, gray, COLOR_BGR2GRAY);//此句代码的OpenCV2版为://cvtColor(frame, gray, CV_BGR2GRAY);frame.copyTo(output);// 添加特征点if (addNewPoints()){goodFeaturesToTrack(gray, features, maxCount, qLevel, minDist);points[0].insert(points[0].end(), features.begin(), features.end());initial.insert(initial.end(), features.begin(), features.end());}if (gray_prev.empty()){gray.copyTo(gray_prev);}// l-k光流法运动估计calcOpticalFlowPyrLK(gray_prev, gray, points[0], points[1], status, err);// 去掉一些不好的特征点int k = 0;for (size_t i = 0; i < points[1].size(); i++){if (acceptTrackedPoint(i)){initial[k] = initial[i];points[1][k++] = points[1][i];}}points[1].resize(k);initial.resize(k);// 显示特征点和运动轨迹for (size_t i = 0; i < points[1].size(); i++){line(output, initial[i], points[1][i], Scalar(0, 0, 255));circle(output, points[1][i], 3, Scalar(0, 255, 0), -1);}// 把当前跟踪结果作为下一此参考swap(points[1], points[0]);swap(gray_prev, gray);
}//-------------------------------------
// function: addNewPoints
// brief: 检测新点是否应该被添加
// parameter:
// return: 是否被添加标志
//-------------------------------------
bool addNewPoints()
{return points[0].size() <= 10;
}//--------------------------------------
// function: acceptTrackedPoint
// brief: 决定哪些跟踪点被接受
// parameter:
// return:
//-------------------------------------
bool acceptTrackedPoint(int i)
{return status[i] && ((abs(points[0][i].x - points[1][i].x) + abs(points[0][i].y - points[1][i].y)) > 2);
}
VideoCapture capture("1.avi");:打开名为"1.avi"的视频文件。
while (true):无限循环,用于处理视频的每一帧。
cvtColor(frame, gray, COLOR_BGR2GRAY);:将彩色图像转换为灰度图像。
goodFeaturesToTrack(gray, features, maxCount, qLevel, minDist);:检测图像中的好的特征点。
calcOpticalFlowPyrLK(gray_prev, gray, points[0], points[1], status, err);:使用Lucas-Kanade光流法计算特征点的新位置。
line(output, initial[i], points[1][i], Scalar(0, 0, 255)); 和 circle(output, points[1][i], 3, Scalar(0, 255, 0), -1);:绘制特征点的轨迹和新位置。
swap(points[1], points[0]); 和 swap(gray_prev, gray);:交换points[1]和points[0]、gray_prev和gray的引用,为下一帧的计算做准备。
这段代码实现了基于Lucas-Kanade光流法的简单目标跟踪系统。在每一帧图像中,它通过检测特征点,并使用光流法计算特征点的运动轨迹,然后在图像上绘制特征点的运动路径,从而实现了目标的跟踪。
3.python版本
import cv2
import numpy as np# 初始化全局变量
gray_prev = None # 上一帧的灰度图像
points1 = None # 上一帧的特征点
points2 = None # 当前帧的特征点
st = None # 特征点的状态# 判断特征点是否应该被接受
def acceptTrackedPoint(a, b, c):return (c == 1) and ((abs(a[0][0] - b[0][0]) - abs(a[0][1] - b[0][1])) > 2)# 交换两个变量的值
def swap(a, b):return b, a# 跟踪函数
def tracking(frame):global gray_prev, points1, points2, stgray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 将彩色图像转换为灰度图像output = frame.copy()# 添加特征点if gray_prev is None:gray_prev = gray.copy()else:# 计算光流points2, st, err = cv2.calcOpticalFlowPyrLK(gray_prev, gray, points1, None)# 去掉一些不好的特征点k = 0for i in range(points2.size):if i >= st.size:breakif acceptTrackedPoint(points1[i], points2[i], st[i]):points1[k] = points1[i]points2[k] = points2[i]k += 1points1 = points1[:k]points2 = points2[:k]# 显示特征点和运动轨迹for (new, old) in zip(points2, points1):a, b = new.ravel()c, d = old.ravel()output = cv2.line(output, (int(a), int(b)), (int(c), int(d)), (0, 0, 255), 1)output = cv2.circle(output, (int(c), int(d)), 3, (0, 255, 0), -1)# 把当前跟踪结果作为下一次参考points1, points2 = swap(points1, points2)gray_prev, gray = swap(gray_prev, gray)return outputif __name__ == "__main__":# 打开视频文件video = cv2.VideoCapture('1.avi')fps = video.get(cv2.CAP_PROP_FPS) # 获取视频帧率success = Truewhile success:success, frame = video.read() # 读取视频帧if not success:breakresult = tracking(frame) # 进行目标跟踪cv2.imshow('result', result) # 显示结果cv2.waitKey(int(1000 / int(fps))) # 设置延迟时间,使播放速度与视频原始帧率一致video.release() # 释放视频文件cv2.destroyAllWindows() # 关闭所有窗口
这段代码通过光流法实现了视频中运动目标的跟踪。它使用Lucas-Kanade光流法来估计特征点的运动轨迹。首先,它将视频的每一帧转换为灰度图像。然后,它利用光流法计算相邻两帧之间的特征点的运动。接着,通过acceptTrackedPoint函数筛选出好的特征点,并将它们连接起来形成运动轨迹。最终,它在原始视频帧上绘制了特征点的运动轨迹,并通过窗口展示结果。整个过程实现了对视频中运动目标的简单跟踪。
import numpy as np
import cv2# 打开默认摄像头(通常是编号为0的摄像头)
cap = cv2.VideoCapture(0)# ShiTomasi角点检测的参数
feature_params = dict(maxCorners=100, # 最多返回的角点数qualityLevel=0.3, # 角点的质量水平minDistance=7, # 角点之间的最小距离blockSize=7) # 计算角点检测时的窗口大小# 光流法参数
lk_params = dict(winSize=(15, 15), # 搜索窗口的大小maxLevel=2, # 金字塔的最大层数criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)) # 迭代终止条件# 随机生成100个颜色,用于绘制跟踪点的轨迹
color = np.random.randint(0, 255, (100, 3))# 读取视频的第一帧
ret, old_frame = cap.read()
# 将第一帧转换为灰度图像
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# 使用ShiTomasi角点检测方法找到角点
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# 创建一个和视频帧大小相同的黑色掩码图片
mask = np.zeros_like(old_frame)while True:# 读取视频帧ret, frame = cap.read()# 将当前帧转换为灰度图像frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)# 计算光流以获取点的新位置p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)# 选择good points(即成功跟踪的点)good_new = p1[st == 1]good_old = p0[st == 1]# 绘制跟踪框和跟踪点的轨迹for i, (new, old) in enumerate(zip(good_new, good_old)):a, b = new.ravel() # 获取新点的坐标c, d = old.ravel() # 获取旧点的坐标# 在掩码图片上绘制轨迹mask = cv2.line(mask, (int(a), int(b)), (int(c), int(d)), color[i].tolist(), 2)# 在当前帧上绘制跟踪点frame = cv2.circle(frame, (int(a), int(b)), 5, color[i].tolist(), -1)# 将轨迹绘制到当前帧上img = cv2.add(frame, mask)# 显示带有跟踪点轨迹的当前帧cv2.imshow('frame', img)# 按下ESC键退出循环k = cv2.waitKey(30) & 0xffif k == 27:break# 更新旧帧和旧点old_gray = frame_gray.copy()p0 = good_new.reshape(-1, 1, 2)# 关闭所有窗口,释放摄像头
cv2.destroyAllWindows()
cap.release()
上述代码实现了一个基于光流法的实时目标跟踪系统。该系统使用了OpenCV的光流法函数来追踪视频帧中的角点。下面是代码的主要步骤和功能总结:
摄像头初始化: 代码使用OpenCV的VideoCapture类初始化摄像头,获取视频帧。角点检测: 使用ShiTomasi角点检测方法在视频的第一帧中找到特征点(角点),这些点将作为跟踪的起始点。光流估计: 利用Lucas-Kanade光流法(cv2.calcOpticalFlowPyrLK函数)计算特征点在下一帧中的位置。特征点筛选: 根据光流法的输出,筛选出成功跟踪的特征点。绘制跟踪结果: 将跟踪点绘制在当前视频帧上,并用线连接跟踪点的轨迹,形成了目标的运动轨迹。循环处理: 通过循环不断读取视频帧,进行光流法跟踪,并在每一帧上绘制跟踪点的轨迹。用户退出: 当用户按下ESC键时,程序退出循环,关闭窗口,释放摄像头资源。
这段代码演示了光流法在实时视频中的应用,能够捕捉目标在连续帧之间的运动轨迹,具有实时性和动态性。在这个例子中,代码使用Lucas-Kanade光流法实现了简单的目标跟踪,可以通过调整检测参数、光流法参数以及绘制效果来适应不同的场景和需求。
总结
提示:这里对文章进行总结:
光流法(Optical Flow)是一种计算图像中像素运动的技术。它基于图像序列中相邻帧之间像素灰度值的变化,通过分析这些变化,推断出像素在图像中的运动情况。光流法常被用于运动目标跟踪、视频压缩、图像稠密匹配等计算机视觉领域的任务。以下是光流法的使用总结和原理介绍:
使用总结:
目标跟踪: 光流法可以用于实时目标跟踪,通过追踪图像中的特征点,了解目标在视频帧之间的运动轨迹。动作分析: 在视频分析中,光流法可用于分析人体、车辆等物体的运动轨迹和动作,从而实现动作识别、行为分析等应用。图像稠密匹配: 光流法可以用于计算两幅图像之间的稠密匹配,即找到两幅图像中每个像素的对应点,通常在立体视觉和结构运动恢复中应用广泛。运动估计: 光流法用于估计图像序列中物体的运动速度和方向,这对于视频压缩和运动估计相关研究非常重要。
原理介绍:
光流法基于以下假设和原理:
空间一致性假设: 在图像的局部区域内,相邻像素的运动是一致的。这意味着,相邻像素的灰度值变化是由相同的运动引起的。亮度恒定假设(Brightness Constancy Assumption): 在短时间内,运动物体的像素灰度值保持不变。这意味着,同一物体上的像素在不同帧之间的灰度值保持恒定,变化的灰度值是由于光照变化或阴影引起的。
基于上述假设,光流法通常使用以下步骤进行计算:
构建光流约束方程: 通过亮度恒定假设,可以得到一个描述相邻帧像素之间关系的方程。光流方程可以通过对图像中每个像素应用亮度恒定假设得到。求解光流方程: 光流方程通常是一个局部的非线性方程组。常见的求解方法有Lucas-Kanade方法、Horn-Schunck方法等。这些方法通过最小化误差函数或优化约束条件来求解像素的运动速度。稠密光流与稀疏光流: 光流法可以得到稠密光流和稀疏光流。稠密光流计算图像中所有像素的运动信息,而稀疏光流只计算选定像素点的运动信息。
总的来说,光流法是一种基于局部运动假设的计算图像运动的方法。然而,它对光照变化和遮挡等情况敏感,因此在实际应用中,通常需要结合其他方法来处理复杂场景。