注意:本人尚处在opencv的入门学习阶段,本博客仅为个人学习笔记见解,如有不当,欢迎指出
题目
(实验/理论)平面标志物的视觉跟踪,要求:
- 选择一个标志物,可以是人工标志物,也可以是自然标志物;实现和实验二相同的效果。
- 用手机或摄像头拍摄标志物的影像,建议读取视频流中的影像;
- 写一个视觉算法获得标志物与相机的相对位姿;
- 测量算法的帧率;
- 添加虚拟物体;
- 算法自己完成,不得使用ARCore/easyAR等现成SDK。可以使用opencv中自带的函数。
注:使用OpenCV/OpenGL来实现一些滤波、矩阵运算、优化、画图等低层的算法。
实验思路
步骤:识别标志物→空间注册→跟踪→绘图
识别标志物
可以使用的方法有:模板匹配、前景分离、边缘提取、特征点匹配、训练级联分类器
模板匹配
模板匹配是一种用于在较大图像中搜索和查找模板图像位置的方法。它只是将模板图??像滑动到输入图像上(就像在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 = cv.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
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:
img1_idx = mat.queryIdx
img2_idx = mat.trainIdx
(x1, y1) = kp1[img1_idx].pt
if y1 < kp1[leftQueryIdx].pt[1]:
leftQueryIdx = img1_idx
if x1 > kp1[bottomQueryIdx].pt[0]:
bottomQueryIdx = img1_idx
leftIntCd = tuple(map(lambda x: int(x), kp1[leftQueryIdx].pt))
bottomIntCd = tuple(map(lambda x: int(x), kp1[bottomQueryIdx].pt))
(x, y) = leftIntCd
w = bottomIntCd[0] - leftIntCd[0]
h = bottomIntCd[1] - leftIntCd[1]
track_window = (x, y, w, h)
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)
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)
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)
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())
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中文官方文档
|