(OpenCV)图像目标尺寸检测
Posted 明柳梦少
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(OpenCV)图像目标尺寸检测相关的知识,希望对你有一定的参考价值。
https://www.pyimagesearch.com/2016/03/28/measuring-size-of-objects-in-an-image-with-opencv/
有那么很长一段时间,PyImage Search博客上的小伙伴都在强烈呼吁有关图像目标尺寸检测的博客文档,好了,你们如愿以偿了,我也很高兴能在PyImage Search博客上发布这篇文章并与你分享它。
关于图像目标检测我写成了三部分,今天介绍的内容选自我所写的第二部分,主要是关于测量图像中物体大小和计算它们之间的距离。
上周,我们学习了一项重要的技术:如何可靠有效地让旋转的边界框在左上角、右上角、右下角和左下角排列。今天,我们将利用这种技术来帮助我们计算图像中物体的大小。一定要通读整篇文章,看看它是怎么做的!
图像目标尺寸检测类似于计算从我们的相机到一个物体的距离——在这两种情况下,我们都需要事先定义一个比率来测量每个给定度量单位的像素数(pixels_per_metric)。在这里所说的这个被称为“pixels_per_metric”的比率指标,我在接下来的部分中对其更正式的定义。
pixels_per_metric
为了确定图像中物体的大小,我们首先需要使用一个参照物作为“校准”点。我们的参照物应该有两个重要的属性:
1、我们应该知道这个物体的真实尺寸(在宽度或高度上的毫米或英寸等值的大小)。
2、我们应该能够轻松地在图片中找到这个参照物,要么基于参照物的位置(如,参照物可以是一副图像中左上角的物体)或基于参照物的外表(例如参照物可以是图片中具有最独特的颜色或独一无二的形状,不同于所有其他的物体)。一句话而言:在任何一种情况下,我们的参照物都应该是以某种方式进行唯一可识别的The One。
在第1个例子中,我们将使用美分硬币作为我们的参照物,正如你阅读后续其他例子时所看到的,我总是选择图像中最左侧的物体作为参照物。
图1:我们将使用一个美分硬币作为我们的参照物,并确保它总是被放置在图像中最左边,这使得我们可以通过对它们位置的轮廓大小进行排序,进一步来提取信息。
通过保证这个美分硬币是最左边的物体后,我们可以从左到右对我们的物体等高线区域进行排列,抓住这个硬币(它将始终对应于排序列表中的第一个等高线区域),并使用它来定义我们的pixels_per_metric比率,我们将其定义为:
pixels_per_metric =物体像素宽 / 物体真实宽
美分硬币的真实宽度是0.955英寸。现在,假设我们图像中硬币的像素宽为150像素(基于它的相关边界框)。那么这种情况下pixels_per_metric这样计算:
pixels_per_metric = 150px / 0.955in = 157px
因此,在我们这幅图像中,每英寸大约有157个像素。有了这个比率,我们可以计算图像中其他物体的大小了。
利用计算机视觉测量物体大小
既然我们已经理解了pixels_per_metric,我们就可以实现用于测量图像中对象大小的Python程序脚本了。
打开一个新的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
另外如果你已经安装过这个imutils包,也请确保它为最新版本,在我这里所使用最新的版本是0.3.6。
$ pip install --upgrade imutils
第10行和第11行定义了一个midpoint函数,顾名思义,它用于计算两个(x,y)坐标之间的中点。
然后我们在第14-19行中解析我们的命令行参数。我们需要两个参数,—image,它是我们输入图像的路径,其中包含我们想要测量的对象,—width,也就是我们的参照物的宽度(英寸),—image路径图像中所认定的那个最左边的物体。
我们现在可以加载我们的图像并对其进行预处理:
# 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 = cnts[0] if imutils.is_cv2() else cnts[1]
# 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行时,对pixelsPerMetric值进行初始化。
下一步是对每一个等高线区域值大小进行检查校验。
# 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函数是针对于opencv2.4版本,而cv2.BoxPoints函数是针对于OpenCV 3版本。
然后我们将旋转的边界框坐标按顺序排列在左上角,右上角,右下角,左下角,正如上周的博客文章(第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行在我们的图像上画出蓝色的中点,然后将各中间点用紫色线连接起来。
接下来,我们需要通过查看我们的参照物来初始化pixelsPerMetric变量:
# 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变量将包含高度距离(以像素为单位),而dB将保持我们的宽度距离。然后,我们在第96行进行检查,看看我们的pixelsPerMetric变量是否已经被初始化了,如果没有,我们将dB除以我们提供的宽度,从而得到每英寸的(近似)像素。
现在我们已经定义了pixelsPerMetric变量,我们可以测量图像中各物体的大小:# 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行计算物体的尺寸(英寸),方法是通过pixelsper度量值划分各自的欧几里得距离(参见上面的“pixels_per_metric ”一节,以获得关于这个比率如何工作的更多信息)。 第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.8in x 0.8in。 然而,并不是所有的结果都是完美的。 两个游戏男孩的墨盒被报道有稍微不同的尺寸(实际上它们的尺寸是一样的)。两个镍币的尺寸也下降了0.1英寸。 这是为什么?为什么物体的测量不是百分之百准确的?
有两个原因:
首先,我匆忙用我的iPhone拍了这张照片。这个角度当然不是一个完美的90度角“向下看”(就像鸟的眼睛一样)在物体上。如果没有一个完美的90度视图(或者尽可能接近它),物体的尺寸就会被扭曲。
其次,我没有使用相机的内在和外在参数来校准我的iPhone。如果不确定这些参数,照片就会倾向于径向和切向镜头失真。执行一个额外的校准步骤来发现这些参数可以“不扭曲”我们的图像,并导致更好的对象大小近似(但是我将把关于失真校正的讨论作为未来博客文章的主题)。
换句话说,在拍摄对象的照片时,尽量接近90度的视角——这将有助于提高对物体大小估计的准确性。
让我们看第二个测量物体大小的例子,这次测量的是药片的尺寸:
python object_size.py --image images/example_02.png --width 0.955
在美国,几乎有一半的处方药物是圆的和/或白色的,因此,如果我们能根据他们的测量结果来过滤药片,我们就有更好的机会准确识别药物。
最后,我们有一个最后的例子,这次用的是3.5in x 2in 的名片来测量两个黑胶唱片和一个信封的大小:
同样,结果也不是很完美,但这是由于原因(1)的视角和原因(2)中镜头失真,正如上述分析时所提到的。
在这篇博客文章中,我们学习了如何使用Python和OpenCV来测量图像中物体的大小。 就像我们在测量从相机到物体的距离的教程一样,我们需要确定我们的“”比率,它描述了如何将物体的真实尺寸大小(如英寸、毫米、米等)对应图片中像素的大小(像素)。
为了计算这个比率,我们需要一个具有两个重要属性的参考对象:
属性1:参照物的宽度或高度对应的(英寸、毫米等单位)
属性2:参照物在图中很容易找到,无论是在对象的位置还是在外观上。
如果这两个属性都能被满足,您可以利用您的参照物来校准您的pixelspermetric变量,然后从那里计算图像中其他物体的大小。
翻译能力有限,如有费解之处请多谅解。如需图片、代码,请看原文链接并给原文作者留下邮箱:
https://www.pyimagesearch.com/2016/03/28/measuring-size-of-objects-in-an-image-with-opencv/
以上是关于(OpenCV)图像目标尺寸检测的主要内容,如果未能解决你的问题,请参考以下文章
在 Python 多处理进程中运行较慢的 OpenCV 代码片段
pyhton—opencv直线检测(HoughLines)找到最长的一条线