单目相机测距



前言

根据导师的项目需求,需要在工厂环境下控制相机拍摄水泥罐车车顶图片,识别出车顶的水泥罐装圆形口并进行坐标定位,这其中需要涉及到 相机标定、单目测距、图像坐标确定、圆形物体识别 等。


一、单目测距

进行单目相机测距之前我们首先要做到两点:1.保证测距使用的图片是矫正后的、没有畸变的,可以提高我们的精度(涉及相机标定);2.我们已知相机的焦距(可以通过相机标定得到)。

1.相机标定

参考

https://blog.csdn.net/spw_1201/article/details/78417551

我们首先使用MATLAB对相机拍摄图片进行标定得到内外参数,再使用opencv通过已知内外参数进行图片矫正。

(1)获取标定板

使用最简单的黑白棋盘格标定板,可以直接从opencv官网下载得到:

https://docs.opencv.org/2.4/_downloads/pattern.png

10*7的棋盘格

(2)拍摄图片

使用系统所用的单目相机拍摄带有棋盘格标定板各个角度的照片,保存大概15-20张即可。需要测量记录好棋盘格方格的大小,在标定中会使用。按照opencv官网提供的图片下载打印出的方格大小大约为26mm
数据

(3)进行标定

使用MATLAB进行相机标定:首先在命令行窗口中输入cameraCalibrator调用标定应用;
命令行窗口
或者在APP中找到cameraCalibrator。
cameraCalibrator
即可打开 Camera Calibrator
Camera Calibrator
将我们之前采集的图片添加进去,会出现选择棋盘格方格大小的窗口,按照我们之前的测量记录选择参数即可。
棋盘格方格大小的窗口
单击确定后,MATLAB会自动地分析图片
分析图片
图片分析结束后,会出现结果窗口
结果窗口
可以看到,我们的20张图片中有6个是不可以使用的,可能是由于角度等问题。点击view images也可以具体查看时哪些图片不可以使用。

点击确定后出现检测成功的图片结果
图片结果

后面就是比较关键的一步,我们需要选择标定的参数

畸变参数,总共有五个,径向畸变3个(k1,k2,k3)和切向畸变2个(p1,p2)。
在OpenCV中的畸变系数的排列(k1,k2,p1,p2,k3),千万不要以为k是连着的
畸变系数

参数选择
1.camera model : standard(标准) fisheye(鱼眼),我的摄像头是标准。

2.options:选中径向畸变:“2 coefficients”并且选择偏差:“Skew“和切向畸变:“Tangential Distortion“

径向畸变:通常,两个系数足以进行校准。对于严重失真,例如在广角镜头中,您可以选择3″“3 coefficients””个系数来包含k3。

偏差: 选择Compute Skew 复选框时,校准器会估算图像轴偏斜。某些相机传感器包含缺陷,导致图像的x轴和y轴不垂直。您可以使用skew参数对此缺陷进行建模。如果不选中该复选框,则假定图像轴是垂直的,大多数现代相机都是这种情况。

切向畸变: 当镜头和图像平面不平行时,发生切向畸变。切向失真系数模拟了这种类型的失真:
(这些选项根据你的相机进行选择)

然后点击Calibrate按钮即可得到标定的结果。

在这里插入图片描述
点击Show Undistorted按钮可以显示无畸变的图像
无畸变的图像
选择导出数据,即可把参数进行保存
参数进行保存
保存后可以退出标定应用,在MATLAB主界面中将保存的cameraParams文件打开。
cameraParams文件
里面的RadialDistortion对应 **k1,k2(k3设置为0了)。
RadialDistortion
TangentialDistortion对应 p1,p2
TangentialDistortion
IntrinsicMatrix对应 内参数矩阵,注意这个和OpenCV中是转置的关系,注意不要搞错。
IntrinsicMatrix
对应于
在这里插入图片描述

FocalLength对应 相机焦距,这个在后面进行测距工作会用到
相机焦距

(4)使用opencv查看标定结果

代码如下

import cv2
import numpy as np


#将相机的参数设置好,固定值
#相机内参数矩阵,3*3矩阵
cameraMatrix = np.array([[3.520275278305173e+03, 7.951186274833666, 2.302224660073940e+03],
                         [0, 3.521771625935550e+03, 1.729056461937039e+03],
                         [0, 0, 1]])

#相机畸变系数矩阵,5*1矩阵(k1,k2,p1,p2,k3)
distCoeffs = np.array([0.092142020349690, -0.532537876414274, -0.002400984624408, 0.001577353702050, 0])

# 读入原图片
img = cv2.imread("E:/Cement Canning/600.jpg")
h, w = img.shape[:2]

newCameraMatrix, roi = cv2.getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, (w,h), 1, (w,h), 0)
# 计算无畸变和修正转换关系
mapx, mapy = cv2.initUndistortRectifyMap(cameraMatrix, distCoeffs, None, newCameraMatrix, (w,h), cv2.CV_16SC2)

# 重映射
dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)

