OpenCV 光流

Ammar Ali 2024年2月15日
  1. 在 OpenCV 中使用光流检测视频中的运动物体
  2. 在 OpenCV 中跟踪视频中对象的运动
OpenCV 光流

本教程将讨论在 OpenCV 中使用光流检测视频中的移动对象。

在 OpenCV 中使用光流检测视频中的运动物体

光流可以检测 OpenCV 中视频中存在的运动物体。我们还可以使用光流检测物体的运动路径。

在光流中,物体的位置在两帧之间进行比较,如果物体的位置在帧之间发生变化,我们可以将其标记为移动物体,并使用 OpenCV 将其突出显示。例如,我们有一个视频,我们想要突出显示一个移动的对象。

首先,我们需要从视频中获取两帧,前一帧,下一帧。我们将使用 OpenCV 的 calcOpticalFlowFarneback() 函数来查找视频中移动的对象。

calcOpticalFlowFarneback() 函数使用两个帧并比较这些帧中对象的位置,如果对象的位置发生变化,该函数将该对象保存在二维数组中。

我们可以使用 cartToPolar()calcOpticalFlowFarneback() 返回的 2D 数组来查找给定视频中存在的对象的大小和角度。

之后,我们可以根据图形上移动物体的大小和角度绘制不同的颜色,以可视化物体。例如,让我们使用一条狗的视频并突出显示它的动作。

请参阅下面的代码。

import numpy as np
import cv2

cap_video = cv2.VideoCapture("bon_fire_dog_2.mp4")
ret, frame1 = cap_video.read()
prvs_frame = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
hsv_drawing = np.zeros_like(frame1)
hsv_drawing[..., 1] = 255
while 1:
    ret, frame2 = cap_video.read()
    if not ret:
        print("No frames available!")
        break
    next_frame = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
    flow_object = cv2.calcOpticalFlowFarneback(
        prvs_frame, next_frame, None, 0.5, 3, 15, 3, 5, 1.2, 0
    )
    magnitude, angle = cv2.cartToPolar(flow_object[..., 0], flow_object[..., 1])
    hsv_drawing[..., 0] = angle * 180 / np.pi / 2
    hsv_drawing[..., 2] = cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX)
    bgr_drawing = cv2.cvtColor(hsv_drawing, cv2.COLOR_HSV2BGR)
    cv2.imshow("frame2", bgr_drawing)
    cv2.waitKey(10)
    prvs_frame = next_frame
cv2.destroyAllWindows()

输出:

运动物体检测

正如你在上面的输出中看到的那样,狗被标记为不同的颜色,因为狗是视频中唯一移动的对象。在上面的代码中,OpenCV 的 cvtColor() 函数用于将视频的彩色帧转换为灰度。

zeros_like() 函数创建一个黑色绘图以显示移动对象。calcOpticalFlowFarneback() 函数查找移动对象。

calcOpticalFlowFarneback() 函数的第一个参数是第一个 8 位单通道图像或前一帧。第二个参数是第二个图像或下一帧。

第三个参数是将保存流对象的输出数组。第四个参数是用于为图像构建金字塔的图像比例。

第五个参数定义了金字塔的层数,如果我们不想使用额外的层,我们可以将其值设置为 1。第六个参数是平均窗口大小,它的值定义了算法的速度。

较小的窗口大小意味着速度会很慢,但输出会很锐利。第七个参数定义了算法在每一层的迭代次数。

第八个参数用于设置像素邻域的大小,用于查找每个像素的多项式。第 9 个参数用于设置多项式的标准差,第 10 个参数用于设置标志。

normalize() 函数用于使用 MINMAX 归一化来归一化移动对象的大小。

在 OpenCV 中跟踪视频中对象的运动

我们还可以跟踪视频中正在移动的特征点。

例如,要跟踪狗的移动位置,我们需要获取一些特征点,然后进行跟踪。我们可以使用 OpenCV 的 goodFeaturesToTrack() 函数来获取特征点。

