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