简体   繁体   English

使用 OpenCV 的光流 - 水平和垂直组件

[英]Optical Flow using OpenCV - Horizontal and Vertical Components

I have the following code that finds the Optical Flow of 2 images (or 2 frames of a video) and it's colour coded.我有以下代码可以找到 2 张图像(或视频的 2 帧)的光流,并用颜色编码。 What I want is the horizontal and vertical components of the optical flow separately (as in separate images)我想要的是光流的水平和垂直分量分别(如在单独的图像中)

Here is the code I have so far:这是我到目前为止的代码:

import cv2
import numpy as np
frame1 = cv2.imread('my1.bmp')
frame2 = cv2.imread('my2.bmp')
prvs = cv2.cvtColor(frame1,cv2.COLOR_BGR2GRAY)
next = cv2.cvtColor(frame2,cv2.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[...,1] = 255

while(1):
    next = cv2.cvtColor(frame2,cv2.COLOR_BGR2GRAY)
    flow = cv2.calcOpticalFlowFarneback(prvs, next, 0.5, 3, 15, 3, 5, 1.2, 0)
    mag, ang = cv2.cartToPolar(flow[...,0], flow[...,1])
    hsv[...,0] = ang*180/np.pi/2
    hsv[...,2] = cv2.normalize(mag,None,0,255,cv2.NORM_MINMAX)
    rgb = cv2.cvtColor(hsv,cv2.COLOR_HSV2BGR)

    cv2.imshow('frame2',rgb)
    k = cv2.waitKey(30) & 0xff
    if k == 27:
        break
    elif k == ord('s'):
        cv2.imwrite('opticalmyhsv.pgm',rgb)

cap.release()
cv2.destroyAllWindows()

This is what the optical flow looks like given my two images:鉴于我的两个图像,这就是光流的样子:

If you want to visualize the horizontal and vertical component separately, you can visualize both separately as grayscale images.如果要分别可视化水平和垂直分量,可以将两者分别可视化为灰度图像。 I'll make it such that a colour of gray denotes no motion, black denotes the maximum amount of motion in the frame going to the left (negative) while white denotes the maximum amount of motion in the frame going towards the right (positive).我会用灰色表示没有运动,黑色表示向左(负)的帧中的最大运动量,而白色表示向右(正)的帧中的最大运动量.

The output of calcOpticalFlowFarneback is a 3D numpy array where the first slice denotes the amount of horizontal ( x ) displacement while the second slice denotes the amount of vertical ( y ) displacement. calcOpticalFlowFarneback的输出是一个 3D numpy数组,其中第一个切片表示水平 ( x ) 位移量,而第二个切片表示垂直 ( y ) 位移量。

As such, all you need to do is define two separate 2D numpy arrays that will store these values so we can display them to the user.因此,您需要做的就是定义两个单独的 2D numpy数组来存储这些值,以便我们可以将它们显示给用户。 However, you're going to need to normalize the flow for display such that no motion is a rough gray, motion to the extreme left is black, or intensity 0, and motion to the extreme right is white, or intensity 255.但是,您需要对显示流进行标准化,以便没有运动是粗糙的灰色,最左边的运动是黑色,或强度为 0,而最右边的运动是白色,或强度为 255。

Therefore, all you would need to do is modify your code to show two OpenCV windows for the horizontal and vertical motion like so:因此,您需要做的就是修改代码以显示两个用于水平和垂直运动的 OpenCV 窗口,如下所示:

import cv2
import numpy as np
frame1 = cv2.imread('my1.bmp')
frame2 = cv2.imread('my2.bmp')
prvs = cv2.cvtColor(frame1,cv2.COLOR_BGR2GRAY)
next = cv2.cvtColor(frame2,cv2.COLOR_BGR2GRAY)

flow = cv2.calcOpticalFlowFarneback(prvs, next, 0.5, 3, 15, 3, 5, 1.2, 0)

# Change here
horz = cv2.normalize(flow[...,0], None, 0, 255, cv2.NORM_MINMAX)     
vert = cv2.normalize(flow[...,1], None, 0, 255, cv2.NORM_MINMAX)
horz = horz.astype('uint8')
vert = vert.astype('uint8')

# Change here too
cv2.imshow('Horizontal Component', horz)
cv2.imshow('Vertical Component', vert)

k = cv2.waitKey(0) & 0xff
if k == ord('s'): # Change here
    cv2.imwrite('opticalflow_horz.pgm', horz)
    cv2.imwrite('opticalflow_vert.pgm', vert)

cv2.destroyAllWindows()

I've modified the code so that there is no while loop as you're only finding the optical flow between two predetermined frames.我已经修改了代码,以便没有while循环,因为您只找到两个预定帧之间的光流。 You're not grabbing frames off of a live source, like a camera, so we can just show both of the images not in a while loop.您不会从实时源(例如相机)中抓取帧,因此我们可以只显示两个图像, while不是在while循环中显示。 I've made the wait time for waitKey set to 0 so that you wait indefinitely until you push a key.我已经将waitKey的等待时间设置为 0,这样您就可以无限期地等待,直到您按下某个键。 This pretty much simulates your while loop behaviour from before, but it doesn't burden your CPU needlessly with wasted cycles.这几乎模拟了您之前的while循环行为,但它不会因浪费的周期而不必要地给您的 CPU 带来负担。 I've also removed some unnecessary variables, like the hsv variable as we aren't displaying both horizontal and vertical components colour coded.我还删除了一些不必要的变量,比如hsv变量,因为我们没有显示颜色编码的水平和垂直分量。 We also just compute the optical flow once.我们也只计算一次光流。

In any case, with the above code we compute the optical flow, extract the horizontal and vertical components separately, normalize the components between the range of [0,255] , cast to uint8 so that we can display the results then show the results.无论如何,通过上面的代码,我们计算光流,分别提取水平和垂直分量,将[0,255]范围内的分量归一化,转换为uint8以便我们可以显示结果,然后显示结果。 I've also modified your code so that if you wanted to save the components, it'll save the horizontal and vertical components as two separate images.我还修改了您的代码,以便如果您想保存组件,它将水平和垂直组件保存为两个单独的图像。


Edit编辑

In your comments, you want to display a sequence of images using the same logic we have created above.在您的评论中,您希望使用我们在上面创建的相同逻辑来显示一系列图像。 You have a list of file names that you want to cycle through.您有一个要循环浏览的文件名列表。 That isn't very difficult to do.这不是很难做到。 Simply take your strings and put them into a list and compute the optical flow between pairs of images by using the file names stored in this list.只需将您的字符串放入一个列表中,然后使用存储在此列表中的文件名来计算图像对之间的光流。 I'll modify the code such that when we reach the last element of the list, we will wait for the user to push something.我将修改代码,以便当我们到达列表的最后一个元素时,我们将等待用户推送某些内容。 Until then, we will cycle through each pair of images until the end.在此之前,我们将循环遍历每对图像直到结束。 In other words:换句话说:

import cv2
import numpy as np

# Create list of names here from my1.bmp up to my20.bmp
list_names = ['my' + str(i+1) + '.bmp' for i in range(20)]

# Read in the first frame
frame1 = cv2.imread(list_names[0])
prvs = cv2.cvtColor(frame1,cv2.COLOR_BGR2GRAY)

# Set counter to read the second frame at the start
counter = 1

# Until we reach the end of the list...
while counter < len(list_names):
    # Read the next frame in
    frame2 = cv2.imread(list_names[counter])
    next = cv2.cvtColor(frame2,cv2.COLOR_BGR2GRAY)

    # Calculate optical flow between the two frames
    flow = cv2.calcOpticalFlowFarneback(prvs, next, 0.5, 3, 15, 3, 5, 1.2, 0)

    # Normalize horizontal and vertical components
    horz = cv2.normalize(flow[...,0], None, 0, 255, cv2.NORM_MINMAX)     
    vert = cv2.normalize(flow[...,1], None, 0, 255, cv2.NORM_MINMAX)
    horz = horz.astype('uint8')
    vert = vert.astype('uint8')

    # Show the components as images
    cv2.imshow('Horizontal Component', horz)
    cv2.imshow('Vertical Component', vert)

    # Change - Make next frame previous frame
    prvs = next.copy()

    # If we get to the end of the list, simply wait indefinitely
    # for the user to push something
    if counter == len(list_names)-1
        k = cv2.waitKey(0) & 0xff
    else: # Else, wait for 1 second for a key
        k = cv2.waitKey(1000) & 0xff

    if k == 27:
        break
    elif k == ord('s'): # Change
        cv2.imwrite('opticalflow_horz' + str(counter) + '-' + str(counter+1) + '.pgm', horz)
        cv2.imwrite('opticalflow_vert' + str(counter) + '-' + str(counter+1) + '.pgm', vert)

    # Increment counter to go to next frame
    counter += 1

cv2.destroyAllWindows()

The above code will cycle through pairs of frames and wait for 1 second between each pair to give you the opportunity to either break out of the showing, or saving the horizontal and vertical components to file.上面的代码将循环显示成对的帧,并在每对帧之间等待 1 秒,让您有机会退出显示,或将水平和垂直分量保存到文件中。 Bear in mind that I have made it such that whatever frames you save, they are indexed with two numbers that tell you which pairs of frames they are showing.请记住,我已经做到了,无论您保存什么帧,它们都会用两个数字进行索引,告诉您它们显示的是哪对帧。 Before the next iteration happens, the next frame will be come the previous frame and so next gets replaced by a copy of prvs .在下一次迭代发生之前,下一帧将是前一帧,因此nextprvs的副本prvs At the beginning of the loop, the next frame gets read in appropriately.在循环开始时,下一帧被适当地读入。


Hope this helps.希望这可以帮助。 Good luck!祝你好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM