在计算机视觉中,光流法(Optical Flow)是一种常用的技术,通过分析连续图像帧之间的像素运动,推测物体的运动情况。光流法可以用于目标跟踪、运动分析等任务,广泛应用于机器人导航、视频分析等领域。

本文将介绍如何使用光流法分析物体的运动速度。我们将这个过程分成两个阶段:计算位移像素根据像素计算速度。下面是详细的实现过程。

光流法原理

光流法的核心思想是通过比较连续帧中相同位置的像素点,计算它们的位移。这个过程通常依赖于一些特征点(如角点),在两帧之间跟踪这些点的位移。最终,我们可以推算出物体的速度。

步骤一:计算像素位移

首先,我们使用光流法来计算图像中每个特征点的位移。这里我们采用 Lucas-Kanade 光流算法,这是最常用的光流估计方法之一。

  1. 角点检测:在第一帧图像中检测角点,这些角点将在后续帧中被追踪。

  2. 光流计算:在后续帧中,通过光流算法追踪这些角点,计算每个点的位移。

代码实现如下:

import cv2
import numpy as np

class OpticalFlow:
    def __init__(self):
        # 设置 Lucas-Kanade 光流法的参数
        self.lk_params = dict(winSize=(15, 15),
                              maxLevel=2,
                              criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
        # 创建随机数生成器
        self.rng = np.random.default_rng()

    def do(self, frame, device):
        # 转为灰度图
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # 在第一帧中检测角点(角点是光流的起点)
        if not hasattr(self, 'prev_gray'):
            self.prev_gray = gray

            # 使用 goodFeaturesToTrack 来获取10个角点
            self.prev_points = cv2.goodFeaturesToTrack(gray,
                                                       maxCorners=10,    # 最大角点数设置为10
                                                       qualityLevel=0.05,  # 角点质量阈值
                                                       minDistance=7,    # 最小角点间距
                                                       blockSize=7)      # 用于计算角点的邻域大小

            # 为每个特征点分配一个唯一的颜色,确保饱和度最大
            self.colors = [tuple(int(c) for c in color) for color in self.rng.integers(200, 256, (len(self.prev_points), 3))]

            return frame

        # 计算光流(在当前帧中跟踪前一帧的角点)
        next_points, status, _ = cv2.calcOpticalFlowPyrLK(self.prev_gray, gray, self.prev_points, None, **self.lk_params)

        # 选出跟踪成功的点
        good_new = next_points[status == 1]
        good_old = self.prev_points[status == 1]

        # 计算位移并准备显示信息
        displacements = []

        # 绘制光流轨迹
        for i, (new, old) in enumerate(zip(good_new, good_old)):
            a, b = new.ravel()
            c, d = old.ravel()

            # 计算位移
            displacement = np.sqrt((a - c) ** 2 + (b - d) ** 2)
            displacements.append(f"Point {i+1}: {displacement:.2f} px")

            # 获取对应的颜色
            color = self.colors[i]

            # 绘制轨迹
            frame = cv2.line(frame, (int(a), int(b)), (int(c), int(d)), color, 2)
            frame = cv2.circle(frame, (int(a), int(b)), 5, color, -1)

        # 在右下角显示位移信息,确保每个信息的颜色与点的颜色一致
        y_offset = frame.shape[0] - 10
        for i, displacement in enumerate(displacements):
            color = self.colors[i]
            cv2.putText(frame, displacement, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2, cv2.LINE_AA)  # 字体增大,饱和度增加
            y_offset -= 40  # 每个信息之间间隔40个像素

        # 更新上一帧数据
        self.prev_gray = gray.copy()
        self.prev_points = good_new.reshape(-1, 1, 2)

        return frame
步骤二:根据像素和帧率计算速度

计算完每个特征点的位移后,我们接下来要根据实际情况(如帧率和像素与实际距离的换算)计算物体的运动速度。

  1. 位移换算:1厘米 = 10像素,这个换算关系用于将像素单位的位移转换为实际物理单位。

  2. 速度计算:通过已知的帧率(例如 25 帧/秒)和像素位移,可以计算物体的速度。

假设视频的帧率为 25 帧/秒,1厘米 = 10像素,我们可以使用以下公式来计算速度:

速度=位移10×帧率\text{速度} = \frac{\text{位移}}{10} \times \text{帧率}速度=10位移​×帧率

这里的 位移 为像素单位,帧率 为每秒的帧数,10 是像素与厘米之间的换算系数。

import cv2
import numpy as np

class OpticalFlow:
    def __init__(self):
        # 设置 Lucas-Kanade 光流法的参数
        self.lk_params = dict(winSize=(15, 15),
                              maxLevel=2,
                              criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
        # 创建随机数生成器
        self.rng = np.random.default_rng()

    def do(self, frame, device):
        # 转为灰度图
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # 在第一帧中检测角点(角点是光流的起点)
        if not hasattr(self, 'prev_gray'):
            self.prev_gray = gray
            # 使用 goodFeaturesToTrack 来获取10个角点
            self.prev_points = cv2.goodFeaturesToTrack(gray, maxCorners=10,  # 最大角点数设置为10
                                                       qualityLevel=0.05,  # 角点质量阈值
                                                       minDistance=7,  # 最小角点间距
                                                       blockSize=7)  # 用于计算角点的邻域大小

            # 为每个特征点分配一个唯一的颜色,确保饱和度最大
            self.colors = [tuple(int(c) for c in color) for color in self.rng.integers(200, 256, (len(self.prev_points), 3))]

            return frame  # 在第一帧中返回原始图像

        # 计算光流(在当前帧中跟踪前一帧的角点)
        next_points, status, _ = cv2.calcOpticalFlowPyrLK(self.prev_gray, gray, self.prev_points, None, **self.lk_params)

        # 选出跟踪成功的点
        good_new = next_points[status == 1]
        good_old = self.prev_points[status == 1]

        # 计算位移并准备显示信息
        displacements = []
        speeds = []

        # 计算位移、速度并绘制光流轨迹
        for i, (new, old) in enumerate(zip(good_new, good_old)):
            a, b = new.ravel()
            c, d = old.ravel()

            # 计算位移
            displacement = np.sqrt((a - c) ** 2 + (b - d) ** 2)
            displacements.append(f"Point {i+1}: {displacement:.2f} px")

            # 计算速度(1秒25帧,1厘米 = 10像素)
            frame_rate = 25  # 帧率:25帧/秒
            displacement_cm = displacement / 10  # 1厘米 = 10像素
            speed = displacement_cm * frame_rate  # 速度单位为cm/s

            speeds.append(f"Speed: {speed:.2f} cm/s")

            # 获取对应的颜色
            color = self.colors[i]

            # 绘制轨迹
            frame = cv2.line(frame, (int(a), int(b)), (int(c), int(d)), color, 2)
            frame = cv2.circle(frame, (int(a), int(b)), 5, color, -1)

        # 在右下角显示位移信息,确保每个信息的颜色与点的颜色一致
        y_offset = frame.shape[0] - 10  # 初始Y坐标在底部
        for i, (displacement, speed) in enumerate(zip(displacements, speeds)):
            color = self.colors[i]
            # 在表格中显示每个点的位移和速度
            cv2.putText(frame, f"{displacement} {speed}", (10, y_offset),
                        cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2, cv2.LINE_AA)
            y_offset -= 40  # 每行之间间隔40个像素

        # 更新上一帧数据
        self.prev_gray = gray.copy()
        self.prev_points = good_new.reshape(-1, 1, 2)

        return frame
完整流程
  1. 计算位移像素:通过光流算法,检测并追踪特征点,计算出每个特征点在两帧之间的位移,单位为像素。

  2. 根据像素计算速度:根据给定的帧率和像素与实际距离的换算关系,计算每个特征点的运动速度,单位为厘米/秒。

总结

光流法不仅可以帮助我们跟踪图像中的物体,还能够准确估算物体的运动速度。通过将位移计算与速度换算分开,我们可以更清晰地理解并应用光流法。在实际应用中,光流法广泛应用于运动检测、目标跟踪、水文气象检测等场景中。

 对 PiscTrace or PiscCode感兴趣?更多精彩内容请移步官网看看~🔗 PiscTrace

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