测量金属零件上孔的直径图片,用远心单色相机拍摄

2024-05-15 00:51:41 发布

您现在位置:Python中文网/ 问答频道 /正文

设置:

  • 相机:Blackfly S Mono 20.0万像素
  • 镜头:光学远心镜头TC23080
  • 灯:16个绿色LED
  • Python:3.7.3
  • 开放式CV:4.0+

对不起图片链接,但一张图片大约是20MB,也不想丢失任何质量

图像示例:

https://drive.google.com/file/d/11PU-5fzvSJt1lKlmP-lQXhdsuCJPGKbN/view?usp=sharinghttps://drive.google.com/file/d/1B3lSFx8YvTYv3hzuuuYtphoHBuyEdc4o/view

案例: 有不同形状的金属部件,尺寸从5x5到10x10(cm)。在这些金属零件内部有很多2到10个圆孔,必须非常精确地检测出来。孔的实际尺寸未知,因为可能的零件种类繁多。目标是用OpenCV编写一个通用算法,可以处理任何金属零件并检测圆孔。在

我们所做的: 我们尝试用HoughCircles算法来检测这些漏洞,但几乎没有成功。该算法要么过于敏感,要么根本检测不到漏洞。我们尝试了不同的param1和param2值,但是没有成功。在使用HoughCircles之前,我们也尝试过模糊图像并将其传递给Canny,但这样的方法并没有产生更好的效果。同样的算法在分辨率较低的图片上效果更好。然而,不能牺牲分辨率,因为在这个项目中精度是极其重要的。在

https://drive.google.com/file/d/1TRdDbperi37bha0uJVALS4C2dBuaNz6u/view?usp=sharing

使用以下参数检测到上述圆圈:

minradius=0
maxradius=0
dp=1
param1=100
param2=21

通过使用上述参数,我们几乎可以得到我们想要的结果。当我们对不同的图片使用相同的参数时,问题就出现了。在

我们想要得到的最终结果是一个给定圆的直径,并且我们希望相同的算法可以用于不同的零件图片

这个问题与其他问题不同的是,我们不知道给定圆的近似半径(因此我们无法操纵minradius、maxradius、param1、param2或任何其他值)。在


Tags: https图像com算法view参数google图片
3条回答

您可以设置图像的阈值并使用findContours来查找孔的轮廓,然后用minEnclosingCircle将圆拟合到它们上面。通过将拟合圆与轮廓面积进行比较,可以检查拟合圆是否完好。在

import cv2 as cv
import math
import numpy as np
from matplotlib import pyplot as pl

gray = cv.imread('geriausias.bmp', cv.IMREAD_GRAYSCALE)
_,mask = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
contours,_ = cv.findContours(mask, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)
contours = [contour for contour in contours if len(contour) > 15]
circles = [cv.minEnclosingCircle(contour) for contour in contours]
areas = [cv.contourArea(contour) for contour in contours]
radiuses = [math.sqrt(area / math.pi) for area in areas]

# Render contours blue and circles green.
canvas = cv.cvtColor(mask, cv.COLOR_GRAY2BGR)
cv.drawContours(canvas, contours, -1, (255, 0, 0), 10)
for circle, radius_from_area in zip(circles, radiuses):
    if 0.9 <= circle[1] / radius_from_area <= 1.1:  # Only allow 10% error in radius.
        p = (round(circle[0][0]), round(circle[0][1]))
        r = round(circle[1])
        cv.circle(canvas, p, r, (0, 255, 0), 10)
cv.imwrite('geriausias_circles.png', canvas)

canvas_small = cv.resize(canvas, None, None, 0.25, 0.25, cv.INTER_AREA)
cv.imwrite('geriausias_circles_small.png', canvas_small)

geriausias_circles_small.png

通过健全性检查的圆在所有以蓝色显示的轮廓顶部以绿色显示。在

enter image description here

enter image description here


这是一种方法

  • 将图像转换为灰度和高斯模糊
  • 自适应阈值
  • 对平滑/过滤图像执行形态变换
  • 查找等高线
  • 求周长的等高线
  • 求出边界矩形和质心得到直径

在找到轮廓后,我们进行轮廓近似。其思想是,如果近似轮廓有三个顶点,那么它必须是一个三角形。类似地,如果它有四个,它必须是正方形或矩形。因此,我们可以假设,如果它的顶点数大于某个数,那么它就是一个圆。在

有几种方法可以得到直径,一种方法是找到轮廓的边界矩形并使用其宽度。另一种方法是从质心坐标计算。在

import cv2

image = cv2.imread('1.bmp')

# Gray, blur, adaptive threshold
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Morphological transformations
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)

# Find contours
cnts = cv2.findContours(opening, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]

