用 Python 和 OpenCV 来测量相机到目标的距离-程序员宅基地

(点击上方蓝字,快速关注我们)


英文:Adrian Rosebrock  编译: 伯乐在线 - G.K. 

http://python.jobbole.com/84378/


几天前,一个叫 Cameron 的 PyImageSearch 读者发来邮件询问摄像头测距的方法。他花了一些时间研究,但是没有找到解决办法。

我很能体会 Cameron 的感受。几年前我做过一个分析棒球离手飞向本垒的运动的小项目。

我通过使用运动分析和基于轨迹的跟踪方法来确定或者估计小球在视频帧中的位置。并且因为棒球的大小是已知的,所以我也能估计出其到本垒的距离。

那是个有趣的项目,虽然系统的精度没有达到我的预期。——棒球运动太快所造成的“运动模糊”让达到高精度变得十分困难。

我的项目完全算是一个个例,但是通常来说,在计算机视觉或者图形处理领域计算从相机到目标的距离实际上是一个非常容易的问题。你可以找到一个像三角形相似这样简单粗暴的方法,或者你也可以用上相机模型的内参这样更复杂一点(但是更精确)的方法。

在这篇博客,我将会告诉大家我和 Cameron 是如果解决这个计算相机到已知物体或目标的距离。

千万要看——你一定不想错过。

OpenCV 和 Python 版本: 这个例子可以在 Python 2.7/Python 3.4+ 和 OpenCV 2.4.X上运行。

用相似三角形计算物体或者目标到相机的距离

我们将使用相似三角形来计算相机到一个已知的物体或者目标的距离。

相似三角形就是这么一回事:假设我们有一个宽度为 W 的目标或者物体。然后我们将这个目标放在距离我们的相机为 D 的位置。我们用相机对物体进行拍照并且测量物体的像素宽度 P 。这样我们就得出了相机焦距的公式:

F = (P x D) / W

举个例子,假设我在离相机距离 D = 24 英寸的地方放一张标准的 8.5 x 11 英寸的 A4 纸(横着放;W = 11)并且拍下一张照片。我测量出照片中 A4 纸的像素宽度为 P = 249 像素。

因此我的焦距 F 是:

F = (248px x 24in) / 11in = 543.45

当我继续将我的相机移动靠近或者离远物体或者目标时,我可以用相似三角形来计算出物体离相机的距离:

D’ = (W x F) / P

为了更具体,我们再举个例子,假设我将相机移到距离目标 3 英尺(或者说 36 英寸)的地方并且拍下上述的 A4 纸。通过自动的图形处理我可以获得图片中 A4 纸的像素距离为 170 像素。将这个代入公式得:

D’ = (11in x 543.45) / 170 = 35 英寸

或者约 36 英寸,合 3 英尺。

注意:当我给这次例子拍照时,我的卷尺有一点松,因此结果造成了大约 1 英寸的误差。还有我也是很快速地拍下了照片并且没有完全对齐卷尺上的脚标,这也会对最终结果的 1 英寸误差产生影响。综上所述,相似三角形的方法还是合理的,你也可以用这个方法很简单地计算出物体或者目标距离你的相机的距离。

现在理解了?

太棒了。接下来让我们用一些代码来看看如何用 Python、OpenCV、图像处理和计算机视觉技术来获得相机到物体或者目标的距离。

用Python和OpenCV来测量相机到目标的距离

继续,我们开始这个项目。打开一个文件,命名为distance_to_camera.py,然后就可以开工了。

# import the necessary packages

import numpy as np

import cv2

 

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)

 

    # 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

    (cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_LIST,        cv2.CHAIN_APPROX_SIMPLE)

    c = max(cnts, key = cv2.contourArea)

 

    # compute the bounding box of the of the paper region and return it

    return cv2.minAreaRect(c)


第一件要做的事情就是导入必要的包。我们将用 NumPy 来进行数值计算和 cv2 来绑定 OpenCV 。

在那之后我们定义 find_marker 函数。这个函数接收一个 image 参数,并且这意味着我们将用它来找出将要计算距离的物体。

