技术研究之单目测距

标签: 无 分类: 未分类 创建时间:2024-11-17 10:26:18 更新时间:2025-01-17 10:39:23

1.前言

最近在弄无人机的项目,其中的一个就是目标检测,在检测到目标物体之后,如何获取到目标物体离当前无人机的距离,然后快速发现问题经纬度坐标,这是一个问题。主要分为了:单目测距和双目测距。

  • 单目测距
    单目测距主要依靠图像中物体的几何信息来进行距离估计。常用的方法包括三角测量法、景深测量法和结构光测量法。三角测量法基于物体在图像中的尺寸和位置信息,结合相机的内外参数进行距离估计。景深测量法利用图像中物体的模糊程度来反映物体的远近关系,通过对焦距离的调整来估计距离。结构光测量法则利用投影仪投射特定的光纹到物体上,通过获取投影图像和物体表面的形变信息来计算距离。

  • 双目测距
    双目摄像头的原理与人眼相似。人眼能够感知物体的远近,是由于两只眼睛对同一个物体呈现的图像存在差异,也称“视差”。物体距离越远,视差越小;反之,视差越大。视差的大小对应着物体与眼睛之间距离的远近,这也是3D电影能够使人有立体层次感知的原因。

参考文章:
【1】.用普通摄像头测量距离 近年来,由于无人机、无人车等技术的不断成熟,需要用到实时测距的场所也越来越多,如定位,避障,测速等,相比于其他测距方法,单目测距是利用一个摄像头进行视频拍摄,在图像中找到待测物体。
【2】.使用OpenCV实现摄像头测距 需要先测量出目标物体的实际宽度和目标物体到相机的距离,然后使用图像处理的方法自动计算图片中目标物体的像素宽度,并使用相似三角形计算出相机的焦距。根据相机的焦距就可以计算图片中的目标物体到相机的距离。用 cv2.findContours 函数找到图片中的众多轮廓,然后获取其中面积最大的轮廓,并假设这是目标物体的轮廓。
这种假设只适用于我们这个场景,在实际使用时,在图片中找出目标物体的方法与应用场景有很大关系。我们这个场景用简单的边缘检测并找出最大的轮廓就可以了。当然为了使程序更具有鲁棒性,也可以用轮廓近似,并剔除不是四个点的轮廓(纸张是一个有四个点的矩形),然后再找出面积最大,具有四个点的轮廓。
【3】.单目测距的基本介绍和实现原理 单目测距主要依靠图像中物体的几何信息来进行距离估计。常用的方法包括三角测量法、景深测量法和结构光测量法。三角测量法基于物体在图像中的尺寸和位置信息,结合相机的内外参数进行距离估计。景深测量法利用图像中物体的模糊程度来反映物体的远近关系,通过对焦距离的调整来估计距离。结构光测量法则利用投影仪投射特定的光纹到物体上,通过获取投影图像和物体表面的形变信息来计算距离。
【4】.双目测距原理
【5】.双目相机测距原理 1.视差测量;2.立体重建;3.距离测量;4.精度提高。
【6】.一种单目视觉测距方法
【7】.单目测距双目测距杂篇
【8】.OpenCV与AI深度学习 | 使用单相机对已知物体进行3D位置估计 在计算机视觉中,有很多方法可以找到物体的3D位置,例如使用立体摄像头、激光雷达、雷达等。但有时仅用单个摄像头就可以实现3D感知。使用单相机找到3D位置的一个条件是,需要知道图片中需要估计位置的物体的大小。请记住,当对象的方向发生变化时,图片中的对象可能会具有不同的大小。在本文中,为了避免这种需要我们了解对象方向的复杂性,我们将尝试估计球的 3D 位置。因为一个球,无论从任何方向看,都具有相同的大小。
【9】.基于图像识别的物体位移测量方法、装置及系统

2.相机标定

相机标定就是通过代码,获取相机的焦距。将摄像机固定位置,在一米远处固定另外一个黑底的黑板,上面放一张白色的A4纸,然后拍摄照片,类似于下图。为了测试方便,还可以使用不同的已知距离,分别拍摄不同的三张照片,最后进行验证。我综合了 Python-OpenCV的单目视觉测距 (这里的代码和我修改的代码差不多,这里有中文代码注释。)和 单目摄像机测距(python+opencv) (这里有两种方法,一种通过固定位置拍摄,一种是通过摄像头拍摄,这里还结合了人像检测的代码)两篇文章,些了下面的代码。

  • read_photo
    固定摄像头和 A4 纸的距离为 50cm 拍摄一张照片,然后执行 read_photo函数,左上角显示摄像机焦距。

  • read_camera
    固定 A4 纸,移动摄像头到距离 A4 纸50厘米的地方,这个时候左上角显示的就是摄像机的焦距。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#!usr/bin/python
# -*- coding: utf-8 -*-

#import the necessary packages
import numpy as np
import cv2
import imutils

# 找到目标函数
def find_marker(image):
# convert the image to grayscale, blur it, and detect edges
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(gray, 35, 125)

