【Python3-OpenCV】测量图像中物体的大小

这是我参与更文挑战的第27天,活动详情查看: 更文挑战

OpenCV是一个C++库,目前流行的计算机视觉编程库,用于实时处理计算机视觉方面的问题,它涵盖了很多计算机视觉领域的模块。在Python中常使用OpenCV库实现图像处理。

image.png

本文将介绍如何在Python3中使用OpenCV实现测量图像中物体的大小。

参考资料《Measuring size of objects in an image with OpenCV》

原理介绍

为了确定图片中一个物体的尺寸,我们首先需要使用一个参考对象进行“校准”。

我们的参考对象需要有两个重要的属性:

  • 我们应该以可测量的单位(例如毫米,英寸等)知道此对象的尺寸(以宽度或高度为单位)。
  • 我们应该能够根据对象的位置(例如,始终将对象放置在图像的左上角)或通过外观(例如独特的颜色)轻松地在图像中找到该参考对象,与图片中的所有其他对象唯一且不同)。

无论哪种情况,我们的参考对象都应该以某种方式唯一地标识。

在本案例中,我们将使用一个两角五分的美元硬币作为参考物体,并在所有示例中确保它始终是图像中最左的物体,如下所示:

demo06.png

通过保证美分硬币是最左边的物体,我们可以从左到右对我们的物体等高线区域进行排列,抓住这个硬币(它将始终对应于排序列表中的第一个等高线区域)。并使用它来定义我们的pixels_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 个像素。

利用这个比率,我们可以计算图像中物体的大小。

实现步骤

  • 1.原图转灰度图,高斯滤波,提取边缘,提取轮廓;
  • 2.将轮廓从左到右排序,最左边的作为参照物;
  • 3.计算参照物轮廓大小,pixelPerMetricXpixelPerMetricY
  • 4.计算其他物体轮廓,进行处理。

完整代码

import numpy as np
from scipy.spatial import distance as dist
import cv2
from imutils import contours
from imutils import perspective
import imutils
def show(name,img):
    cv2.imshow(name,img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
def midpoint(ptA,ptB):
    return ((ptA[0] + ptB[0]) * 0.5 , (ptA[1] + ptB[1]) * 0.5)
img = cv2.imread('E:\\demo\\demo06.png')
width = 25
img = imutils.resize(img,height = 500)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray,(5,5),0)
edged = cv2.Canny(gray,70,200)
edged = cv2.dilate(edged,None,iterations = 1)
edged = cv2.erode(edged,None,iterations = 1)
cnts,_ = cv2.findContours(edged,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
(cnts,_) = contours.sort_contours(cnts)
pixelPerMetricX = 0
pixelPerMetricY = 0
order = 1
for c in cnts:
    if cv2.contourArea(c) < 100:
        continue
    orig = img.copy()
    box = cv2.minAreaRect(c)
    box = cv2.boxPoints(box)
    box = box.astype('int')
    box = perspective.order_points(box)
    cv2.drawContours(orig,[box.astype(int)],0,(0,255,0),2)
    for x,y in box:
        cv2.circle(orig,(int(x),int(y)),5,(0,0,255),3)
    (tl,tr,br,bl) = box
    (tltrX,tltrY) = midpoint(tl,tr)
    (tlblX,tlblY) = midpoint(tl,bl)
    (blbrX,blbrY) = midpoint(bl,br)
    (trbrX,trbrY) = midpoint(tr,br)
    cv2.circle(orig,(int(tltrX),int(tltrY)),5,(183,197,57),-1)
    cv2.circle(orig,(int(tlblX),int(tlblY)),5,(183,197,57),-1)
    cv2.circle(orig,(int(blbrX),int(blbrY)),5,(183,197,57),-1)
    cv2.circle(orig,(int(trbrX),int(trbrY)),5,(183,197,57),-1)
    cv2.line(orig,(int(tltrX),int(tltrY)),(int(blbrX),int(blbrY)),(255,0,0),2)
    cv2.line(orig,(int(tlblX),int(tlblY)),(int(trbrX),int(trbrY)),(255,0,0),2)
    #纵向
    dA = dist.euclidean((tltrX,tltrY),(blbrX,blbrY))
    #横向
    dB = dist.euclidean((tlblX,tlblY),(trbrX,trbrY))
    if pixelPerMetricX == 0 or pixelPerMetricY == 0:
        pixelPerMetricX = dB / width
        pixelPerMetricY = dA / width
    dimA = dA / pixelPerMetricY
    dimB = dB / pixelPerMetricX
    cv2.putText(orig,"{:.1f}mm".format(dimB),(int(tltrX)-10,int(tltrY)),cv2.FONT_HERSHEY_COMPLEX,0.6,(255,255,255),1)
    cv2.putText(orig,"{:.1f}mm".format(dimA),(int(trbrX)-10,int(trbrY)),cv2.FONT_HERSHEY_COMPLEX,0.6,(255,255,255),1)
    cv2.imwrite('{}.jpg'.format(order),orig)
    order += 1
复制代码

输入图片:

demo06.png

输出结果为:

1.jpg

2.jpg

3.jpg

4.jpg

5.jpg

6.jpg

不完善之处

通过单纯的按比率来算图中物体的大小与长度,肯定会存在一定的误差。例如图像的拍摄角度不同会造成一定的测量上的误差。想要得到更好效果,需要对图像进行进一步的处理。

本月将陆续推出相关系列文章,

篇篇精彩,尽请关注。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享