在当今技术日益进步的时代,计算机视觉已成为我们生活中不可或缺的一部分。从智能监控到虚拟现实,计算机视觉技术的应用范围日益广泛。在这篇博客中,我们将探索一个特别实用的计算机视觉案例:使用OpenCV实现摄像头测距。这一技术不仅对专业人士有用,也为编程爱好者和技术创新者提供了广泛的应用可能性。
OpenCV,作为一个功能强大的开源计算机视觉库,已经在全球范围内被广泛应用于各种项目和研究中。其中,摄像头测距是一个极具挑战性且实用的话题。本文将介绍如何利用OpenCV结合相似三角形原理来实现精确的距离测量。无论你是计算机视觉的新手还是有经验的开发者,这篇文章都将为你提供一种新的视角来理解和应用这一领域的基本概念。我们将从相似三角形的基本原理出发,逐步深入,展示如何在实际项目中应用这些知识来实现摄像头测距的功能。。
使用相似三角形计算物体到相机的距离
在使用相似三角形方法计算摄像头测距时,我们关注的是在不同距离下,同一个物体在相机视野中所占的像素大小变化。这个方法基于一个简单的几何原理:当我们知道某个距离下物体的实际大小和在图像中的大小,就可以推算出在其他距离下的参数。
举一个实际例子,我们可以将此过程的单位转换为厘米来更好地适应中国常用的度量标准。假设我们已知一个物体的真实宽度是 W 厘米,然后将其放置在距离相机 D 厘米的地方进行拍照。在照片中测量该物体的像素宽度为 P 像素,那么可以通过以下公式计算相机的焦距 F(单位为像素):
F = ( P ⋅ D ) / W F = (P \cdot D) / W F=(P⋅D)/W
以实际数据为例,假设我们在距离相机 60.96厘米(大约等于24英寸)的地方放置了一张纸,其宽度为 27.94厘米(大约等于11英寸)。通过图像处理,我们测得照片中纸的像素宽度为 248 像素。那么,焦距 F 计算如下:
F = ( 248 px ⋅ 60.96 cm ) / 27.94 cm ≈ 543.45 px F = (248 \text{px} \cdot 60.96 \text{cm}) / 27.94 \text{cm} ≈ 543.45 \text{px} F=(248px⋅60.96cm)/27.94cm≈543.45px
现在,无论我们将相机移动得更近或更远,都可以使用相似三角形的原理来计算物体到相机的距离 D’:
D ′ = ( W ⋅ F ) / P D' = (W \cdot F) / P D′=(W⋅F)/P
其中,W 是物体的实际宽度(厘米),F 是之前计算得到的焦距(像素),P 是新位置下物体在照片中的宽度(像素)。通过这种方式,我们就能够计算出物体在不同距离下到相机的距离,从而实现精准的测距。
首先,我们需要导入必要的库,并定义一个find_marker
函数来定位图像中我们想要测量距离的物体。在这个例子中,我们以一张约A4纸作为目标物体。下面是找到这个目标物体的过程:
from imutils import paths
import numpy as np
import imutils
import cv2
# 用于识别要计算距离的物体
def find_marker(image):
# 将图像转换为灰度图,然后进行模糊处理,以去除图像中的高频噪声
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
# 使用 Canny 算法进行边缘检测
edged = cv2.Canny(gray, 35, 125)
# 寻找边缘图像中的轮廓,保留最大的一个,假设这是我们图像中的纸张
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
c = max(cnts, key=cv2.contourArea)
# 计算纸张区域的边界框,并返回
return cv2.minAreaRect(c)
我们的目标是使用 OpenCV 来识别图像中的物体(例如一张纸),并计算它与摄像头之间的距离。首先,我们需要定义一个函数 find_marker
,用于在图片中找到我们要计算距离的目标物体。在这个例子中,我们使用一张 8.5 x 11 英寸的纸作为目标物体。
# 导入必要的包
from imutils import paths
import numpy as np
import imutils
import cv2
# 将图像转换为灰度图,然后进行模糊处理,以减少噪声,最后进行边缘检测
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(gray, 35, 125)
在这几行代码中,我们首先将图像转换为灰度图,这是因为灰度图处理起来更加高效,且对于边缘检测来说足够了。然后,我们使用高斯模糊来降低图像的高频噪声,这有助于后续的边缘检测。最后,我们使用 Canny 算法来检测图像中的边缘。
# 在边缘检测后的图像中找到轮廓,保留最大的那个轮廓
# 我们假设最大的轮廓就是我们的目标物体(比如纸)
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
c = max(cnts, key=cv2.contourArea)
# 计算物体轮廓的边界框,并返回
return cv2.minAreaRect(c)
这一部分代码用于找出图像中的所有轮廓,然后选择面积最大的轮廓作为目标物体的轮廓。我们使用 cv2.findContours
函数来检测轮廓,并通过 cv2.contourArea
函数找出最大的轮廓。cv2.minAreaRect
函数则用于计算该轮廓的最小面积矩形,这个矩形包含了轮廓的 (x, y)
坐标以及像素长度和宽度。
from imutils import paths
import numpy as np
import imutils
import cv2
# 用于识别要计算距离的物体
def find_marker(image):
# 将图像转换为灰度图,然后进行模糊处理,以去除图像中的高频噪声
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
# 使用 Canny 算法进行边缘检测
edged = cv2.Canny(gray, 35, 125)
# 寻找边缘图像中的轮廓,保留最大的一个,假设这是我们图像中的纸张
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
c = max(cnts, key=cv2.contourArea)
# 计算纸张区域的边界框,并返回
return cv2.minAreaRect(c)
阶段总结
cv2.minAreaRect
函数被用来计算轮廓的最小区域矩形。这个矩形可以用来估计物体的大小和它在图像中的方向,这是后续计算距离所必需的。接下来,我们将详细探讨如何使用OpenCV和相似三角形原理来计算物体到摄像头的距离。我们会深入每个步骤的技术细节,并解释背后的原理。
相似三角形原理:这是计算距离的核心原理。当两个三角形的相应角相等时,它们的边长成比例。在我们的应用中,我们利用这一点来推算物体到摄像头的距离。
焦距的计算与应用:焦距是摄像头镜头中心到成像平面的距离。通过已知距离和物体尺寸,我们可以计算出焦距。然后,使用这个焦距来计算在其他距离下的物体尺寸。
def distance_to_camera(knownWidth, focalLength, perWidth):
# 计算并返回从物体到摄像头的距离
return (knownWidth * focalLength) / perWidth
这个函数使用三个参数:目标物体的已知宽度(knownWidth
)、计算得到的焦距(focalLength
)和图像中目标物体的像素宽度(perWidth
)。通过相似三角形原理,可以计算出物体到摄像头的距离。
距离计算公式及其解析
函数
distance_to_camera
的核心在于应用相似三角形原理来计算物体到摄像头的距离。这里的计算公式为:D = W × F P D = \frac{W \times F}{P} D=PW×F
其中,D
代表从物体到摄像头的距离,W
是物体的已知宽度(knownWidth
),F
是焦距(focalLength
),P
是图像中物体的像素宽度(perWidth
)。公式解析
已知宽度(W):这是实际物体的宽度,它是我们预先知道的一个固定值,通常用于校准过程。例如,在本例中,如果我们使用一张标准尺寸的纸张,其宽度是一个已知量。
焦距(F):焦距是相机镜头特性的一部分,表示在光学系统中,光线汇聚成清晰图像的特定距离。在标定过程中通过特定的设置(如已知物体距离和尺寸)计算得出。
像素宽度(P):这是通过图像处理技术(如边缘检测)从相机拍摄的图像中测量得到的物体的像素宽度。
应用原理
这个公式基于相似三角形的概念。在两个三角形中,如果角度相同,那么它们的边长比例相同。在这种情况下,一侧是实际物体到相机的距离(我们想要计算的),另一侧是物体在相机成像平面上的像素宽度。通过已知的实际宽度和计算出的焦距,我们可以使用比例关系来找出未知的距离。
# 已知目标物体到摄像头的距离,这里是24英寸
KNOWN_DISTANCE = 24.0
# 已知目标物体的宽度,这里是11英寸
KNOWN_WIDTH = 11.0
# 加载第一张图片,这张图片中的物体距离摄像头已知,然后找到标记,初始化焦距
image = cv2.imread("images/object.jpg")
marker = find_marker(image)
focalLength = (marker[1][0] * KNOWN_DISTANCE) / KNOWN_WIDTH
在这个准备阶段,我们首先确定目标物体到摄像头的已知距离和物体的已知宽度。然后,我们加载一张物体距离摄像头已知的图片,通过 find_marker
函数找到物体的轮廓,并使用这些信息计算焦距。这里的焦距计算基于相似三角形原理,即在已知一定距离下物体在图像中的尺寸,我们可以推算出在其他距离下的尺寸。
# 遍历每张图片
for imagePath in sorted(paths.list_images("images")):
# 加载图片,找到标记,然后计算摄像头到标记的距离
image = cv2.imread(imagePath)
marker = find_marker(image)
inches = distance_to_camera(KNOWN_WIDTH, focalLength, marker[1][0])
# 绘制边界框并展示
box = cv2.cv.BoxPoints(marker) if imutils.is_cv2() else cv2.boxPoints(marker)
box = np.int0(box)
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)
在这个循环中,我们处理每张图片,使用 find_marker
函数找到物体的轮廓,然后调用 distance_to_camera
函数计算物体到摄像头的距离。结果显示在图片上,包括绘制的边界框和距离信息。
import cv2
import numpy as np
import imutils
from imutils import paths
class DistanceCalculator:
def __init__(self, knownWidth, knownDistance):
# 初始化已知物体宽度和距离
self.knownWidth = knownWidth
self.knownDistance = knownDistance
self.focalLength = None
def calibrate(self, calibrationImagePath):
# 使用一张已知距离的图片进行焦距标定
image = cv2.imread(calibrationImagePath)
marker = self.find_marker(image)
self.focalLength = (marker[1][0] * self.knownDistance) / self.knownWidth
def find_marker(self, image):
# 在图像中寻找目标物体的轮廓
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(gray, 35, 125)
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
c = max(cnts, key=cv2.contourArea)
return cv2.minAreaRect(c)
def distance_to_camera(self, perWidth):
# 计算物体到摄像头的距离
if self.focalLength is None:
raise ValueError("Focal length is not calibrated.")
return (self.knownWidth * self.focalLength) / perWidth
def process_image(self, imagePath):
# 处理单张图片,并显示距离
image = cv2.imread(imagePath)
marker = self.find_marker(image)
distance = self.distance_to_camera(marker[1][0])
box = cv2.cv.BoxPoints(marker) if imutils.is_cv2() else cv2.boxPoints(marker)
box = np.int0(box)
cv2.drawContours(image, [box], -1, (0, 255, 0), 2)
cv2.putText(image, "%.2fcm" % (distance * 2.54), # 将英寸转换为厘米
(image.shape[1] - 200, image.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX,
2.0, (0, 255, 0), 3)
cv2.imshow("Distance", image)
cv2.waitKey(0)
# 主程序
if __name__ == "__main__":
KNOWN_DISTANCE = 60.96 # 设置已知距离(厘米)
KNOWN_WIDTH = 27.94 # 设置已知物体宽度(厘米)
calculator = DistanceCalculator(KNOWN_WIDTH, KNOWN_DISTANCE)
calculator.calibrate("images/calibration_image.jpg") # 使用已知距离的图片进行焦距标定
for imagePath in sorted(paths.list_images("images")):
calculator.process_image(imagePath) # 处理每张图片并计算距离
cv2.destroyAllWindows() # 关闭所有OpenCV窗口
请确保你已经安装了C++OpenCV环境。Windows下Visual Studio详情参考可以参考:Windows安装Opencv与VS配置
#include
#include
#include
#include
class DistanceCalculator {
public:
DistanceCalculator(float knownWidth, float knownDistance)
: knownWidth(knownWidth)
, knownDistance(knownDistance)
, focalLength(0.0f)
{}
void calibrate(const cv::Mat& calibrationImage)
{
cv::RotatedRect marker = findMarker(calibrationImage);
focalLength = (marker.size.width * knownDistance) / knownWidth;
}
float distanceToCamera(float perWidth) const
{
if (focalLength == 0.0f)
{
throw std::logic_error("Focal length is not calibrated.");
}
return (knownWidth * focalLength) / perWidth;
}
cv::RotatedRect findMarker(const cv::Mat& image) const
{
cv::Mat gray, blurred, edged;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);
cv::Canny(blurred, edged, 35, 125);
std::vector> contours;
cv::findContours(edged, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
std::sort(contours.begin(), contours.end(),
[](const std::vector& c1, const std::vector& c2)
{
return cv::contourArea(c1) > cv::contourArea(c2);
});
return cv::minAreaRect(contours[0]);
}
private:
float knownWidth;
float knownDistance;
float focalLength;
};
int main()
{
float KNOWN_DISTANCE = 60.96f; // cm
float KNOWN_WIDTH = 27.94f; // cm
DistanceCalculator calculator(KNOWN_WIDTH, KNOWN_DISTANCE);
cv::Mat calibrationImage = cv::imread("calibration_image.jpg");
calculator.calibrate(calibrationImage);
std::vector imagePaths = { "path_to_images/image1.jpg", "path_to_images/image2.jpg", ... };
for (const std::string& path : imagePaths)
{
cv::Mat image = cv::imread(path);
cv::RotatedRect marker = calculator.findMarker(image);
float distance = calculator.distanceToCamera(marker.size.width);
cv::Point2f points[4];
marker.points(points);
for (int i = 0; i < 4; i++)
cv::line(image, points[i], points[(i+1)%4], cv::Scalar(0, 255, 0), 2);
std::string text = std::to_string(distance) + " cm";
cv::putText(image, text, cv::Point(10, image.rows - 20), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 2);
cv::imshow("Distance", image);
cv::waitKey(0);
}
cv::destroyAllWindows();
return 0;
}
当你运行结果会有以下输出
本项目的目标是使用OpenCV实现摄像头测距,即计算照片中目标物体到相机的距离。我们采用了相似三角形的方法,利用物体在不同距离下在相机成像平面上的尺寸变化来推算实际距离。首先,通过图像处理技术识别并提取目标物体的轮廓,然后计算焦距,并利用已知的物体宽度和图像中的像素宽度来计算物体到摄像头的距离。
在代码实现方面,我们首先定义了一个处理图像和计算距离的类 DistanceCalculator
。这个类包含方法来寻找图像中的目标物体轮廓、计算相机焦距、以及最终计算物体到摄像头的距离。
find_marker
方法提取目标物体的轮廓。distance_to_camera
方法计算物体到摄像头的距离。这依赖于焦距、已知物体的实际宽度以及图像中物体的像素宽度。DistanceCalculator
类,使用标定图像进行焦距计算,然后遍历目标图像集合,对每张图像进行处理和距离计算。
__init__
: 构造函数,用于初始化已知的物体宽度和距离。calibrate
: 通过一张已知距离的图片计算焦距。这一步是关键,因为正确的焦距计算对于后续的距离测量至关重要。find_marker
: 在给定的图像中找到目标物体的轮廓。这通过将图像转换为灰度图,应用高斯模糊和Canny边缘检测来完成。distance_to_camera
: 使用计算得到的焦距和图像中物体的像素宽度来计算物体到摄像头的距离。运行逻辑流程图
如果您喜欢我们的文章,请不要忘记点击关注。我们将继续推出更多关于计算机视觉、人工智能、以及C++、Python、Java等技术领域的精彩内容。您的支持是我们不断前进、分享更多知识和见解的最大动力。我们期待与您一起探索这些激动人心的技术领域,共同成长。感谢您的阅读和支持,敬请期待我们的后续文章!