这是我参与更文挑战的第27天,活动详情查看: 更文挑战
OpenCV
是一个C++
库,目前流行的计算机视觉编程库,用于实时处理计算机视觉方面的问题,它涵盖了很多计算机视觉领域的模块。在Python
中常使用OpenCV
库实现图像处理。
本文将介绍如何在Python3
中使用OpenCV
实现测量图像中物体的大小。
参考资料《Measuring size of objects in an image with OpenCV》
原理介绍
为了确定图片中一个物体的尺寸,我们首先需要使用一个参考对象进行“校准”。
我们的参考对象需要有两个重要的属性:
- 我们应该以可测量的单位(例如毫米,英寸等)知道此对象的尺寸(以宽度或高度为单位)。
- 我们应该能够根据对象的位置(例如,始终将对象放置在图像的左上角)或通过外观(例如独特的颜色)轻松地在图像中找到该参考对象,与图片中的所有其他对象唯一且不同)。
无论哪种情况,我们的参考对象都应该以某种方式唯一地标识。
在本案例中,我们将使用一个两角五分的美元硬币作为参考物体,并在所有示例中确保它始终是图像中最左的物体,如下所示:
通过保证美分硬币是最左边的物体,我们可以从左到右对我们的物体等高线区域进行排列,抓住这个硬币(它将始终对应于排序列表中的第一个等高线区域)。并使用它来定义我们的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.计算参照物轮廓大小,
pixelPerMetricX
,pixelPerMetricY
; - 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
复制代码
输入图片:
输出结果为:
不完善之处
通过单纯的按比率来算图中物体的大小与长度,肯定会存在一定的误差。例如图像的拍摄角度不同会造成一定的测量上的误差。想要得到更好效果,需要对图像进行进一步的处理。
本月将陆续推出相关系列文章,
篇篇精彩,尽请关注。