Python中OpenCV的光流技术解析
文章目录
1、光流
光流(Optical Flow)是计算机视觉和图像处理中的一个重要概念,它描述了连续帧图像中像素点随时间的运动轨迹和速度的二维矢量场。以下是关于光流的详细解释:
一、光流的概念
稀疏光流:仅计算图像中选定特征点(如角点、边缘点等)的光流。
密集光流:计算图像中每个像素点的光流,从而得到整个图像的光流场。
二、光流的基本假设
光流算法通常基于以下几个基本假设:
三、光流的应用场景
光流法因其实时性和计算简单的特点,在计算机视觉和机器人视觉中有许多应用场景:
四、光流的计算方法
光流的计算方法多种多样,主要包括以下几种:
五、总结
光流作为计算机视觉和图像处理中的一个重要工具,具有广泛的应用前景。通过分析连续帧图像中像素的运动信息,光流法可以实现对物体运动和环境变化的感知和分析,为智能控制和决策提供支持。随着计算机视觉技术的不断发展,光流法的计算精度和效率将不断提高,其应用场景也将更加广泛。
2、Opencv 中光流的实现
OpenCV提供了一些算法实现来解决稀疏光流任务
1)Pyramid Lucas-Kanade
2)Sparse RLOF
仅使用稀疏特征集意味着我们将不会有不包含在其中的像素的运动信息。使用密集光流算法可以消除这一限制,该算法假定为图像中的每个像素计算一个运动向量。
OpenCV中已经实现了一些密集光流算法:
1)Dense Pyramid Lucas-Kanade
2)Farneback
3)PCAFlow
4)SimpleFlow
5)RLOF
6)DeepFlow
7)DualTVL1
3、稀疏光流
Lucas-Kanade 方法
它假设在一个小的窗口内,所有的像素点都有相似的运动
# lucas_kanade.py
import cv2
import numpy as np
def lucas_kanade_method(video_path):
cap = cv2.VideoCapture(video_path)
# ShiTomasi角点检测的参数
feature_params = dict(maxCorners=100, qualityLevel=0.3, minDistance=7, blockSize=7)
# lucas kanade光流算法的参数
lk_params = dict(
winSize=(15, 15),
maxLevel=2,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03),
)
# 创建一些随机的颜色
color = np.random.randint(0, 255, (100, 3))
# 取第一帧并在其中找到角点
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# 返回检测到的角点坐标
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# 创建用于绘图的掩模图像
mask = np.zeros_like(old_frame)
index = 0
while True:
index += 1
ret, frame = cap.read()
if not ret:
break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 计算光流
# calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts[, status[, err[, \\
# winSize[, maxLevel[, criteria[, flags[, minEigThreshold]]]]]]]) -> nextPts, status, err
p1, st, err = cv2.calcOpticalFlowPyrLK(
old_gray, frame_gray, p0, None, **lk_params
)
# 返回成功跟踪的特征点的位置
# 哪些点成功跟踪(True)或失败(False)
# 每个点的错误度量(通常是跟踪的质量或置信度)
# 选择比较好的点
good_new = p1[st == 1]
good_old = p0[st == 1]
# 画出轨迹
for i, (new, old) in enumerate(zip(good_new, good_old)):
a, b = new.ravel() # 多维数组展开
c, d = old.ravel() # 多维数组展开
mask = cv2.line(mask, (int(a), int(b)), (int(c), int(d)), color[i].tolist(), 2)
frame = cv2.circle(frame, (int(a), int(b)), 5, color[i].tolist(), -1)
img = cv2.add(frame, mask)
cv2.imshow("frame", img)
cv2.imwrite(f"duck_{str(index)}.jpg", img)
k = cv2.waitKey(25) & 0xFF
if k == 27:
break
if k == ord("c"):
mask = np.zeros_like(old_frame)
# 现在更新之前的帧和之前的点
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
if __name__ == "__main__":
video_path = "duck.mp4"
lucas_kanade_method(video_path)
# python lucas_kanade.py
原理是检测前帧的角点,然后计算光流,绘制
结果展示,分割成多个片段,前后片段是连续的
可以观测到还是有些异常值的(比较直的线段)
4、密集光流
# dense_optical_flow.py
import cv2
import numpy as np
import argparse
def dense_optical_flow(method, video_path, params=[], to_gray=False):
# 读取视频
cap = cv2.VideoCapture(video_path)
# 读取第一帧
ret, old_frame = cap.read()
# 创建HSV并使Value为常量
hsv = np.zeros_like(old_frame)
hsv[..., 1] = 255
# 精确方法的预处理
if to_gray:
old_frame = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
index = 0
while True:
index += 1
# 读取下一帧
ret, new_frame = cap.read()
frame_copy = new_frame
if not ret:
break
# 精确方法的预处理
if to_gray:
new_frame = cv2.cvtColor(new_frame, cv2.COLOR_BGR2GRAY)
# 计算光流
flow = method(old_frame, new_frame, None, *params)
# 编码:将算法的输出转换为极坐标
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)
# 转换HSV图像为BGR
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
cv2.imshow("frame", frame_copy)
cv2.imshow("optical flow", bgr)
cv2.imwrite(f"duck_{str(index)}.jpg", bgr)
k = cv2.waitKey(25) & 0xFF
if k == 27:
break
old_frame = new_frame
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--algorithm",
choices=["farneback", "lucaskanade_dense", "rlof"],
required=True,
help="Optical flow algorithm to use",
)
parser.add_argument(
"--video_path", default="duck.mp4", help="Path to the video",
)
args = parser.parse_args()
video_path = args.video_path
if args.algorithm == "lucaskanade_dense":
method = cv2.optflow.calcOpticalFlowSparseToDense
dense_optical_flow(method, video_path, to_gray=True)
elif args.algorithm == "farneback":
# OpenCV Farneback算法需要一个单通道的输入图像,因此我们将BRG图像转换为灰度。
method = cv2.calcOpticalFlowFarneback
params = [0.5, 3, 15, 3, 5, 1.2, 0] # Farneback的算法参数
dense_optical_flow(method, video_path, params, to_gray=True)
elif args.algorithm == "rlof":
# 与Farneback算法相比,RLOF算法需要3通道图像,所以这里没有预处理。
method = cv2.optflow.calcOpticalFlowDenseRLOF
dense_optical_flow(method, video_path)
if __name__ == "__main__":
main()
# python dense_optical_flow.py
4.1、farneback
输出结果
效果还行,显示出来的部分仅为手拿着物体在移动
4.2、lucaskanade_dense
输出结果
这个感觉更稠密
4.3、rlof
输出结果
这个光流不太稳定的样子,噪点比较大
5、涉及到的库
5.1、cv2.goodFeaturesToTrack
cv2.goodFeaturesToTrack
用于检测图像中的角点,这些角点通常用于后续的图像跟踪或特征匹配任务
函数原型
corners = cv2.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance, [, corners[, mask[, blockSize[, useHarrisDetector[, k]]]]])
参数说明
返回值
使用示例
import cv2
import numpy as np
# 读取图像
image = cv2.imread('path_to_image.jpg', 0) # 0 表示以灰度模式读取图像
# 设置角点检测参数
maxCorners = 100
qualityLevel = 0.3
minDistance = 7
# 检测角点
corners = cv2.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance)
# 如果检测到了角点,则绘制并显示它们
if corners is not None:
for i in corners:
x, y = i.ravel()
cv2.circle(image, (x, y), 3, 255, -1)
cv2.imshow('Corners', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
注意事项
5.2、cv2.calcOpticalFlowPyrLK
cv2.calcOpticalFlowPyrLK 是 OpenCV 库中用于计算两幅图像之间稀疏光流(Sparse Optical Flow)的一个函数,特别是通过 Lucas-Kanade 方法结合金字塔(PyrLK)来跟踪图像中的特征点。
函数原型
p1, st, err = cv2.calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts[, status[, err[, winSize[, maxLevel[, criteria[, flags[, minEigThreshold]]]]]]])
参数说明
返回值
工作原理
使用示例
import cv2
import numpy as np
# 读取前一帧和当前帧图像
prevImg = cv2.imread('frame1.png', cv2.IMREAD_GRAYSCALE)
nextImg = cv2.imread('frame2.png', cv2.IMREAD_GRAYSCALE)
# 使用 Shi-Tomasi 角点检测器找到前一帧图像中的关键点
prevPts = cv2.goodFeaturesToTrack(prevImg, maxCorners=100, qualityLevel=0.3, minDistance=7, blockSize=7)
# 设置 LK 光流算法的参数
lk_params = dict(winSize=(15, 15), maxLevel=2,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
# 计算光流
nextPts, st, err = cv2.calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, None, **lk_params)
# 筛选出被成功跟踪的点
good_new = nextPts[st == 1]
good_old = prevPts[st == 1]
# 绘制跟踪结果(省略具体绘制代码)
注意事项
5.3、cv2.optflow.calcOpticalFlowSparseToDense
在OpenCV中,cv2.optflow.calcOpticalFlowSparseToDense 是一个用于计算从稀疏特征点到密集光流场的函数。这个函数结合了稀疏特征匹配和光流算法的优点,通过先找到一组稀疏的关键点(如使用Shi-Tomasi角点检测器),然后利用这些稀疏点来估计整个图像的光流场。
函数原型
flow = cv2.optflow.calcOpticalFlowSparseToDense(prevImg, nextImg, prevPts, nextPts, density=1, sigma_dist=0.05, sigma_color=0.1, patchSize=21, maxLevel=2)
参数说明
返回值
示例代码
import cv2
import numpy as np
# 假设 prevImg, nextImg, prevPts, nextPts 已经准备好
# ...
# 计算密集光流场
flow = cv2.optflow.calcOpticalFlowSparseToDense(prevImg, nextImg, prevPts, nextPts, density=1.0, sigma_dist=0.05, sigma_color=0.1, patchSize=21, maxLevel=2)
# 可视化光流场
hsv = np.zeros_like(nextImg)
hsv[...,1] = 255
hsv[...,0] = 0.5 * flow[...,0] + 0.5
hsv[...,2] = 0.5 * flow[...,1] + 0.5
hsv = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
img = cv2.add(nextImg, hsv)
# 显示结果
cv2.imshow('Optical Flow', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
5.4、cv2.calcOpticalFlowFarneback
cv2.calcOpticalFlowFarneback 是 OpenCV 库中用于计算两个连续帧之间稠密光流的函数。该函数基于 Gunnar Farneback 的算法,该算法在计算速度和准确性之间取得了良好的平衡。
函数原型
cv2.calcOpticalFlowFarneback(prevImg, nextImg, pyr_scale, levels, winsize, iterations, poly_n, poly_sigma, flags[, flow])
参数说明
返回值
使用示例
import cv2
import numpy as np
# 读取前后两帧图像
prev_frame = cv2.imread('frame1.jpg')
next_frame = cv2.imread('frame2.jpg')
# 将图像转换为灰度图
gray_prev = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
gray_next = cv2.cvtColor(next_frame, cv2.COLOR_BGR2GRAY)
# 计算光流
flow = cv2.calcOpticalFlowFarneback(gray_prev, gray_next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
# 可视化光流(这里省略了可视化代码,通常使用箭头或其他方式表示光流)
注意事项
5.5、cv2.optflow.calcOpticalFlowDenseRLOF
cv2.optflow.calcOpticalFlowDenseRLOF 是 OpenCV 中用于计算两帧之间光流的一个函数,它属于密集光流算法的一种。RLOF 代表 Robust Local Optical Flow,即鲁棒的局部光流算法。这种算法试图通过考虑图像中的局部特征和区域来更准确地估计光流,即使在图像中存在噪声、遮挡或运动不连续的情况下也能表现出较好的性能。
函数原型
cv2.optflow.calcOpticalFlowDenseRLOF(prevImg, nextImg, win_size, max_iter, eps, criteria_type[, dst[, flow[, layers[, scale_layers]]]])
参数解释
返回值
使用示例
import cv2
import numpy as np
# 加载视频或图像序列
cap = cv2.VideoCapture('video.mp4')
ret, prev = cap.read()
if not ret:
exit()
prev_gray = cv2.cvtColor(prev, cv2.COLOR_BGR2GRAY)
while cap.isOpened():
ret, next = cap.read()
if not ret:
break
next_gray = cv2.cvtColor(next, cv2.COLOR_BGR2GRAY)
# 计算光流
flow = cv2.optflow.calcOpticalFlowDenseRLOF(prev_gray, next_gray, win_size=15, max_iter=3, eps=0.01, criteria_type=cv2.TERM_CRITERIA_EPS+cv2.TERM_CRITERIA_COUNT, dst=None)
# 可视化光流
hsv = np.zeros_like(next)
hsv[..., 1] = 255
hsv[..., 0] = 0.5 * flow[..., 0] + 0.5
hsv[..., 2] = 0.5 * flow[..., 1] + 0.5
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
# 显示结果
cv2.imshow('Optical Flow', bgr)
if cv2.waitKey(30) & 0xFF == ord('q'):
break
# 更新前一帧
prev_gray = next_gray.copy()
cap.release()
cv2.destroyAllWindows()
参考
OpenCV进阶(2)OpenCV中的光流
Optical Flow in OpenCV (C++/Python)
作者:bryant_meng