注意:本人尚处在opencv的入门学习阶段,本博客仅为个人学习笔记见解,如有不当,欢迎指出
(实验/理论)平面标志物的视觉跟踪,要求:
步骤:识别标志物→空间注册→跟踪→绘图
可以使用的方法有:模板匹配、前景分离、边缘提取、特征点匹配、训练级联分类器
模板匹配是一种用于在较大图像中搜索和查找模板图像位置的方法。它只是将模板图像滑动到输入图像上(就像在2D卷积中一样),然后在模板图像下比较模板和输入图像的拼图。
模板匹配具有自身的局限性,主要表现在它只能进行平行移动,若原图像中的匹配目标发生旋转或大小变化,该算法无效。
可以用于车辆识别,就是在视频中有动的物体和静止的物体,通过前景分离,可以把静的物体过滤掉,实现对动的车辆的识别
提取图片的边缘,如果视频背景是纯色的,可以用这个方法提取标志物,但是如果视频中还有其他物体,则还需要再进行处理
这是我这次实验使用的方法。因为匹配了特征点后,物体的旋转、平移都是可以检测到的,方便在这个基础上绘制虚拟物体
这个方法尝试过,但最后因为匹配结果比较差,就放弃了。这个方法就是对标志物进行样本采集,然后训练一个分类器,在视频中调用该分类器对每帧进行识别,很多人脸识别、车辆识别是用的这个方法
将视频第一帧的图像与标志物图像进行特征匹配,找到标志物特征点对应在相机图像上的坐标,跟踪这些坐标的移动
本实验采用的是Meanshift跟踪方法,因为它比较简单,而且可以实现想要的效果
原理:
由于相邻两帧之间目标的偏移量非常小,而当前帧F1的目标框B1已知,所以一种启发式的做法是在下一帧F2中依旧框选已知的B1区域,根据F2中B1区域和F1中B1区域的相似性推断出目标框所需的偏移量,进而得到F2的目标框B2,这就是MeanShift算法所需要解决的事
资料:MeanShift跟踪算法
本实验只是绘制了一个半透明矩形在标志物上方,因为找不到使用opencv绘制3D物体的方法,好像用openGL可以
另外,如果使用相机校准棋盘格的方法,也可以绘制出一个立体图形,参考:Docs » 相机校准和3D重建 » 7_2_姿态估计
import numpy as np
import cv2 as cv
if __name__ == '__main__':
# 视频的位置
cap = cv.VideoCapture(r"C:\Users\ccy\Desktop\video3.mp4")
# 拍摄第一帧
ret, first_frame = cap.read()
# 对第一帧进行特征匹配
if ret:
img1 = cv.cvtColor(first_frame, cv.COLOR_BGR2GRAY)
# 训练图像
img_train = cv.imread(r'C:\Users\ccy\Desktop\1.png')
img2 = cv.cvtColor(img_train, cv.COLOR_BGR2GRAY)
# 初始化ORB检测器
orb = cv.ORB_create()
# 基于ORB找到关键点和检测器
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# 创建BF匹配器的对象
# 第一个参数normType表示距离度量方法
# 第二个参数crossCheck是布尔型变量,如果为true,表示进行双向匹配,这样就可以增强匹配鲁棒性
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)
# 匹配描述符,返回最佳匹配
matches = bf.match(des1, des2)
leftQueryIdx = matches[0].queryIdx
bottomQueryIdx = matches[0].queryIdx
for mat in matches:
# Get the matching keypoints for each of the images
img1_idx = mat.queryIdx
img2_idx = mat.trainIdx
# x - columns
# y - rows
# Get the coordinates
(x1, y1) = kp1[img1_idx].pt
# 找到矩形最左上角的点和最右下角的点,这个可能得根据不同的标志物稍作调整
if y1 < kp1[leftQueryIdx].pt[1]:
leftQueryIdx = img1_idx
if x1 > kp1[bottomQueryIdx].pt[0]:
bottomQueryIdx = img1_idx
# (x2, y2) = kp2[img2_idx].pt
# print("kp1[img1_idx].pt[0]:", kp1[img1_idx].pt[0])
# print("kp1[img1_idx].pt[1]:", kp1[img1_idx].pt[1])
# print("(x2,y2):", (x2, y2))
# -1是thickness参数,即CV_FILL,其结果是使用与边一样的颜色填充圆内部
# 下面这行代码会标记出匹配到的特征点
# first_frame = cv.circle(first_frame, (int(x1), int(y1)), 5, (255, 0, 0), -1)
# print("kp1[leftQueryIdx].pt", kp1[leftQueryIdx].pt)
# print("kp1[bottomQueryIdx].pt", kp1[bottomQueryIdx].pt)
leftIntCd = tuple(map(lambda x: int(x), kp1[leftQueryIdx].pt))
bottomIntCd = tuple(map(lambda x: int(x), kp1[bottomQueryIdx].pt))
# 对后面每帧进行meanshift跟踪
(x, y) = leftIntCd
w = bottomIntCd[0] - leftIntCd[0]
h = bottomIntCd[1] - leftIntCd[1]
track_window = (x, y, w, h)
# 设置初始ROI来追踪
roi = first_frame[y:y + h, x:x + w]
hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
mask = cv.inRange(hsv_roi, np.array((0., 60., 32.)), np.array((180., 255., 255.)))
roi_hist = cv.calcHist([hsv_roi], [0], mask, [180], [0, 180])
cv.normalize(roi_hist, roi_hist, 0, 255, cv.NORM_MINMAX)
# 设置终止条件,可以是10次迭代,也可以至少移动1 pt
term_crit = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1)
while (1):
retOtherFrame, frame = cap.read()
if retOtherFrame:
# 测量帧率
loop_start = cv.getTickCount()
# 灰度处理
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
dst = cv.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)
# 应用meanshift来获取新位置
ret, track_window = cv.meanShift(dst, track_window, term_crit)
# 在图像上绘制
x, y, w, h = track_window
# 绘制半透明矩形
blk = np.zeros(frame.shape, np.uint8)
cv.rectangle(blk, (x, y), (x + w, y + h), (0, 255, 0), -1) # 注意在 blk的基础上进行绘制;
cv.putText(blk, 'virtural', (x, y), cv.FONT_HERSHEY_COMPLEX_SMALL, 1.3, (0, 255, 0), 1)
img2 = cv.addWeighted(frame, 1.0, blk, 0.8, 1)
# 中间测帧率的代码段
loop_time = cv.getTickCount() - loop_start
total_time = loop_time / (cv.getTickFrequency()) # 使用getTickFrequency()更加准确
running_FPS = int(1 / total_time) # 帧率取整
print("running_FPS:", running_FPS)
# 显示图片
cv.namedWindow('img2', cv.WINDOW_FREERATIO)
cv.imshow('img2', img2)
k = cv.waitKey(1) & 0xff
if k == 27:
break
else:
break
OpenCV中文官方文档