之后,我们需要在 calcOpticalFlowPyrLK() 函数中将这些特征点与前一帧和下一帧一起传递,以跟踪给定点以及视频帧。该函数将返回下一个点、状态和错误。

我们可以使用 OpenCV 的 line()circle() 函数使用输出来绘制直线和圆。之后,我们可以使用 OpenCV 的 add() 函数将绘图添加到原始视频中。

请参阅下面的代码。

import numpy as np
import cv2

cap_video = cv2.VideoCapture("bon_fire_dog_2.mp4")

feature_parameters = dict(maxCorners=100, qualityLevel=0.3, minDistance=7, blockSize=7)

lk_parameters = dict(
    winSize=(15, 15),
    maxLevel=2,
    criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03),
)

random_color = np.random.randint(0, 255, (100, 3))

ret, previous_frame = cap_video.read()
previous_gray = cv2.cvtColor(previous_frame, cv2.COLOR_BGR2GRAY)
p0_point = cv2.goodFeaturesToTrack(previous_gray, mask=None, **feature_parameters)

mask_drawing = np.zeros_like(previous_frame)
while 1:
    ret, frame = cap_video.read()
    if not ret:
        print("No frames available!")
        break
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    p1, st_d, error = cv2.calcOpticalFlowPyrLK(
        previous_gray, frame_gray, p0_point, None, **lk_parameters
    )

    if p1 is not None:
        good_new_point = p1[st_d == 1]
        good_old_point = p0_point[st_d == 1]

    for i, (new_point, old_point) in enumerate(zip(good_new_point, good_old_point)):
        a, b = new_point.ravel()
        c, d = old_point.ravel()
        mask_drawing = cv2.line(
            mask_drawing,
            (int(a), int(b)),
            (int(c), int(d)),
            random_color[i].tolist(),
            2,
        )
        frame = cv2.circle(frame, (int(a), int(b)), 5, random_color[i].tolist(), -1)
    img = cv2.add(frame, mask_drawing)
    cv2.imshow("frame", img)
    cv2.waitKey(30)
    previous_gray = frame_gray.copy()
    p0_point = good_new_point.reshape(-1, 1, 2)
cv2.destroyAllWindows()

输出:

跟踪点

如你所见,视频中正在跟踪特征点。当我们想要跟踪视频中对象的运动时,此算法很有用。

在上面的代码中,goodFeaturesToTrack() 函数的第一个参数是我们想要跟踪特征点的帧。第二个参数是包含角点的输出。

第三个参数 maxCorners 设置最大角。第四个参数 minDistance 用于设置质量等级,第五个参数用于设置点之间的最小距离。

第六个参数 mask 用于设置使用蒙版从中提取点的帧部分,如果我们想从整个图像中提取点,我们可以将蒙版设置为 none .

第七个参数 blockSize 用于设置块大小,第八个参数用于设置梯度大小。

在上面的代码中,我们使用 dict() 函数定义了一些属性,然后在代码中传递它们,但我们也可以在函数内部定义属性。

calcOpticalFlowPyrLK() 函数的第一个参数是第一个输入图像或前一帧,第二个参数是第二个图像(或下一帧)。

第三个参数是上一个输入点,第四个是下一个输出点。第五个参数 status 是状态,如果找到该点的流并且它是输出参数,则该点的状态将为 1。

第六个参数 err 是错误向量和输出参数。第七个参数 winSize 用于设置每个金字塔的窗口大小,第八个参数 maxLevel 用于设置金字塔的数量。

最后一个参数 criteria 用于设置算法的标准。

作者: Ammar Ali
Ammar Ali avatar Ammar Ali avatar

Hello! I am Ammar Ali, a programmer here to learn from experience, people, and docs, and create interesting and useful programming content. I mostly create content about Python, Matlab, and Microcontrollers like Arduino and PIC.

LinkedIn Facebook

相关文章 - OpenCV Video