首先,您需要知道类似于比例的概念称为每度量比的像素(pixels per metric ratio)。
近似含义是每个单位指标中包含的像素数。例如,图表上的1厘米包含100张图像。
实际上,相当于引用对象的作用,例如已知地图上的引用材质,我们可以使用此引用对象将其转换为地图上其他对象的大小。
引用对象需要具有两个重要属性:
我们应该能够轻松地在图像中找到参考对象,无论是基于对象的位置(例如,参考对象总是放置在图像的左上角)还是通过外观(例如,独特的颜色或形状,与图像不同)其他物品)。在任何一种情况下,我们的参考应该以某种方式唯一可识别。
在下面的示例中,我们将使用美国硬币作为参考对象。在所有示例中,确保它始终是图像中最左侧的对象。
我们将使用美国四分之一作为参考对象,并确保它始终作为图像中最左侧的对象放置,使我们可以通过基于其位置对轮廓进行排序来轻松提取它。
通过保证四分之一是最左边的对象,我们可以从左到右对对象轮廓进行排序,抓住四分之一(它将始终是排序列表中的第一个轮廓),并使用它来定义 pixel_per_metric,我们定义为:
pixels_per_metric = object_width / know_width
美国四分之一的 已知宽度为0.955英寸。现在,假设我们的 object_width(以像素为单位)计算为150像素宽(基于其关联的边界框)。
因此 pixels_per_metric是:
pixels_per_metric = 150px / 0.955in = 157px
因此暗示在我们的图像中每0.955英寸大约有157个像素。使用此比率,我们可以计算图像中对象的大小。
用计算机视觉测量物体的大小
现在我们了解“每度量像素数”比率,我们可以实现用于测量图像中对象大小的Python驱动程序脚本。
打开一个新文件,将其命名为 object_size 。py ,并插入以下代码:
# import the necessary packages
from scipy.spatial import distance as dist
from imutils import perspective
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
def midpoint(ptA, ptB):
return ((ptA[0] + ptB[0]) * 0.5, (ptA[1] + ptB[1]) * 0.5)
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument('-i', '--image', required=True,
help='path to the input image')
ap.add_argument('-w', '--width', type=float, required=True,
help='width of the left-most object in the image (in inches)')
args = vars(ap.parse_args())
第2-8行导入我们所需的Python包。我们将在此示例中大量使用imutils包,因此如果您没有安装它,请确保在继续之前安装它:
$ pip install imutils
请确保您拥有最新版本
pip install --upgrade imutils
第10行和第11行定义了一个称为中点的辅助方法 ,顾名思义,它用于计算两组(x,y)坐标之间 的中点。
然后我们在第14-19行解析命令行参数 。我们需要两个参数, - image ,它是包含我们想要测量的对象的输入图像的路径,以及 - width ,它是我们的参考对象的宽度(以英寸为单位),被认为是最左边的我们的形象 。
我们现在可以加载我们的图像并预处理它:
# load the image, convert it to grayscale, and blur it slightly
image = cv2.imread(args['image'])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
# perform edge detection, then perform a dilation + erosion to
# close gaps in between object edges
edged = cv2.Canny(gray, 50, 100)
edged = cv2.dilate(edged, None, iterations=1)
edged = cv2.erode(edged, None, iterations=1)
# find contours in the edge map
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# sort the contours from left-to-right and initialize the
# 'pixels per metric' calibration variable
(cnts, _) = contours.sort_contours(cnts)
pixelsPerMetric = None
第22-24行从磁盘加载我们的图像,将其转换为灰度,然后使用高斯滤波器对其进行平滑处理。然后,我们进行边缘检测以及扩张+侵蚀,以封闭边缘图中边缘之间的任何间隙(第28-30行)。
第33-35行找到与我们的边缘图中的对象相对应的轮廓(即轮廓)。
然后在第39行从左到右(允许我们提取参考对象)对这些轮廓进行排序 。我们还在第40行初始化 pixelPerMetric 值 。
下一步是检查每个轮廓:
# loop over the contours individually
for c in cnts:
# if the contour is not sufficiently large, ignore it
if cv2.contourArea(c) < 100:
continue
# compute the rotated bounding box of the contour
orig = image.copy()
box = cv2.minAreaRect(c)
box = cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box)
box = np.array(box, dtype='int')
# order the points in the contour such that they appear
# in top-left, top-right, bottom-right, and bottom-left
# order, then draw the outline of the rotated bounding
# box
box = perspective.order_points(box)
cv2.drawContours(orig, [box.astype('int')], -1, (0, 255, 0), 2)
# loop over the original points and draw them
for (x, y) in box:
cv2.circle(orig, (int(x), int(y)), 5, (0, 0, 255), -1)
在 第43行,我们开始在每个轮廓上循环。如果轮廓不够大,我们丢弃该区域,假设它是边缘检测过程遗留的噪声(第45和46行)。
如果轮廓区域足够大,我们计算第50-52行上图像的旋转边界框 ,特别注意使用 cv2 。cv 。BoxPoints 功能适用于OpenCV 2.4和 cv2 。openCV 3的boxPoints方法。
然后,我们将旋转的边界框 坐标排列 在左上角,右上角,右下角和左下角,如上周的博文 (第58行)所述。
最后, 第59-63行以绿色绘制对象 的轮廓,然后以小的红色圆圈绘制边界框矩形的顶点。
现在我们已经订购了边界框,我们可以计算出一系列中点:
# unpack the ordered bounding box, then compute the midpoint
# between the top-left and top-right coordinates, followed by
# the midpoint between bottom-left and bottom-right coordinates
(tl, tr, br, bl) = box
(tltrX, tltrY) = midpoint(tl, tr)
(blbrX, blbrY) = midpoint(bl, br)
# compute the midpoint between the top-left and top-right points,
# followed by the midpoint between the top-righ and bottom-right
(tlblX, tlblY) = midpoint(tl, bl)
(trbrX, trbrY) = midpoint(tr, br)
# draw the midpoints on the image
cv2.circle(orig, (int(tltrX), int(tltrY)), 5, (255, 0, 0), -1)
cv2.circle(orig, (int(blbrX), int(blbrY)), 5, (255, 0, 0), -1)
cv2.circle(orig, (int(tlblX), int(tlblY)), 5, (255, 0, 0), -1)
cv2.circle(orig, (int(trbrX), int(trbrY)), 5, (255, 0, 0), -1)
# draw lines between the midpoints
cv2.line(orig, (int(tltrX), int(tltrY)), (int(blbrX), int(blbrY)),
(255, 0, 255), 2)
cv2.line(orig, (int(tlblX), int(tlblY)), (int(trbrX), int(trbrY)),
(255, 0, 255), 2)
第68-70行打开我们订购的边界框,然后计算左上角和右上角之间的中点,然后是右下角之间的中点。
我们还将分别计算左上角+左下角和右上角+右下角之间的中点(第74和75行)。
第78-81行在我们的图像上绘制 蓝色中点 ,然后用紫色线连接中点 。
接下来,我们需要 通过调查我们的引用对象来初始化 pixelPerMetric变量:
# compute the Euclidean distance between the midpoints
dA = dist.euclidean((tltrX, tltrY), (blbrX, blbrY))
dB = dist.euclidean((tlblX, tlblY), (trbrX, trbrY))
# if the pixels per metric has not been initialized, then
# compute it as the ratio of pixels to supplied metric
# (in this case, inches)
if pixelsPerMetric is None:
pixelsPerMetric = dB / args['width']
首先,我们计算我们的中点集之间的欧几里德距离(第90和91行)。该 DA 变量将包含 高度距离(以像素为单位),而 分贝 将保存我们的 宽度的距离。
然后,我们就检查 96号线,看看我们的 pixelsPerMetric 变量已经初始化,如果没有,我们把 分贝 由我们提供 - 宽度 ,从而使我们每英寸我们的(近似)的像素。
现在我们 已经定义了 pixelPerMetric变量,我们可以测量图像中对象的大小:
# compute the size of the object
dimA = dA / pixelsPerMetric
dimB = dB / pixelsPerMetric
# draw the object sizes on the image
cv2.putText(orig, '{:.1f}in'.format(dimA),
(int(tltrX - 15), int(tltrY - 10)), cv2.FONT_HERSHEY_SIMPLEX,
0.65, (255, 255, 255), 2)
cv2.putText(orig, '{:.1f}in'.format(dimB),
(int(trbrX + 10), int(trbrY)), cv2.FONT_HERSHEY_SIMPLEX,
0.65, (255, 255, 255), 2)
# show the output image
cv2.imshow('Image', orig)
cv2.waitKey(0)
第100行和第101行通过将相应的欧几里德距离除以pixelsPerMetric 值来计算对象的尺寸(以英寸为单位) (有关此比率的工作原理的详细信息,请参阅上面的 “每公制像素数”部分)。
第104-109行在图像上绘制对象的尺寸 ,而 第112和113行显示输出结果。
物体尺寸测量结果
测试我们的 object_size 。py 脚本,只需发出以下命令:
$ python object_size.py --image images/example_01.png --width 0.955
输出应如下所示:
图2:使用OpenCV,Python和计算机视觉+图像处理技术测量图像中对象的大小。
如您所见,我们已成功计算出图像中每个对象的大小 - 我们的名片正确报告为 3.5in x 2in。同样,我们的镍被准确地描述为 0.8英寸x 0.8英寸。
然而,并非我们所有的结果都是 完美的。
据报道,Game Boy墨盒的尺寸略有不同(即使尺寸相同)。两个季度的高度也 减去了0.1英寸。
那么为什么呢?为什么物体测量不是100%准确?
原因有两方面:
首先,我匆匆用iPhone拍了这张照片。角度肯定 不是物体上“俯视”(如鸟瞰图)的完美90度角。如果没有完美的90度视图(或尽可能接近它),对象的尺寸可能会出现扭曲。
其次,我没有使用相机的内在和外在参数来校准我的iPhone。在不确定这些参数的情况下,照片可能容易发生径向和切向镜头失真。执行额外的校准步骤来查找这些参数可以“扭曲”我们的图像并导致更好的对象大小近似(但我将讨论失真校正作为未来博客文章的主题)。
同时,在拍摄物体照片时尽量获得尽可能接近90度的视角 - 这有助于提高物体尺寸估计的准确性。
那就是说,让我们看一下测量物体尺寸的第二个例子,这次测量药丸的尺寸:
在美国,所有20,000多种处方药中有近50%是圆形和/或白色,因此如果我们可以根据它们的测量值来过滤药丸,我们就有更好的机会准确识别药物。
最后,我们有一个最后的例子,这次使用 3.5英寸x 2英寸名片来测量两个乙烯基EP和一个信封的大小:
$ python object_size.py --image images/example_03.png --width 3.5
同样,结果不是很完美,但这是由于(1)视角和(2)透镜畸变,如上所述。
总结
在本文中,我们学习了如何通过使用python和OpenCV来测量图片中的物体的大小。
我们需要确定pixels per metric比率(单位尺寸像素数),即在给定的度量(如英寸、毫米、米等)下,像素的数量。
为了计算这个比率,我们需要一个参考物体,它需要两点重要的性质:
1、参考物体需要有含测量单位(英寸、毫米等等)的尺寸
2、无论从物体的位置还是形状,参考物体都需要容易被找到。
加入上面的性质都能满足,你可以使用参考物体计算pixels per metric比率,并根据这个计算图片中物体的大小。
相关资源关注微信公众号:“图像算法”或者微信搜索imalg_cn 获取
联系客服