for c in cnts:
    # Find perimeter of contour
    perimeter = cv2.arcLength(c, True)
    # Perform contour approximation
    approx = cv2.approxPolyDP(c, 0.04 * perimeter, True)

    # We assume that if the contour has more than a certain
    # number of verticies, we can make the assumption
    # that the contour shape is a circle
    if len(approx) > 6:

        # Obtain bounding rectangle to get measurements
        x,y,w,h = cv2.boundingRect(c)

        # Find measurements
        diameter = w
        radius = w/2

        # Find centroid
        M = cv2.moments(c)
        cX = int(M["m10"] / M["m00"])
        cY = int(M["m01"] / M["m00"])

        # Draw the contour and center of the shape on the image
        cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),4)
        cv2.drawContours(image,[c], 0, (36,255,12), 4)
        cv2.circle(image, (cX, cY), 15, (320, 159, 22), -1) 

        # Draw line and diameter information 
        cv2.line(image, (x, y + int(h/2)), (x + w, y + int(h/2)), (156, 188, 24), 3)
        cv2.putText(image, "Diameter: {}".format(diameter), (cX - 50, cY - 50), cv2.FONT_HERSHEY_SIMPLEX, 3, (156, 188, 24), 3)

cv2.imwrite('image.png', image)
cv2.imwrite('thresh.png', thresh)
cv2.imwrite('opening.png', opening)

关于这些图像,我们知道两件事:

  1. 这些物体是深色的,背景是明亮的。在
  2. 洞都是圆的,我们要测量所有的洞。在

所以我们要做的就是探测漏洞。这实际上是非常微不足道的:

  1. 阈值(背景变成物体,因为它是明亮的)
  2. 移除边缘对象

剩下的是那些洞。不包括任何接触图像边缘的孔。我们现在可以很容易地测量这些孔。既然我们假设它们是圆形的,我们可以做三件事:

  1. 计算对象像素,这是一个无偏估计的面积。根据面积我们确定了孔径。在
  2. 检测轮廓,找到质心,然后用轮廓点到质心的平均距离作为半径。在
  3. 规格化图像强度,使背景照明的强度为1,而其中有孔的对象的强度为0。对每个孔强度的积分是对该区域的亚像素精度估计(请参阅底部的快速解释此方法)。在

这段Python代码使用DIPlib(我是作者)演示了如何执行以下三种方法:

import PyDIP as dip
import numpy as np

img = dip.ImageRead('geriausias.bmp')
img.SetPixelSize(dip.PixelSize(dip.PhysicalQuantity(1,'um'))) # Usually this info is in the image file
bin, thresh = dip.Threshold(img)
bin = dip.EdgeObjectsRemove(bin)
bin = dip.Label(bin)
msr = dip.MeasurementTool.Measure(bin, features=['Size','Radius'])
print(msr)
d1 = np.sqrt(np.array(msr['Size'])[:,0] * 4 / np.pi)
print("method 1:", d1)
d2 = np.array(msr['Radius'])[:,1] * 2
print("method 2:", d2)

bin = dip.Dilation(bin, 10) # we need larger regions to average over so we take all of the light
                            # coming through the hole into account.
img = (dip.ErfClip(img, thresh, thresh/4, "range") - (thresh*7/8)) / (thresh/4)
msr = dip.MeasurementTool.Measure(bin, img, features=['Mass'])
d3 = np.sqrt(np.array(msr['Mass'])[:,0] * 4 / np.pi)
print("method 3:", d3)

这将产生以下输出:

^{pr2}$

在图像的大小上,{cd2>仍然是一个整数值。我没有费心制作一个显示图像大小的标记图像,但是这可以很容易地完成,正如您在其他答案中看到的那样。在

因为图像文件中没有像素大小信息,所以我将每个像素设置为1微米。这可能不正确,您必须进行校准以获得像素大小信息。在

这里的一个问题是背景照明太亮,导致像素饱和。这会导致孔看起来比实际大。校准系统很重要,这样背景照明接近相机可以记录的最大值,但不在该最大值或以上。例如,尝试将背景强度设置为245或250。第三种方法受光照不良影响最大。在

对于第二个图像,亮度非常低,给出的图像比必要的噪声更大。我需要将bin = dip.Label(bin)行修改为:

bin = dip.Label(bin, 2, 500) # Imposing minimum object size rather than filtering

也许做些噪声过滤会更容易些。结果是:

  |       Size |                                            Radius | 
- |       |                         - | 
  |            |        Max |       Mean |        Min |     StdDev | 
  |      (µm²) |       (µm) |       (µm) |       (µm) |       (µm) | 
- |       |       |       |       |       | 
1 |  4.023e+06 |      1133. |      1132. |      1125. |     0.4989 | 

method 1: [2263.24621554]
method 2: [2263.22724164]
method 3: [2262.90068056]

方法3快速讲解

方法在the PhD thesis of Lucas van Vliet (Delft University of Technology, 1993), chapter 6中描述。在

可以这样想:通过孔的光的量与孔的面积成正比(实际上它是由“面积”x“光强度”给出的)。把通过洞的光加起来,我们就知道洞的面积了。代码将对象的所有像素强度以及对象外部的一些像素相加(我在那里使用了10个像素,要走多远取决于模糊)。在

erfclip函数被称为“软剪辑”函数,它确保孔内强度一致为1,孔外强度一致为0,并且仅在边缘周围留下中间灰度值。在这种特殊情况下,这种软剪辑避免了成像系统中偏移的一些问题,以及对光强度的估计不好。在其他情况下,这一点更为重要,避免被测物体颜色不均匀的问题。它还可以减少噪音的影响。在

相关问题 更多 >

    热门问题