Advertisement

【OpenCv光流法进行运动目标检测】

阅读量:

光流法总结
使用总结:
目标跟踪:
光流法广泛应用于实时目标跟踪系统中。通过追踪图像中的关键特征点(如角点、质心等),可以有效识别并跟踪目标的运动轨迹。这种方法尤其适用于视频监控、自动驾驶等场景。
动作分析:
在视频分析领域,光流法可用于分析人体、车辆等物体的运动轨迹和动作。通过对多个帧进行对比和分析,可以实现动作识别、行为分析等功能。
图像稠密匹配:
光流法则被应用于计算两幅图像之间的稠密匹配关系。这种技术在立体视觉、结构运动恢复等领域具有重要意义。
运动估计:
光流法还可用于估计图像序列中物体的运动速度和方向。这对于视频压缩编码、运动估计研究等具有重要价值。
原理介绍:
基本假设:

  • 空间一致性假设:在同一局部区域内,相邻像素具有相同的运动特性。
  • 亮度恒定假设(Brightness Constancy Assumption):同一物体在不同帧中的灰度值保持不变。
    核心步骤:
  • 构建光流约束方程。
  • 求解方程以获得像素的运动向量。
  • 处理稠密光流与稀疏光流的不同应用场景。
    常用方法:
  • Lucas-Kanade方法:基于最小二乘优化求解方程组。
  • Horn-Schunck方法:通过全局优化减少噪声影响。
  • 基于特征点的方法(如SIFT)结合检测与跟踪技术。
    应用场景扩展:
  • 结合光照变化补偿技术提升鲁棒性。
  • 与其他算法结合用于复杂场景下的目标跟踪与分割。
  • 应用于医学图像分析中的动态过程可视化研究。
    总之,光流法则提供了一种高效且灵活的方法来处理动态图像中的运动问题,在计算机视觉领域具有广泛应用和技术意义。

opencv系列文章目录

文章目录

  • opencv系列文章目录
    • 前言

    • 什么是光流法?

    • 光流法实例

      • C语言的
        1. C版本
        1. C++版本
        1. Python版本
    • 总结


前言

伴随着计算机视觉技术的迅速发展,在图像处理领域中运动目标检测已经成为了不可或缺的重要组成部分。在现实生活中,在监控系统等应用环境中我们会频繁遇到需要追踪视频中的运动目标的情况。例如,在交通监控摄像头捕捉到的车辆、以及安防监控系统识别出的人行以及自动驾驶场景下的车辆与行人等场景下进行分析与判断就显得尤为重要。为了确保运动目标检测既高效又精确地识别出物体的研究者们提出了各种创新性的解决方案。

在运动目标检测领域中广泛使用的光流法(Optical Flow)是一种经过验证的经典技术。该技术通过精确追踪图像像素点的空间位移变化来实现对运动目标位置与速度信息的实时获取。该方法不仅展现出卓越的实时性能,在应对复杂环境与多类型运动变化方面也表现优异。


一、光流法是什么?

在计算机视觉领域中,光流法(Optical Flow)是一种经典的用于描述图像序列中像素点随时间的变化情况的技术。其基本概念源于分析图像中像素在相邻帧之间的变化规律,并通过分析相邻帧之间的差异来推导出场景内物体运动的重要信息。这种技术广泛应用于目标跟踪、运动分析以及视觉导航等多个领域。

光流法的核心观点认为,在短时间内,相邻帧中相邻像素的灰度值主要保持不变。基于这一核心观点,该方法通过分析图像序列中相邻帧之间的灰度值变化趋势来推断出像素点的运动信息。该方法不仅有助于推断运动目标的速度、方向及其轨迹信息;同时还可以用来实现运动目标的跟踪与分割功能。

在实际应用环境中,光流法算法提供了多样化的解决方案。其中最常用的是Lucas-Kanade算法这一方案,在该方案下它通过分析图像运动特性实现了对光流计算的有效处理过程。该算法基于以下关键假设:即图像中某一点附近的像素点具有相似的运动特性,并通过计算像素点周围区域的梯度信息以及时间维度上的亮度变化来构建方程组进而求解出精确的光流向量参数值这一结果输出。此外还有一种基于全局建模的方法即Horn-Schunck算法它则引入了另一种基本假设即整幅图像的空间内存在整体的一致性从而能够在全局范围内求解出统一且平滑的整体光流场这一结果输出

光流法的应用领域涵盖范围广。在视频压缩技术中,通过利用光流信息实现帧间估计以提高压缩效率。在自动驾驶系统研究中,在实时监控物体运动状态方面具有重要价值。特别是在医学图像处理领域,在心肌组织和血液流动情况的动态研究方面具有重要价值。

