我从2019年3月份开始学习python,在有一定的基础后,我看到学校有一个物理实验竞赛:北京联合大学第十二届物理实验竞赛,其中有一个题目是空间定位,即利用物理原理,自行搭建实验装置,实现物体的空间定位。
在有一定编程能力的基础之上,我在想,能不能用python写出一个程序,用来检测摄像头到目标物体的距离.我暂且把我的想法起了一个名字,叫: “鸿鹄”空间定位仪.
近年来,深度学习(DeepLearning)备受热捧,但是,在我看来,为了达到很好的精度,DeepLearning需要大数据支撑。由于深度学习中图模型的复杂化导致算法的时间复杂度急剧提升,为了保证算法的实时性,需要更高的并行编程技巧和更多更好的硬件支持,作为普通程序员,只能望而却步.
于是,我把目光转向了opencv.我的想法是:通过ip摄像头连接个人电脑,从而实现在移动端上的识别,在pc端识别该设备与某个物体的相对距离,即判断某个物体的空间位置。该实验类似于目前的倒车雷达判断距离,其区别是,目前的倒车雷达使用超声波进行回声定位,而“鸿鹄”空间定位仪则通过逐帧分析,实时判定距离,能瞬时输出检测物体的空间相对位置,且计算速度更高。
我在网上看了几篇关于摄像头标定和摄像头焦距等原理的文章,然后通过这篇文章真正启发了我:单目摄像机测距(python+opencv)主要的测距的原理是利用相似三角形计算物体到相机的距离。
在开始之前,先讲讲我遇到的问题吧,希望大家在做的时候也注意一下这个问题.我使用的环境是: Python 3.7.1+ opencv-python(3.4.7+contrib).在安装opencv库时,如果直接pip install opencv-python会出现报错的情况,这个时候,需要安装与python版本对应的库!在命令行里输入python即可查看python的版本;
opencv在这个网站上下载: https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml,那么如何找到与python版本一致的opencv版本呢?
举个例子: opencv_python‑3.4.2‑cp37‑cp37m‑win_amd64.whl(cp37指的是python的版本,win_amd64是指python是64位的,也有可能有人64位的系统装了32位的python,这时候就需要装win32的版本).下载好以后,再用pip安装这个文件即可.
除此之外,还需要安装一个小小的资源库:numpy.简单来说,这是一个处理矩阵的库.安装这个库只需要常规操作即可.
我们先导入一下两个必要的资源库,当pycharm未提示错误时,则说明这两个库已成功安装:
import numpy as np
import cv2
在编写代码前,必要的理论知识要了解一下:
我们将使用相似三角形来计算摄像头到一个已知的物体或者目标的距离。
相信大家在初高中都学过:三角分别相等,三边成比例的两个三角形叫做相似三角形.在本实验中,相似三角形就是这么一回事:假设我们有一个宽度为 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 英尺。
从以上的解释中,我们可以看到,要想得到距离,我们就要知道摄像头的焦距和目标物体的尺寸大小,这两个已知条件根据公式: D’ = (W x F) / P得出目标到摄像机的距离D,其中P是指像素距离,W是A4纸的宽度,F是摄像机焦距。
好了,那么现在我们可以开始编写代码了!在这里,为了便于初学者学习,我的注释可能会显得有些繁杂,在日常写代码时,希望大家有一个良好编程习惯:注释言简意赅就行,不必长篇大论.
# 找到目标函数
def find_marker(image):
# convert the image to grayscale, blur it, and detect edges
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # cvtcolor()函数是一个颜色空间转换函数,可以实现RGB颜色向HSV,HSI等颜色空间转换。也可以转换为灰度图。
gray = cv2.GaussianBlur(gray, (5, 5),0) # 高斯模糊滤波器.低通滤波器是在像素与周围像素的亮度差值小于一个特定值时,平滑该像素的亮度。主要用于去噪和模糊化,如高斯模糊是最常用的模糊滤波器,是一个削弱高频信号强度的低通滤波器。
edged = cv2.Canny(gray, 35, 125) # 采用 Canny 算法做边缘检测
# 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
# 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
我们把标定好的图片与.py文件放在同一个文件夹下:
KNOWN_DISTANCE = 34.64
# initialize the known object width, which in this case, the piece of
# paper is 11 inches wide
# A4纸的长和宽(单位:inches)
KNOWN_WIDTH = 11.8
KNOWN_HEIGHT = 8.27
# initialize the list of images that we'll be using
IMAGE_PATHS = ["images/88cm.jpg", "images/70cm.jpg", "images/60cm.jpg"]
# 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]) # 使用imread()读取图像地址:从硬盘读取第一张图,——我们将用这张图来作为标定图片
marker = find_marker(image) # 计算图中 A4 纸的轮廓信息
focalLength = (marker[1][0] * KNOWN_DISTANCE) / KNOWN_WIDTH # 使用三角形相似法计算出 focalLength(焦距)
print('focalLength = ', focalLength)
为了让“鸿鹄”空间定位仪有更广的应用,我使用电脑调用IP摄像头,从而实现了可以在移动端实时检测的操作,该操作可以延伸到无人机,智能小车等需要测距和测速的领域,打开GPS即可查看经纬度信息以及速度.调用IP摄像头的具体代码如下:
import cv2
if __name__ == '__main__':
cv2.namedWindow("camera1",1)
#开启ip摄像头
video1=http://admin:[email protected]:8081
#此处@后的ipv4 地址需要修改为自己的地址
capture1 =cv2.VideoCapture(video1)
while(capture1.isOpened()):
success1,img = capture1.read()
cv2.imshow("camera1",img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
capture1.release()
cv2.destroyAllWindows("camera1")
现在到最为关键的一步了,为了保证代码的运行效果,我在测试时加了一句代码:
try:
pass
except Exception as e:
print(e)
通过这段代码,我们可以让程序在运行的过程中不报错,对于代码数量大的程序来说,是很有必要的.
以下是我在制作“鸿鹄”空间定位仪的花絮:
为了便于演示“鸿鹄”空间定位仪的效果,我做了一个视频,大家可以进入链接查看:“鸿鹄”空间定位仪