目录
图像特征
harris
Harris 角点检测是一种计算图像中关键点的方法,它可以用来检测图像中的角点或特征点。Harris 角点检测算法主要基于图像中灰度值的变化来确定角点的位置。
上图理论来自课件,有兴趣可以深入了解,我们要学会的就是如何调包使用。
cv2.cornerHarris()
img: 数据类型为 float32 的入图像
blockSize: 角点检测中指定区域的大小
ksize: Sobel求导中使用的窗口大小
k: 取值参数为 [0,04,0.06]
import cv2
import numpy as np
# 读取图像
img = cv2.imread('test_1.jpg')
# 转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 计算 Harris 角点
dst = cv2.cornerHarris(gray, 2, 3, 0.04)
# 标记角点,将角点像素值设为红色
img[dst > 0.01 * dst.max()] = [0, 0, 255]
# 展示结果
cv2.imshow('dst', img)
# 等待用户关闭图像窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
窗户、房顶、树的红色点就是检测出来的角点。
SIFT
SIFT(尺度不变特征转换)是一种计算机视觉算法,用于在数字图像中检测和描述局部特征。它是由David Lowe于1999年提出并在2004年发表的论文中详细介绍的。
SIFT的主要目标是生成一组能在不同尺度和旋转角度下保持稳定的图像特征点(关键点)及其描述子。这些特征点具有对尺度变化、旋转变化和视角变化具有不变性,对于在不同图像中进行特征匹配、目标识别、图像拼接等应用起到了关键作用。
SIFT算法的关键步骤包括:
- 尺度空间极值检测:通过在不同尺度上的高斯滤波器对图像进行平滑,然后在不同尺度上比较像素与其相邻像素的值,以检测出图像中的极值点。
- 关键点定位:根据检测到的尺度空间极值点,使用插值方法精确定位关键点的位置。
- 方向分配:为每个关键点分配主方向,以便后续的特征描述。
- 特征描述:为每个关键点周围的局部区域计算特征描述子,该描述子对局部图像的尺度和旋转具有不变性。
SIFT的详细理论不再深入研究,代码如下
import cv2
import numpy as np
# 读取图像
img = cv2.imread('test_1.jpg')
# 转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 创建 SIFT 特征提取器对象
sift = cv2.xfeatures2d.SIFT_create()
# 检测特征点
kp = sift.detect(gray, None)
# 绘制特征点
img_with_keypoints = cv2.drawKeypoints(gray, kp, img)
# 展示结果
cv2.imshow('drawKeypoints', img_with_keypoints)
# 等待用户关闭图像窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
# 计算特征点描述符
kp, des = sift.compute(gray, kp)
# 打印特征点的数量
print(np.array(kp).shape)
特征匹配
图像的特征匹配是计算机视觉中的一项关键任务,用于在不同图像之间找到相似的特征点或区域。特征是图像中具有鲜明而独特性质的点、线、角或区域,可以用来描述和表示图像。
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# 读取两张灰度图像
img1 = cv2.imread('box.png', 0)
img2 = cv2.imread('box_in_scene.png', 0)
# 定义一个显示图像的函数
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 分别展示两张图像
cv_show('img1',img1)
cv_show('img2',img2)
# 创建SIFT实例
sift = cv2.xfeatures2d.SIFT_create()
# 在图像中检测关键点并计算描述子
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# 使用暴力匹配器进行特征匹配
bf = cv2.BFMatcher(crossCheck=True)
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
# 绘制匹配结果
img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None, flags=2)
cv_show('img3',img3)
首先,通过cv2.imread
函数读取了两张灰度图像。然后定义了一个显示图像的函数cv_show
,用于显示图像。接着,创建了SIFT(尺度不变特征变换)算法的实例,并使用detectAndCompute
函数在图像中检测关键点并计算描述子。描述子是用来描述关键点周围区域特征的向量。随后,创建了暴力匹配器(BFMatcher)实例,使用match
函数进行特征点的匹配。匹配结果根据特征点之间的距离进行了排序,并选取了距离最近的前10个匹配对。最后,利用drawMatches
函数绘制了匹配结果,包括了前10个匹配对,然后通过cv_show
函数显示了绘制的匹配结果图像。
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# 读取两张灰度图像
img1 = cv2.imread('box.png', 0)
img2 = cv2.imread('box_in_scene.png', 0)
# 定义一个显示图像的函数
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 分别展示两张图像
cv_show('img1',img1)
cv_show('img2',img2)
# 创建SIFT实例
sift = cv2.xfeatures2d.SIFT_create()
# 在图像中检测关键点并计算描述子
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# 使用k-近邻匹配器进行特征匹配
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
# 筛选出较好的匹配结果
good = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good.append([m])
# 绘制匹配结果
img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2)
cv_show('img3',img3)
首先创建一个暴力匹配器(BFMatcher)实例,并使用knnMatch函数对描述子进行匹配。knnMatch函数返回每个关键点的k个最佳匹配项,这里选择了k=2,即每个关键点最多返回两个最佳匹配项。然后,遍历每个匹配对,通过比较距离来筛选出质量较好的匹配项。我们定义了一个阈值(0.75 * n.distance),如果第一个匹配项的距离小于阈值乘以第二个匹配项的距离,则认为该匹配是较好的。最后,通过drawMatchesKnn函数绘制筛选出的较好匹配结果,并使用cv_show函数显示绘制的图像。
案例-图像拼接
把上面两张图合并为一张图
import numpy as np
class Stitcher:
#拼接函数
def stitch(self, images, ratio=0.75, reprojThresh=4.0,showMatches=False):
#获取输入图片
(imageB, imageA) = images
#检测A、B图片的SIFT关键特征点,并计算特征描述子
(kpsA, featuresA) = self.detectAndDescribe(imageA)
(kpsB, featuresB) = self.detectAndDescribe(imageB)
# 匹配两张图片的所有特征点,返回匹配结果
M = self.matchKeypoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh)
# 如果返回结果为空,没有匹配成功的特征点,退出算法
if M is None:
return None
# 否则,提取匹配结果
# H是3x3视角变换矩阵
(matches, H, status) = M
# 将图片A进行视角变换,result是变换后图片
result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0]))
self.cv_show('result', result)
# 将图片B传入result图片最左端
result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB
self.cv_show('result', result)
# 检测是否需要显示图片匹配
if showMatches:
# 生成匹配图片
vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status)
# 返回结果
return (result, vis)
# 返回匹配结果
return result
def cv_show(self,name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
def detectAndDescribe(self, image):
# 将彩色图片转换成灰度图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 建立SIFT生成器
descriptor = cv2.xfeatures2d.SIFT_create()
# 检测SIFT特征点,并计算描述子
(kps, features) = descriptor.detectAndCompute(image, None)
# 将结果转换成NumPy数组
kps = np.float32([kp.pt for kp in kps])
# 返回特征点集,及对应的描述特征
return (kps, features)
def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh):
# 建立暴力匹配器
matcher = cv2.BFMatcher()
# 使用KNN检测来自A、B图的SIFT特征匹配对,K=2
rawMatches = matcher.knnMatch(featuresA, featuresB, 2)
matches = []
for m in rawMatches:
# 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对
if len(m) == 2 and m[0].distance < m[1].distance * ratio:
# 存储两个点在featuresA, featuresB中的索引值
matches.append((m[0].trainIdx, m[0].queryIdx))
# 当筛选后的匹配对大于4时,计算视角变换矩阵
if len(matches) > 4:
# 获取匹配对的点坐标
ptsA = np.float32([kpsA[i] for (_, i) in matches])
ptsB = np.float32([kpsB[i] for (i, _) in matches])
# 计算视角变换矩阵
(H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh)
# 返回结果
return (matches, H, status)
# 如果匹配对小于4时,返回None
return None
def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status):
# 初始化可视化图片,将A、B图左右连接到一起
(hA, wA) = imageA.shape[:2]
(hB, wB) = imageB.shape[:2]
vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
vis[0:hA, 0:wA] = imageA
vis[0:hB, wA:] = imageB
# 联合遍历,画出匹配对
for ((trainIdx, queryIdx), s) in zip(matches, status):
# 当点对匹配成功时,画到可视化图上
if s == 1:
# 画出匹配对
ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
cv2.line(vis, ptA, ptB, (0, 255, 0), 1)
# 返回可视化结果
return vis
import cv2
# 读取拼接图片
imageA = cv2.imread("left_01.png")
imageB = cv2.imread("right_01.png")
# 把图片拼接成全景图
stitcher = Stitcher()
(result, vis) = stitcher.stitch([imageA, imageB], showMatches=True)
# 显示所有图片
cv2.imshow("Image A", imageA)
cv2.imshow("Image B", imageB)
cv2.imshow("Keypoint Matches", vis)
cv2.imshow("Result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
首先定义了一个Stitcher类,其中包含了图像拼接的各个步骤:
1. 通过detectAndDescribe函数检测并描述输入图像的SIFT关键点和特征。
2. 通过matchKeypoints函数匹配两张图像的关键点,并计算视角变换矩阵。
3. 通过drawMatches函数画出关键点之间的匹配对,用于可视化。
4. 最终的stitch函数则是组合了以上步骤,进行图像拼接,并在需要时显示匹配对的可视化结果。
在Stitcher类中还包含了一个cv_show函数,用于显示图像。
在代码的最后部分,读入两张待拼接的图片imageA和imageB,然后调用Stitcher类的stitch函数进行拼接。接着将拼接前的两张图片、匹配对的可视化结果和拼接后的全景图进行显示。
案例-答题卡选项识别
识别出上图所示的答题卡的选项
#导入工具包
import numpy as np
import cv2
# 正确答案
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
def order_points(pts):
# 一共4个坐标点
rect = np.zeros((4, 2), dtype = "float32")
# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
# 计算左上,右下
s = pts.sum(axis = 1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
# 计算右上和左下
diff = np.diff(pts, axis = 1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect
def four_point_transform(image, pts):
# 获取输入坐标点
rect = order_points(pts)
(tl, tr, br, bl) = rect
# 计算输入的w和h值
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
maxWidth = max(int(widthA), int(widthB))
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA), int(heightB))
# 变换后对应坐标位置
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]], dtype = "float32")
# 计算变换矩阵
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
# 返回变换后结果
return warped
def sort_contours(cnts, method="left-to-right"):
reverse = False
i = 0
if method == "right-to-left" or method == "bottom-to-top":
reverse = True
if method == "top-to-bottom" or method == "bottom-to-top":
i = 1
boundingBoxes = [cv2.boundingRect(c) for c in cnts]
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),key=lambda b: b[1][i], reverse=reverse))
return cnts, boundingBoxes
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 预处理
image = cv2.imread('images/test_05.png')
contours_img = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
cv_show('blurred',blurred)
edged = cv2.Canny(blurred, 75, 200)
cv_show('edged',edged)
# 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[0]
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)
cv_show('contours_img',contours_img)
docCnt = None
# 确保检测到了
if len(cnts) > 0:
# 根据轮廓大小进行排序
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
# 遍历每一个轮廓
for c in cnts:
# 近似
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
# 准备做透视变换
if len(approx) == 4:
docCnt = approx
break
# 执行透视变换
warped = four_point_transform(gray, docCnt.reshape(4, 2))
cv_show('warped',warped)
# Otsu's 阈值处理
thresh = cv2.threshold(warped, 0, 255,
cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
thresh_Contours = thresh.copy()
# 找到每一个圆圈轮廓
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[0]
cv2.drawContours(thresh_Contours,cnts,-1,(0,0,255),3)
cv_show('thresh_Contours',thresh_Contours)
questionCnts = []
# 遍历
for c in cnts:
# 计算比例和大小
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
# 根据实际情况指定标准
if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:
questionCnts.append(c)
# 按照从上到下进行排序
questionCnts = sort_contours(questionCnts,method="top-to-bottom")[0]
correct = 0
# 每排有5个选项
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
# 排序
cnts = sort_contours(questionCnts[i:i + 5])[0]
bubbled = None
# 遍历每一个结果
for (j, c) in enumerate(cnts):
# 使用mask来判断结果
mask = np.zeros(thresh.shape, dtype="uint8")
cv2.drawContours(mask, [c], -1, 255, -1) #-1表示填充
cv_show('mask',mask)
# 通过计算非零点数量来算是否选择这个答案
mask = cv2.bitwise_and(thresh, thresh, mask=mask)
total = cv2.countNonZero(mask)
# 通过阈值判断
if bubbled is None or total > bubbled[0]:
bubbled = (total, j)
# 对比正确答案
color = (0, 0, 255)
k = ANSWER_KEY[q]
# 判断正确
if k == bubbled[1]:
color = (0, 255, 0)
correct += 1
# 绘图
cv2.drawContours(warped, [cnts[k]], -1, color, 3)
score = (correct / 5.0) * 100
print("[INFO] score: {:.2f}%".format(score))
cv2.putText(warped, "{:.2f}%".format(score), (10, 30),cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow("Original", image)
cv2.imshow("Exam", warped)
cv2.waitKey(0)
首先,代码导入了numpy和cv2这两个工具包。然后定义了一个ANSWER_KEY字典,存储了正确答案的编号。接下来是一些辅助函数的定义:
- order_points函数用于按照顺序找到输入坐标点的对应坐标,用于透视变换。
- four_point_transform函数用于执行透视变换,将检测到的试卷图像调整为矩形形状。
- sort_contours函数用于对轮廓进行排序,按照从左到右或从上到下的方式排列。
- cv_show函数用于显示图像。
之后,代码进行了图像预处理的操作:
- 使用cv2.imread函数读取待处理的图像。
- 对图像进行灰度化处理和高斯模糊。
- 使用Canny边缘检测算法找到图像的边缘。
- 对边缘图像进行轮廓检测,得到一系列轮廓。
- 对轮廓按照面积大小进行排序,找到面积最大的轮廓作为试卷的外轮廓。
接下来,代码对检测到的试卷进行了透视变换,得到修正过形状的图像。
然后,代码对透视变换后的图像进行了二值化处理(使用Otsu阈值处理)。
接着,代码再次进行轮廓检测,找到答题卡上的圆圈区域。
之后,代码按照一行一行的方式遍历每个问题,并对每个问题的五个选项进行处理。
- 针对每个选项,通过计算非零点数量来判断是否选择了该选项。
- 通过比较选择的结果与正确答案的对应关系,判断是否回答正确。
- 根据答题情况,在透视变换后的图像上绘制不同颜色的标记。
最后,代码计算了回答正确的题目数量,并计算了得分。最后将得分绘制在最终的图像上,并显示原始图像和处理后的图像。
结果如下图:
背景建模
帧差法
定义:帧差法(Frame Difference Method)是一种视频处理中常用的运动检测算法,用于检测连续视频帧之间的差异,从而找出视频中的运动目标或变化区域。
原理:在视频中,连续的帧之间通常会存在一定的差异,例如移动的物体会在相邻帧中出现位置上的变化。帧差法利用每一帧图像与前一帧图像之间的差异来检测运动目标或变化区域。
步骤:首先,将当前帧图像与前一帧图像进行像素级别的比较,得到差异图像。然后差异图像中的非零像素表示在两帧之间发生了变化的区域,可以通过阈值化或其他处理方式提取出感兴趣的变化区域。最后进一步对提取出来的变化区域进行形态学处理或其他算法,以便检测、跟踪或分割出具体的运动目标。
优缺点:简单易实现,对计算资源要求不高,适用于对小规模运动目标的检测。对于光照变化大、背景复杂的场景,容易受到干扰,需要结合其他方法进行进一步处理。
混合高斯模型
在进行前景检测前,先对背景进行训练,对图像中每个背景采用一个混合高斯模型进行模拟,每个背景的混合高斯的个数可以自适应。然后在测试阶段,对新来的像素进行GMM匹配,如果该像素值能够匹配其中一个高斯,则认为是背景,否则认为是前景。由于整个过程GMM模型在不断更新学习中,所以对动态背景有一定的鲁棒性。最后通过对一个有树枝摇摆的动态背景进行前景检测,取得了较好的效果。
在视频中对于像素点的变化情况应当是符合高斯分布
背景的实际分布应当是多个高斯分布混合在一起,每个高斯模型也可以带有权重
混合高斯模型学习方法
1.首先初始化每个高斯模型矩阵参数。
2.取视频中T帧数据图像用来训练高斯混合模型。来了第一个像素之后用它来当做第一个高斯分布。
3.当后面来的像素值时,与前面已有的高斯的均值比较,如果该像素点的值与其模型均值差在3倍的方差内,则属于该分布,并对其进行参数更新。
4.如果下一次来的像素不满足当前高斯分布,用它来创建一个新的高斯分布。
混合高斯模型测试方法
在测试阶段,对新来像素点的值与混合高斯模型中的每一个均值进行比较,如果其差值在2倍的方差之间的话,则认为是背景,否则认为是前景。将前景赋值为255,背景赋值为0。这样就形成了一副前景二值图。
import numpy as np
import cv2
# 经典的测试视频
cap = cv2.VideoCapture('test.avi')
# 形态学操作需要使用
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
# 创建混合高斯模型用于背景建模
fgbg = cv2.createBackgroundSubtractorMOG2()
# 定义保存视频的参数
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('detection.avi', fourcc, 20.0, (640, 480))
while True:
ret, frame = cap.read()
fgmask = fgbg.apply(frame)
# 形态学开运算去噪点
fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
# 寻找视频中的轮廓
contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
# 计算各轮廓的周长
perimeter = cv2.arcLength(c, True)
if perimeter > 188:
# 找到一个直矩形(不会旋转)
x, y, w, h = cv2.boundingRect(c)
# 画出这个矩形
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
# 将检测结果写入输出视频
out.write(frame)
cv2.imshow('frame', frame)
cv2.imshow('fgmask', fgmask)
k = cv2.waitKey(150) & 0xff
if k == 27:
break
cap.release()
out.release()
cv2.destroyAllWindows()
主要思路是使用 OpenCV 中的视频处理功能,首先通过 `cv2.VideoCapture()` 打开视频文件,然后创建一个混合高斯模型 `fgbg` 用于背景建模,通过不断读取视频帧来进行背景减除和轮廓检测,最后将处理后的帧展示出来。具体步骤如下:
1. 打开视频文件:使用 cv2.VideoCapture('test.avi')打开名为 `test.avi` 的视频文件。
2. 背景建模:创建混合高斯模型fgbg用于背景建模,通过fgbg.apply()对每一帧进行背景建模,得到前景掩码 fgmask。
3. 去噪点处理:对前景掩码进行形态学开运算去除噪点,使用cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)进行处理。
4. 轮廓检测:利用cv2.findContours() 寻找前景掩码中的轮廓,对每个轮廓计算周长,并筛选出符合条件的轮廓。
5. 画出矩形:对符合条件的轮廓,使用cv2.rectangle()画出对应的矩形。
6. 显示处理后的帧:使用cv2.imshow()显示处理后的帧以及前景掩码。
7. 循环处理:通过cap.read()循环读取视频的每一帧并处理,直到视频结束或用户按下Esc键退出。
8. 释放资源:最后释放视频资源并关闭所有窗口。
光流估计
光流是空间运动物体在观测成像平面上的像素运动的“瞬时速度”,根据各个像素点的速度矢量特征,可以对图像进行动态分析,例如目标跟踪。
-
亮度恒定:同一点随着时间的变化,其亮度不会发生改变。
-
小运动:随着时间的变化不会引起位置的剧烈变化,只有小运动情况下才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。
-
空间一致:一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。所以需要连立n多个方程求解。
Lucas-Kanade 算法
cv2.calcOpticalFlowPyrLK():
参数:
-
prevImage 前一帧图像
-
nextImage 当前帧图像
-
prevPts 待跟踪的特征点向量
-
winSize 搜索窗口的大小
-
maxLevel 最大的金字塔层数
返回:
-
nextPts 输出跟踪特征点向量
-
status 特征点是否找到,找到的状态为1,未找到的状态为0
import numpy as np
import cv2
# 打开名为 'test.avi' 的视频文件
cap = cv2.VideoCapture('test.avi')
# 角点检测所需参数
feature_params = dict( maxCorners = 100, # 最大角点数量
qualityLevel = 0.3, # 角点检测品质因子
minDistance = 7) # 角点之间的最小欧氏距离
# Lucas-Kanade方法参数
lk_params = dict( winSize = (15,15), # 窗口大小
maxLevel = 2) # 金字塔层数
# 生成随机颜色,用于绘制轨迹
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)
while(True):
# 读取当前帧
ret, frame = cap.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 计算光流
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# 根据跟踪结果筛选出好的新旧角点
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().astype(int)
c,d = old.ravel().astype(int)
mask = cv2.line(mask, (a,b), (c,d), color[i].tolist(), 2) # 绘制轨迹
frame = cv2.circle(frame, (a,b), 5, color[i].tolist(), -1) # 标记角点
img = cv2.add(frame, mask) # 将轨迹和原始帧叠加
cv2.imshow('frame', img) # 显示处理后的帧
k = cv2.waitKey(150) & 0xff
if k == 27: # 按下Esc键退出循环
break
# 更新参数
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
# 释放资源,关闭窗口
cv2.destroyAllWindows()
cap.release()
以下是主要步骤的思路:
1. 首先使用cv2.VideoCapture('test.avi')打开名为test.avi的视频文件。
2. 定义角点检测和光流跟踪所需要的参数:feature_params用于角点检测,lk_params用于光流跟踪。
3. 读取第一帧图像,并将其转换为灰度图像,然后使用cv2.goodFeaturesToTrack()检测图像中的角点,并存储在p0中。
4. 创建一个与视频帧相同大小的空白图像作为mask,用于绘制光流轨迹。
5. 在一个循环中,不断读取视频的每一帧,并将其转换为灰度图像。
6. 调用cv2.calcOpticalFlowPyrLK()来计算前一帧和当前帧之间的光流,得到新的角点位置p1。
7. 根据计算出的光流,绘制光流的轨迹,并在图像上标记出光流的起点和终点。
8. 将绘制了轨迹的图像通过cv2.add()函数与之前创建的mask相加,以便在同一帧中显示光流轨迹。
9. 循环继续,直到视频结束或用户按下Esc键退出。
10. 在循环中更新前一帧的灰度图像和检测到的角点位置,以便进行下一帧的光流跟踪。
11. 最后释放视频资源并关闭所有窗口。
总的来说,代码通过光流法对视频中的光流进行跟踪,并在图像上显示光流的轨迹,给用户展示了视频中物体的运动路径。
个人不是CV方向,就此打住,学习资料中还有一些停车场车位识别以及疲劳监测等,就不记录了。感觉opencv入门到这就差不多了,作为自己的复习笔记吧。