光流法是一种经典的计算机视觉技术,在探测运动目标和分析动态场景方面具有重要应用价值。其应用不仅有助于我们深入理解图像序列中的运动信息,并为该领域的发展带来了积极的影响。

1) 基于梯度的方法

该方法被称为微分法,在图像处理中通过计算时变图像灰度(及其滤波版本)的空间-时间导数(即空间-时间导数函数)来确定像素的速度矢量。

基于其计算过程的简便性及其产生的良好效果,在多个领域中得到了广泛应用并进行了深入研究。其中两个具有代表性且广为人知的例子是Horn-Schunck算法与Lucas-Kanade(LK)算法。

Horn-Schunck算法基于经典光流约束方程模型,在其基础上增加了全局平滑性这一前提假设;该假设表明,在整个图像域内所有点处的光流场都呈现出空间连续性特征。

基于该理论指导下,众多改进算法不断涌现。Nagel采用了加权矩阵来实现梯度的不同平滑处理;而针对复杂运动场景的估计问题,则有Black和Anadan提出了分段平滑方法。

  1. 基于匹配的方法

基于匹配的光流计算方法包括基于特征和区域的两种。

基于特征的技术持续地使用目标主要特征进行定位与跟踪操作,在面对目标较大运动与亮度变化时展现出良好的鲁棒性特性。然而现有技术普遍面临光流高度稀疏的问题,并且在特征提取与精确匹配方面仍面临极大的挑战

基于区域的方法首先识别出相似的区域范围,并利用这些区域的相对位置变化来计算相应的光流矢量。这种技术在视频编码中的应用非常广泛。然而,在该技术中所获得的光流场仍然不够密集。此外,在亚像素级别的光流估计方面面临较大的挑战,并且其计算复杂度较高。

3)基于能量的方法

基于能量的方法通常被称为频域方法,在应用这类技术时需要特别注意以下几点:首先,在获取均匀流动场中精确的速度估计时必须实施时间与空间上的双重滤波处理;其次,在实现时间和平面位置信息的联合过程中需要注意的是这会导致光流的空间分辨率和时间分辨率均有所下降。然而这些频域方法往往需要消耗大量计算资源;此外,在评估这些方法可靠性的过程中也会面临较大的挑战。

4)基于相位的方法

该方法由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,否则为0
        public 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开发语言编写, 实现了利用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, st
    
    gray = 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 = 0
        for i in range(points2.size):
            if i >= st.size:
                break
            if acceptTrackedPoint(points1[i], points2[i], st[i]):
                points1[k] = points1[i]
                points2[k] = points2[i]
                k += 1
    
        points1 = 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 output
    
    if __name__ == "__main__":
    # 打开视频文件
    video = cv2.VideoCapture('1.avi')
    fps = video.get(cv2.CAP_PROP_FPS)  # 获取视频帧率
    success = True
    
    while success:
        success, frame = video.read()  # 读取视频帧
        if not success:
            break
        result = tracking(frame)  # 进行目标跟踪
        cv2.imshow('result', result)  # 显示结果
        cv2.waitKey(int(1000 / int(fps)))  # 设置延迟时间,使播放速度与视频原始帧率一致
    
    video.release()  # 释放视频文件
    cv2.destroyAllWindows()  # 关闭所有窗口

这段代码利用光流法实现了视频中运动目标的跟踪。该算法采用Lucas-Kanade光流模型来计算特征点的运动轨迹。首先,在整个视频序列中每帧图像都会被转换为灰度图像处理以减少计算复杂度。接着, 该系统通过光流法计算相邻帧之间特征点的位置变化, 这一过程能够较为准确地捕捉到目标物体的姿态变化信息。随后, 在筛选出的有效追踪点的基础上进行连接处理, 最终生成完整的运动轨迹图形表示. 最后, 在原始视频序列中绘制了各追踪点的空间路径, 并使用窗口显示结果这一完整流程实现了对视频中运动目标位置信息的有效追踪和可视化展示.

复制代码
    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) & 0xff
    if 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方法等。这些方法通过最小化误差函数或优化约束条件来求解像素的运动速度。
    
    稠密光流与稀疏光流: 光流法可以得到稠密光流和稀疏光流。稠密光流计算图像中所有像素的运动信息,而稀疏光流只计算选定像素点的运动信息。

总体而言,光流法是一种基于局部运动假设的图像运动计算方法。然而,在光照变化和遮挡等情况下表现出较高的敏感度。因此,在实际应用中通常需要结合其他方法以有效应对复杂的场景。

在这里插入图片描述

全部评论 (0)

还没有任何评论哟~