使用OpenCV的光流-水平和垂直分量 - python

我有以下代码可以找到2张图像(或2帧视频)的光流,并对其进行颜色编码。我想要的是分别将光流的水平和垂直分量(如在单独的图像中)

这是我到目前为止的代码:

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()

给定我的两个图像,这就是光流的样子:

python大神给出的解决方案

如果要分别可视化水平和垂直分量,则可以将它们分别可视化为灰度图像。我将用灰色表示没有运动,黑色表示框架中向左移动的最大运动量(负),而白色表示框架中向右移动的最大运动量(正) 。

calcOpticalFlowFarneback的输出是3D numpy数组,其中第一个切片表示水平(x)位移量,而第二个切片表示垂直(y)位移量。

这样,您所需要做的就是定义两个单独的2D numpy数组,这些数组将存储这些值,以便我们可以将它们显示给用户。但是,您将需要标准化显示流程,以使没有运动是粗糙的灰色,向左运动为黑色或强度0,向右运动为白色或强度255。

因此,您需要做的就是修改代码,以显示两个用于水平和垂直运动的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()

我已经修改了代码,以便没有while循环,因为您只能找到两个预定帧之间的光流。您并不是从像摄像机这样的实时信号源中抓取帧,因此我们可以不以while循环显示两个图像。我已将waitKey的等待时间设置为0,以便您无限期等待,直到您按下某个键。这几乎可以模拟以前的while循环行为,但是不会浪费CPU不必要的负担。我还删除了一些不必要的变量,例如hsv变量,因为我们没有同时显示水平和垂直组件的颜色。我们也只计算一次光流。

无论如何,使用上述代码,我们可以计算光流,分别提取水平分量和垂直分量,将[0,255]范围之间的分量归一化,转换为uint8,以便我们可以显示结果,然后显示结果。我还修改了您的代码,以便如果您想保存组件,它将水平和垂直组件另存为两个单独的图像。

编辑

在您的评论中,您想使用上面创建的相同逻辑来显示图像序列。您有要循环浏览的文件名列表。那不是很难做到的。只需将您的字符串放入列表中,然后使用存储在此列表中的文件名计算成对图像之间的光通量。我将修改代码,以便在到达列表的最后一个元素时,我们将等待用户推送内容。在此之前,我们将循环浏览每对图像直到结束。换一种说法:

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()

上面的代码将在成对的帧之间循环,并在每对之间等待1秒钟,使您有机会突围显示,或将水平和垂直分量保存到文件中。请记住,我这样做的目的是,无论您保存什么帧,都将用两个数字编制索引,以告诉您它们正在显示哪些帧。在下一次迭代发生之前,下一帧将是前一帧,因此next被替换为prvs。在循环的开始,将正确读取下一帧。

希望这可以帮助。祝好运!