在这个例子中我们使用标准的 8.5 x 11 英寸的 A4 纸作为我们的目标。

目前我们的第一个任务是找出图像中的这张纸。

我们先将图像转成灰度图,用高斯模糊除去明显的噪点,并且在第 7-9 行 使用边缘检测。

完成这几步后,我们的图像应该长这样:

如你所见,我们的目标(A4 纸)的边缘已经很清晰了。现在我们只要找出这张纸的轮廓(比如:外形)。

我们用 13 行 的 cv2.findContours 函数找到目标,并且在 14 行 计算出面积最大的轮廓。

我们假设面积最大的轮廓是我们的那张 A4 纸。这个假设在我们的这个例子是成立的,但是实际上在图像中找出目标是和是与应用场景高度相关的。

在我们的例子中,简单的边缘检测和计算最大的轮廓是可行的。我们可以通过使用轮廓近似法使系统更具鲁棒性,排除不包含有4个顶点的轮廓(因为 A4 纸是矩形有四个顶点),然后计算面积最大的四点轮廓。

注意:更多这样的方法见这篇文章,讲述了如何做一个简单粗暴的手机扫描仪。

其他找到图像中目标可选的方法是利用颜色特征(目标的颜色和背景有着明显的不同)。你还可以使用关键点检测,局部不变性描述子,和关键点匹配来寻找目标。但是这些方法以及超出了这篇文章的范畴,并且具有高度定制化的特性。

不管怎样,我们现在获得了目标的轮廓,并且在第 17 行 返回包含 (x, y) 坐标和像素高度和宽度信息的边界框给调用函数。

让我们也快速定义一个用上述的相似三角形法计算距离的函数:

def distance_to_camera(knownWidth, focalLength, perWidth):

# compute and return the distance from the maker to the camera

return (knownWidth * focalLength) / perWidth


这个函数传入目标的 knownWidth ,计算好的 focalLength ,和目标在图像中的像素距离,并且使用上面推导的相似三角形公式来计算到物体的距离


继续读下列代码来看看我们是如何利用这些函数的:


#import the necessary packages

import numpy as np

