python图像处理:测量物体大小 Measuring size of objects in an image with OpenCV

python图像处理:测量物体大小 Measuring size of objects in an image with OpenCV

  • 效果
  • 原理
  • 代码

效果

python图像处理:测量物体大小 Measuring size of objects in an image with OpenCV_第1张图片
python图像处理:测量物体大小 Measuring size of objects in an image with OpenCV_第2张图片
python图像处理:测量物体大小 Measuring size of objects in an image with OpenCV_第3张图片
python图像处理:测量物体大小 Measuring size of objects in an image with OpenCV_第4张图片

物品 实际(长x宽) 测量(单位:mm)
硬币 25.0x25.0 25.0x25.0
橡皮 55.0x18.2 59.2x22.0
药丸 18.0x7.0 20.1x7.4
卡片 87.3x62.7 89.0x64.1

可以看到,还是比较精准的。这还是在未进行镜头畸变矫正的情况下。

原理

首先,我们需要在图中找一个参照物,并且为了方便找到它,我们将其放到最左边。
我选择了一元硬币作为参照,其是一个直径为25mm的圆。然后可以计算width=25mm在图中占了几个像素,得出pixelPerMetric = pixelNum/width,再计算其他物体所占像素n,便能得出实际长度n * pixelPerMetric。

原文中只计算了横向的pixelPerMetric作为全局pixelPerMetric。

考虑到拍摄角度不正的问题,我稍加改进,分别计算横向的pixelPerMetricX和纵向的pixelPerMetricY,测试下来效果确实比不分纵横好些。

但是,只是单纯的按比例来算大小,肯定会存在误差,理由有二:

  1. 拍摄角度肯定不是完美的90度俯角
  2. 照片可能会发生径向和切向镜头畸变

想要得到更好效果,需要进行畸变矫正(未实现)。

详细流程如下:

  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('dis2.jpg')
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

你可能感兴趣的:(图像处理,opencv,计算机视觉,python,图像处理)