光流法(运动检测)

一、算法简介

        光流法(Optical Flow)是一种用于计算图像序列中像素点运动的技术。它在计算机视觉中有广泛的应用,包括运动检测、对象跟踪和视频稳定等。

工作原理:通过分析图像序列中的像素运动,估计每个像素的运动矢量场,但是算法基于下面两个核心假设:

  1. 亮度恒定假设:

    假设在图像序列中,像素点在运动过程中,其亮度值保持不变。这意味着如果一个像素在某个位置具有某个亮度值,那么在下一个时间点,该像素在新的位置上应具有相同的亮度值。
  2. 小位移假设:

    假设图像在相邻帧之间的位移很小,因此可以近似地认为像素的运动在短时间内是连续且平滑的。

应用场景:运动检测、对象跟踪、手势识别、交通监控等等。

局限性:光流法虽然在计算机视觉和图像处理领域有许多应用,但也存在一些局限性。以下是光流法的一些主要局限性:

1. 对亮度恒定假设的依赖

光流法假设图像亮度在运动过程中保持恒定。然而,在实际场景中,由于光照变化、阴影和反射等因素,亮度可能会发生显著变化,导致光流估计不准确。

2. 噪声敏感性

光流法对图像噪声较为敏感。噪声会影响图像梯度的计算,从而导致光流估计误差。在实际应用中,通常需要额外的滤波和预处理步骤来减小噪声的影响。

3. 大位移处理困难

传统的光流算法(如Lucas-Kanade方法)在处理大位移(快速运动)时效果不佳。这是因为这些算法假设运动是小范围和连续的。处理大位移通常需要多尺度金字塔技术,但这会增加计算复杂度。

4. 遮挡问题

当一个物体在运动过程中被其他物体遮挡时,光流法难以准确估计被遮挡区域的运动。这可能导致运动场的不连续和错误估计。

5. 计算复杂度

尽管一些光流算法可以实时运行,但在处理高分辨率图像或复杂场景时,计算复杂度仍然较高,可能需要大量计算资源。对于实时应用,优化计算效率是一个重要挑战。

6. 全局一致性缺乏

许多光流算法是基于局部信息进行计算的,可能会导致全局运动场不一致。需要结合全局优化方法(如全局光流)来改善一致性。

7. 静态场景的局限性

在静态场景或没有显著运动的情况下,光流法无法提供有用的信息。这在一些应用中可能是一个限制。

8. 深度变化的影响

光流法通常不考虑物体的深度变化,而仅基于二维图像进行运动估计。当物体在三维空间中运动时,深度变化可能会影响光流估计的准确性。

9. 处理复杂场景的局限性

在复杂场景中,存在多种运动模式和不同速度的物体,光流法可能难以准确区分和估计每个物体的运动,尤其是在存在复杂的背景和前景互动时。

二、实现步骤

  • 设置图像中检测区域内的检测点集。
  • 加载第一帧图像,可根据需求对图像做预处理,比如高斯滤波。
  • 计算光流。
  • 分析处理计算后的点集和原点集的运动情况。
  • 将计算后的点集替换原点集,并准备下一个计算循环。

三、代码实现(C++)

参数
  • prevImg: 前一帧图像,即光流的参考帧。
  • nextImg:当前帧图像,即光流的目标帧。
  • prevPts:前一帧中的特征点数组,是一个 std::vector<Point2f> 类型的输入参数,表示前一帧中需要跟踪的特征点。
  • nextPts:输出的当前帧中估计的特征点位置,也是一个 std::vector<Point2f> 类型的输入输出参数,函数会将跟踪到的特征点位置写入其中。
  • status:输出的跟踪状态数组,是一个 std::vector<uchar> 类型的输出参数,用于指示每个输入点是否成功跟踪到。如果某个点成功被光流法跟踪到,则对应的 status 元素为 1;如果某个点未能被光流法跟踪到,则对应的 status 元素为 0。
  • err:输出的误差数组,是一个 std::vector<float> 类型的输出参数,用于存储每个特征点的估计误差。
  • winSize:搜索窗口大小,用于光流法中的局部运动估计。
  • maxLevel:图像金字塔的层数,用于多尺度光流计算。
  • criteria:终止迭代的条件,通常包括迭代次数和精度要求。
  • flags:可选标志,如是否使用初始估计。
  • minEigThreshold:最小特征值阈值,低于该值的特征点会被过滤掉。
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main() 
{
    // 打开视频捕捉设备(摄像头)
    VideoCapture cap(0);
    if (!cap.isOpened()) 
    {
        cout << "Error opening video stream" << endl;
        return -1;
    }

    Mat old_frame, old_gray;
    vector<Point2f> p0;

    // 定义矩形区域
    Rect roi(100, 100, 100, 100); 
    
    // 从矩形区域内每隔 10 个像素选取一个点
    for (int y = roi.y; y < roi.y + roi.height; y += 10) 
    {
        for (int x = roi.x; x < roi.x + roi.width; x += 10) 
        {
            p0.push_back(Point2f(x, y));
        }
    }

    while (true) 
    {
        Mat frame, frame_gray;
        cap >> frame;
        if (frame.empty())
            break;

        cvtColor(frame, frame_gray, COLOR_BGR2GRAY);

        if (old_frame.empty())
            old_frame = frame.clone();
        
        if (old_gray.empty())
            cvtColor(old_frame, old_gray, COLOR_BGR2GRAY);

        // 计算光流
        vector<Point2f> p1;
        vector<uchar> status;
        vector<float> err;
        TermCriteria criteria = TermCriteria((TermCriteria::COUNT) + TermCriteria::EPS, 10, 0.03);
        calcOpticalFlowPyrLK(old_gray, frame_gray, p0, p1, status, err, Size(15, 15), 2, criteria);

        vector<Point2f> good_new;
        for (size_t i = 0; i < p0.size(); i++) 
        {
            // 选择好的点
            if (status[i] == 1) 
            {
                good_new.push_back(p1[i]);
                // 绘制轨迹
                line(frame, p1[i], p0[i], Scalar(0, 255, 0), 2);
                circle(frame, p1[i], 5, Scalar(0, 0, 255), -1);
            }
        }

        imshow("Optical Flow", frame);        
        waitKey(10);

        // 更新前一帧和点
        old_gray = frame_gray.clone();
        p0 = good_new;
    }

    cap.release();
    destroyAllWindows();
    return 0;
}

  • 25
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值