import cv2

 

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)

 

    # 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

    (cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

    c = max(cnts, key = cv2.contourArea)

 

    # compute the bounding box of the of the paper region and return it

    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

 

# initialize the known distance from the camera to the object, which

# in this case is 24 inches

KNOWN_DISTANCE = 24.0

 

# initialize the known object width, which in this case, the piece of

# paper is 11 inches wide

KNOWN_WIDTH = 11.0

 

# initialize the list of images that we'll be using

IMAGE_PATHS = ["images/2ft.png", "images/3ft.png", "images/4ft.png"]

 

# load the furst image that contains an object that is KNOWN TO BE 2 feet

# from our camera, then find the paper marker in the image, and initialize

# the focal length

image = cv2.imread(IMAGE_PATHS[0])

marker = find_marker(image)

focalLength = (marker[1][0] * KNOWN_DISTANCE) / KNOWN_WIDTH


找到图像中目标的距离的第一步是标定和计算焦距。我们需要知道以下参数:

  • 相机到物体的距离

  • 这个物体的宽度(单位英尺或米)。注意:也可以用高度,这个例子中我们使用宽度。

这里不得不提示一下我们所做的并不是实质意义上的摄像机标定。真正的摄像机标定包括摄像机的内参,你可以从这里获得更多相关知识。

在第 25 行 我们初始化了已知的 KNOWN_DISTANCE ,从相机到物体的距离为 24 英寸。在第 29 行 我们初始了物体的宽度 KNOWN_WIDTH 为 11 英寸(一张横着放的标准 A4 纸)。

然后我们在第 32 行 定义要用到的图片的路径。

下一步比较重要:是一个简单的标定

第 37 行 从硬盘读取第一张图,——我们将用这张图来作为标定图片。

图片加载以后,在第 38 行 计算图中 A4 纸的轮廓信息,在第 39 行 使用三角形相似法计算出 focalLength

由于我们已经“标定”了我们的系统并且获得了 focalLength ,我们可以很容易地计算出相机离接下来图片中目标的距离。

让我们看看这个是这么做的:

41 # loop over the images

42 for imagePath in IMAGE_PATHS:

# load the image, find the marker in the image, then compute the

# distance to the marker from the camera

image = cv2.imread(imagePath)

46 marker = find_marker(image)

47 inches = distance_to_camera(KNOWN_WIDTH, focalLength, marker[1][0])

# draw a bounding box around the image and display it

box = np.int0(cv2.cv.BoxPoints(marker))

cv2.drawContours(image, [box], -1, (0, 255, 0), 2)

cv2.putText(image, "%.2fft" % (inches / 12),

(image.shape[1] - 200, image.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX,

2.0, (0, 255, 0), 3)

cv2.imshow("image", image)

cv2.waitKey(0)


在第 42 行 开始遍历所有的图片路径。

然后,在第 45 行 我们将列表中所有的图片从硬盘读取下来。在第 46 行 提取目标轮廓,并且在第 47 行 计算摄像机到物体的距离。

在第 50-56 行,我们简单地画出目标的边框并且显示出距离。

结果

来看看我们的脚本运作,打开一个终端,导航到你的代码目录,执行以下命令:

$ python distance_to_camera.py


如果一切正常你将会看到 2ft.png 的结果,这张图是用来“标定”我们的系统并且计算初始的 focalLength

从上面的图片我们可以看到我们的焦距被正确地计算出来并且按照代码中的变量 KNOWN_DISTANCE 和 KNOWN_WIDTH,A4 纸的距离是 2 英尺。

现在我们有了焦距,我们可以在接下来的图片中计算出目标的距离:

上上面的例子,我们的相机大概离目标有 3 英尺远。

让我们退后一步:

再次需要注意的是,我在拍这个例子的时候动作很快并且卷尺并没有绷紧。而且,我也没有确保我的相机是百分之百地对准目标底部,因此,这些例子总会有大概 1 英寸的误差。

以上是我要说的,这篇文章描述的三角形相似法仍然可以用,并且能够让你测量出图像上的物体或目标到你相机的距离。

总结

在这篇博客我们学习了如何计算一个图像上的已知物体到相机的距离。

为了完成这个任务我们利用了三角形相似法,并且需要知道两个重要的参数:

1、 目标的实际宽度(或高度),单位可以是英寸或者米。

2、 标定过程 1 中相机到目标的距离。

计算机视觉和图像处理算法可以被用来自动检测图像中物体的像素宽度或高度并且完成相似三角形的计算,得出一个焦距。

然后在接下来的图片中,我们只要提取出目标轮廓就可以利用得到的焦距测量出目标到相机的距离。


看完本文有收获?请转发分享给更多人

关注「Python开发者」,提升Python技能

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/iodjSVf8U1J7KYc/article/details/79070936

智能推荐

在Google使用Borg进行大规模集群的管理 7-8-程序员宅基地

文章浏览阅读606次。为什么80%的码农都做不了架构师?>>> ..._google trace batch job

python加密字符串小写字母循环后错两位_python学习:实现将字符串进行加密-程序员宅基地

文章浏览阅读2.6k次,点赞3次,收藏3次。'''题目描述1、对输入的字符串进行加解密,并输出。2加密方法为:当内容是英文字母时则用该英文字母的后一个字母替换,同时字母变换大小写,如字母a时则替换为B;字母Z时则替换为a;当内容是数字时则把该数字加1,如0替换1,1替换2,9替换0;其他字符不做变化。s'''#-*-coding:utf-8-*-importre#判断是否是字母defisLetter(letter):iflen..._编写函数fun2实现字符串加密,加密规则为:如果是字母,将其进行大小写转换;如果

【Java容器源码】集合应用总结:迭代器&批量操作&线程安全问题_迭代器是否可以保证容器删除和修改安全操作-程序员宅基地

文章浏览阅读4.4k次,点赞6次,收藏8次。下面列出了所有集合的类图:每个接口做的事情非常明确,比如 Serializable,只负责序列化,Cloneable 只负责拷贝,Map 只负责定义 Map 的接口,整个图看起来虽然接口众多,但职责都很清晰;复杂功能通过接口的继承来实现,比如 ArrayList 通过实现了 Serializable、Cloneable、RandomAccess、AbstractList、List 等接口,从而拥有了序列化、拷贝、对数组各种操作定义等各种功能;上述类图只能看见继承的关系,组合的关系还看不出来,比如说_迭代器是否可以保证容器删除和修改安全操作

养老金融:编织中国老龄社会的金色安全网

在科技金融、绿色金融、普惠金融、养老金融、数字金融这“五篇大文章”中,养老金融以其独特的社会价值和深远影响,占据着不可或缺的地位。通过政策引导与市场机制的双重驱动,激发金融机构创新养老服务产品,如推出更多针对不同年龄层、风险偏好的个性化养老金融产品,不仅能提高金融服务的可获得性,还能增强民众对养老规划的主动参与度,从而逐步建立起适应中国国情、满足人民期待的养老金融服务体系。在人口老龄化的全球趋势下,中国养老金融的发展不仅仅是经济议题,更关乎社会的稳定与进步。养老金融:民生之需,国计之重。

iOS 创建开源库时如何使用图片和xib资源

在需要使用图片的地方使用下面的代码,注意xib可以直接设置图片。将相应的图片资源文件放到bundle文件中。

R语言学习笔记9_多元统计分析介绍_r语言多元统计分析-程序员宅基地

文章浏览阅读3.6k次,点赞4次,收藏66次。目录九、多元统计分析介绍九、多元统计分析介绍_r语言多元统计分析

随便推点

基于psk和dpsk的matlab仿真,MATLAB课程设计-基于PSK和DPSK的matlab仿真-程序员宅基地

文章浏览阅读623次。MATLAB课程设计-基于PSK和DPSK的matlab仿真 (41页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦!9.90 积分武汉理工大学MATLAB课程设计.目录摘要 1Abstract 21.设计目的与要求 32.方案的选择 42.1调制部分 42.2解调部分 43.单元电路原理和设计 63.1PCM编码原理及设计 63.1.1PCM编码原理 ..._通信原理课程设计(基于matlab的psk,dpsk仿真)(五篇模版)

腾讯微搭小程序获取微信用户信息_微搭 用微信号登录-程序员宅基地

文章浏览阅读3.5k次,点赞6次,收藏28次。腾讯微搭小程序获取微信用户信息无论你对低代码开发的爱与恨, 微信生态的强大毋庸置疑. 因此熟悉微搭技术还是很有必要的! 在大多数应用中, 都需要获取和跟踪用户信息. 本文就微搭中如何获取和存储用户信息进行详细演示, 因为用户信息的获取和存储是应用的基础.一. 微搭每个微搭平台都宣称使用微搭平台可以简单拖拽即可生成一个应用, 这种说法我认为是"夸大其词". 其实微搭优点大致来说, 前端定义了很多组件, 为开发人员封装组件节省了大量的时间,这是其一; 其二对后端开发来说, 省去了服务器的部署(并没有省去后_微搭 用微信号登录

sql中索引的使用分析

sql中索引的使用分析

termux安装metasploit()-程序员宅基地

文章浏览阅读8.9k次,点赞16次,收藏108次。因为呢,termux作者,不希望让termux变成脚本小子的黑客工具,于是把msf , sqlmap等包删了。至于如何安装metasploit呢。apt update -y && apt upgrade -y #更新升级更新升级之后要安装一个叫 git 的安装包apt install git -y然后我们就开始//这里的话建议把手机放到路由器旁边,保持网络的优良。或者科学上网。//git clone https://github.com/gushmazuko/metaspl_termux安装metasploit

armbian docker Chrome_一起学docker06-docker网络-程序员宅基地

文章浏览阅读141次。一、Docker支持4种网络模式Bridge(默认)--network默认网络,Docker启动后创建一个docker0网桥,默认创建的容器也是添加到这个网桥中;IP地址段是172.17.0.1/16 独立名称空间 docker0桥,虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。host容器不会获得一个独立的network namespace,而是与宿主..._armbian 172.17.0.1

Ansible-Tower安装破解

Ansible-Tower安装破解。

推荐文章

热门文章

相关标签