# 调整显示窗口大小
cv2.namedWindow("dst",0);
cv2.resizeWindow("dst", 1268, 952);
cv2.imshow("dst", dst)
#按任意键退出
cv2.waitKey(0)
cv2.destroyAllWindows()

矫正后的结果如下:
矫正图
可以看到矫正的效果并不理想,可能是选择options时不合适。
对比其他博主的效果,我认为问题可能是由于,我的图片是用手机拍摄的,手机可能已经带有去畸变矫正,所以导致实验效果不佳?

2.测距

参考

https://blog.csdn.net/m0_37811342/article/details/80394935

通过阅读其他资料可以得知 焦距F(单位像素值)、目标物体宽度W(单位m)、目标物体在图片中的像素宽度P(单位像素值)、目标物体距离相机距离X(单位m) 之间的关系:
F = (P*X) / W

那么在我们已知F、P、W的情况下即可求得物体距离D:
X = (F*W) / P

F我们在上面可以得到:
Fx = 3.520275278305173e+03
Fy = 3.521771625935550e+03

至于为什么一个相机出现两个焦距可以参考下面的讲解:

https://www.cnblogs.com/zipeilu/p/6658177.html

目标物体我使用的是标准的A4纸对折,宽度W为:0.21m,高度H为:0.1485m

白纸图

至于像素宽度P我们可以使用霍夫变换检测并求出白纸在图像中的像素宽度值

代码如下,用霍夫变换检测到图像中的矩形,即可得到矩形的,并通过得到的矩阵的轮廓长度和面积来计算像素宽度和像素高度

需要注意的是: 拍摄的图片中最后不要出现太多无关物体,否则识别白纸时会造成干扰。)

import cv2
import math
import numpy as np

img = cv2.imread("E:/Cement Canning/400.jpg")    #读取图像
cv2.namedWindow("img",0);
cv2.resizeWindow("img", 1268, 952);
cv2.imshow("img",img) #显示原图像

dst = img.copy()
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转为灰度值图
gaussian = cv2.GaussianBlur(gray, (5, 5), 0, 0) #高斯去噪
cv2.namedWindow("gaussian",0);
cv2.resizeWindow("gaussian", 1268, 952);
cv2.imshow("gaussian", gaussian)

ret, binary = cv2.threshold(gaussian,127,255,cv2.THRESH_BINARY) #转为二值图
cv2.namedWindow("binary",0);
cv2.resizeWindow("binary", 1268, 952);
cv2.imshow("binary", binary)

contours, hierarchy = cv2.findContours(binary,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE) #寻找轮廓

#n=len(contours)       #轮廓个数
#for i in range(n):
    #去除掉根本不可能是要识别的矩形的物体,这个可以在
    # if len(contours[i]) >= 4:
    #     print(contours[i])
    #     length = cv2.arcLength(contours[i], True)  #获取轮廓长度
    #     area = cv2.contourArea(contours[i])        #获取轮廓面积
    #     print('length['+str(i)+']长度=',length)
    #     print("contours["+str(i)+"]面积=",area)
    #     cv2.drawContours(dst, contours, i, (0, 0, 255), 3) #绘制轮廓

#通过面积来筛选出我们要识别的矩形
c = max(contours, key=cv2.contourArea)
print(c)
cv2.drawContours(dst, c, -1, (0, 0, 255), 3) #绘制轮廓
#轮廓长度
length = cv2.arcLength(c, True)
#轮廓面积
area = cv2.contourArea(c)
#通过周长和面积计算矩形的高度和宽度
x = length/4 - (math.sqrt(math.pow(length, 2)/16 - area))
y = length/2 - x
print("高度或宽度:", x)
print("高度或宽度:", y)
cv2.namedWindow("dst",0);
cv2.resizeWindow("dst", 1268, 952);
cv2.imshow("dst", dst)
cv2.waitKey()
cv2.destroyAllWindows()

结果图

像素高度和宽度
接下来就可以计算相机距离拍摄的白纸的距离,我们在拍摄时测量了手机镜头到白纸的距离为0.21m,后面我们通过计算来看看误差是多少。

(1)通过高度计算
距离 = (Fy * H)/ 2251.85589
距离 = 0.23225m
误差: 10.595%

(2)通过宽度计算

距离 = (Fx * W)/ 4381.74687
距离 = 0.16871m
误差:19.662%

分析:(1)x,y方向到白纸的距离有差别,可能是我用手机拍摄时,手机镜头不是水平的原因;(2)误差较大,可能是我测量镜头到白纸的距离用尺子测得不准确,还有就是拍摄的图片效果不好,后面可以使用工业相机再次进行尝试;(3)霍夫变换检测矩形时,检测到矩形边缘上的点不是连续的,这就导致计算出来的轮廓长度和面积有误差,导致计算出来的像素宽度和高度有误差。

根据(3)中原因,我尝试使用手动测量像素宽度和高度,高度为:2711.0158px宽度为:3757.6278px
高度计算出的距离为:0.19291m, 误差为:8.138%
宽度计算出的距离为:0.19674m, 误差为:6.314%

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