# 显示灰度图
# cv2.imshow("image", edged)
# cv2.waitKey(0)
#
# find the contours in the edged image and keep the largest one;
# we'll assume that this is our piece of paper in the image
ret, binary = cv2.threshold(edged, 127, 255, 0) # 扫描不到纸张轮廓时,要更改阈值,直到方框紧密框住纸张
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
binary = cv2.dilate(binary, kernel, iterations=2) # 形态学膨胀
cnts, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 求最大面积
c = max(cnts, key = cv2.contourArea)
# compute the bounding box of the of the paper region and return it
# cv2.minAreaRect() c代表点集,返回rect[0]是最小外接矩形中心点坐标,
# rect[1][0]是width,rect[1][1]是height,rect[2]是角度
return cv2.minAreaRect(c)

# 距离计算函数
def distance_to_camera(knownWidth, focalLength, perWidth):
# compute and return the distance from the maker to the camera
return (knownWidth * focalLength) / perWidth

# 显示轮廓
def draw_contours(image,marker):
# draw a bounding box around the image and display it
box = cv2.cv.BoxPoints(marker) if imutils.is_cv2() else cv2.boxPoints(marker)
box = np.intp(box)
cv2.drawContours(image, [box], -1, (0, 255, 0), 2)

# 显示距离
def draw_distance(image,marker):
inches = distance_to_camera(KNOWN_WIDTH, focalLength, marker[1][0])
# draw a bounding box around the image and display it
box = cv2.cv.BoxPoints(marker) if imutils.is_cv2() else cv2.boxPoints(marker)
box = np.intp(box)
cv2.drawContours(image, [box], -1, (0, 255, 0), 2)
cv2.putText(image, "%.2fcm" % (inches * 2.54),
(image.shape[1] - 350, image.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX,
2.0, (0, 255, 0), 3)
# cv2.imshow("image", image)
# cv2.waitKey(0)

# 计算焦距
def calculate_focalDistance(image,marker):
# 得到最小外接矩形的中心点坐标,长宽,旋转角度
# 其中marker[1][0]是该矩形的宽度,单位为像素
focalLength = (marker[1][0] * KNOWN_DISTANCE / 2.54) / KNOWN_WIDTH
cv2.putText(image, "%.2f" % (focalLength),
(20 , 70), cv2.FONT_HERSHEY_SIMPLEX,
2.0, (0, 255, 0), 3)
# 将计算得到的焦距打印出来
return focalLength

# 通过固定照片标定
def read_photo():
# 图片路径
img_path = "img/pic_1.jpg"
# load the image, find the marker in the image, then compute the
# distance to the marker from the camera
image = cv2.imread(img_path)
# 获取屏幕尺寸
screen_width, screen_height = 1920, 1080 # 替换成实际屏幕的尺寸
# 计算缩放比例
scale = min(screen_width / image.shape[1], screen_height / image.shape[0])
# 缩放图像
image = cv2.resize(image, None, fx=scale, fy=scale)
# 获取目标
marker = find_marker(image)
# 显示轮廓
draw_contours(image,marker)
# 显示摄像头焦矩
calculate_focalDistance(image,marker)
# # # 显示距离
# draw_distance(image,marker)
# 显示图片
cv2.imshow("image", image)
cv2.waitKey(0)

# 通过摄像头直接标定
def read_camera():
cap = cv2.VideoCapture(0)
while(1):
# get a frame
ret, image = cap.read()
if not ret:
print("无法打开摄像头")
break
# 获取目标
marker = find_marker(image)
# 显示轮廓
draw_contours(image,marker)
# 显示距离
draw_distance(image,marker)
# 显示摄像头焦矩
calculate_focalDistance(image,marker)
cv2.imshow("capture", image)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()

if __name__ == "__main__":
# A4纸的长和宽(单位:cm)
KNOWN_WIDTH = 29.7
KNOWN_HEIGHT = 21.0
# 已知距离(单位:cm)
# KNOWN_DISTANCE = 11.81 # 30 cm
# KNOWN_DISTANCE = 15.74 # 40 cm
KNOWN_DISTANCE = 50 # 50 cm
# 焦距
focalLength = 0
# 通过摄像头标定
# read_photo()

# 通过摄像头标定
read_camera()
参考文章:
【1】.YOLOv5+单目测距(python) 这篇文章和下面一篇文章比较相似。
【2】.YOLO 单目测距:原理、方法与代码 1.使用摄像机采集道路前方的图像;2.在道路区域对物体进行检测,通过矩形框将物体形状框出来。3.
结合矩形框信息,找到该矩形框底边的两个像平面坐标,分别记为(u1,v1)和(u2,v2);4.使用几何关系推导法,由像平面坐标点(u1, v1)、(u2, v2)推导出道路平面坐标(x1,y1)、(x2, y2);(投影到地面上,z轴为0);5.通过欧氏距离公式计算出d。
【3】.单目测距原理与实现(代码可运行) 从单目测距,到yolov7单目测距,还有深度检测到改进。这里提到了一个onocular_Distance_Velocity_Detect 进行识别和标定距离的例子。缩写的代码和我上面的不太一致。
【4】.YOLOv7+单目测距(python) 1.相关配置。2.测距原理。3.相机标定。4.相机测距。
【5】.[OpenCV实战]38 基于OpenCV的相机标定 这里用一个棋盘进行了相机的标定,包括 c++ 和 python 代码,通过多张照片的形式,获取到了相机的参数。

3.人员检测

1

3.误差

如果相机和被测物体不在同一水平线上,有一定的角度,这个该如何处理误差呢?

无人机

针对摄像机的单目测距,误差可能会小一点,但是对于无人机这种运动的物体,那么测量的距离还有角度,就可能不准了。

小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。