主题
视觉测距作为机器视觉领域内基础技术之一而受到广泛关注,其在机器人领域内占有重要的地位,被广泛应用于机器视觉定位、目标追踪、视觉避障等。 视觉测距主要分为单目测距、双目测距、结构光测距等。结构光由于光源的限制,应用的场合比较固定;而双目测距的难点在于特征点的匹配,会影响了测量的精度和效率,其理论研究的重点集中于特征的匹配上;而单目测距结构简单、运算速度快而具有广阔的应用前景,但是单目测距只是一个二维的平面。
不知道大家有没有玩过苹果推出的“测距仪”,可以使用摄像头来进行物体之间距离测量。它在进行物体距离测量之前,会需要你将苹果设备进行一定的移动,为什么需要移动呢?其实它是在计算立体视差。
什么是立体视差呢?简单来讲,我们从不同的角度(通过细微移动来得到)去观察同一场景下的物体,两张图像叠放在一起,针对两张图像中的同一个物体之间任意一对相互对应的像素点,可以度量这些像素之间的距离,这个度量就是立体视差。近距离的物体会产生较大的立体视差,而远距离的物体就会小一些。如果我们把立体视差的值标准化到[0,255]这个区间内并构成一张图像(即放入到灰度图中),这张图像称为“视差图”,近距离的物体在视差图中就会更接近255,所以会更明亮一些。 除了立体视差以外,还需要用到的知识还有类似于“极几何”等,其属于立体视觉几何学,可以从不同的两张图像中提取三维信息。 很显然的是,这篇文章做不到像苹果的测距仪那样的效果,只是做些单目、简单的、视觉测距。
单目测距的原理
单目测距的原理,简单来说,就是“相似”。 单目测距已知物体是指在已知物体信息的条件下利用摄像机获得的目标图片得到深度信息。此类方法主要用于导航和定位,但其利用单个特征点进行测量,容易因特征点提取不准确性而产生误差。 相似三角形: 接下来使用相似三角形来计算相机到一个已知物体(目标)的距离。 相似三角形的原理是,假设有一个宽度为W的物体(目标),其与相机的距离为D。用相机对物体进行拍照并且测量物体的像素宽度为P,得出相机焦距的公式如下式所示。
其中,P是指像素距离,W是A4纸的宽度,F是摄像机焦距。可以发现,这种算法与实际距离还是有一定的误差,误差是由像素点必须是整数而非小数造成的。 测量焦距 首先需要先测定摄像头的焦距F,对于一个单目摄像头,焦距在测量完成后可以理解为一个常数,示例代码如下。
import numpy as np
import cv2
#已知参数
KNOWN_DISTANCE = 24.0
KNOWN_WIDTH = 11.69
KNOWN_HEIGHT = 8.27
#计算焦距
def find_marker(image):
gray_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray_img = cv2.GaussianBlur(gray_img, (5, 5), 0)
edged_img = cv2.Canny(gray_img, 35, 125)
countours,hierarchy=cv2.findContours(edged_img,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
c = max(countours, key = cv2.contourArea)
rect = cv2.minAreaRect(c)
return rect
def calculate_focalDistance(img_path):
first_image = cv2.imread(img_path)
marker = find_marker(first_image)
focalLength = (marker[1][0] * KNOWN_DISTANCE) / KNOWN_WIDTH
print('焦距(focalLength )= ',focalLength)
return focalLength
if __name__ == "__main__":
img_path = "Picture1.jpg"
focalLength = calculate_focalDistance(img_path)
为了能够获得尽量清楚的边界,这里采用高斯滤波的方式先对原图像进行了过滤,再进行Canny边缘的检测。然后在cv2.findContours之后通过调用max函数来获取轮廓面积最大的轮廓来进行剩下的操作。 在find_marker函数中有一个rect变量,它是构成最小外接矩形所需要的一些数值。
rect[1][0]是width,
rect[1][1]是height
rect[2]是角度
所以接下来计算焦距F的时候采用marker[1][0]来进行计算。
动态单目测距
现在来尝试做动态单目测距,示例代码如下。
import cv2
import numpy as np
#参数,根据测距公式:D=(F*W)/P
KNOWN_WIDTH=2.36
KNOWN_HEIGHT=8.27
KNOWN_DISTANCE=7.7
FOCAL_LENGTH=543.45
cap=cv2.VideoCapture(0)
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('latest.avi',fourcc,20.0,(640,480))
while 1:
ret,img=cap.read()
#画面预处理
img=cv2.flip(img,1)
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
blurred=cv2.GaussianBlur(gray,(5,5),0)
edges=cv2.Canny(img,35,125)
kernel=cv2.getStructuringElement(cv2.MORPH_RECT,(10,10))
closed=cv2.morphologyEx(edges,cv2.MORPH_CLOSE,kernel)
closed=cv2.erode(closed,None,iterations=4)
closed=cv2.dilate(closed,None,iterations=4)
contours,hierarchy=cv2.findContours(closed,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
#获得最大边界
cnt=max(contours,key=cv2.contourArea)
img=cv2.drawContours(img,[cnt],-1,(0,255,0),3)
rect=cv2.minAreaRect(cnt)
distance=(KNOWN_WIDTH*FOCAL_LENGTH)/rect[1][0]
#提示距离
cv2.putText(img,"%.2fcm"%(distance*2.54),(img.shape[1]-300,img.shape[0]-20),cv2.FONT_HERSHEY_SIMPLEX,2.0,(0,0,255),3)
out.write(img)
#显示画面
cv2.imshow('img', img)
cv2.imshow('edges', edges)
cv2.imshow('closed', closed)
if cv2.waitKey(1)==ord('q'):
break
cap.release()
out.release()
cv2.destroyAllWindows()
运行代码之前先准备好测试的工具,然后就可以做到动态的单目测距了,运行结果下图所示。 注意:如果待测物体不在视野内,单目测距会出现错误;当物体回到视野内的时候,数据就会恢复正常。所以在一开始运行代码时记得保证物体处在镜头的视野内,不然数据会出错
总结
现在用的比较多的测距装备还有例如深度摄像头这种,比较有名的就是微软的Kinect,不过Kinect最近几年好像已经停产了。 它是将传统的摄像头和一个红外传感器相结合来帮助摄像头区别相似物体并计算距离。 对于Kinect计算距离的原理除了“极几何”的知识以外,还会涉及到:深度图,点云图,有效深度掩膜,像素代价函数等,这里就不再做详细介绍了,感兴趣的读者可以去查阅相关书籍、资料来进行了解、学习。 文章的最后也谢谢大家的支持(想对从上篇文章过来的读者说声抱歉,原本说好的中文文字识别的文章,暂时性咕咕了,但以后会补回来的),也欢迎新的读者点一波关注,你们的支持就是我最大的写作动力,谢谢大家!