使用 Opencv python 进行精确测量

问题描述

我实际上正在使用 OpenCV 和 Python 进行机器视觉项目.

I am actually working on a Machine Vision project using OpenCV and Python.

目标:该项目的目标是高精度测量组件的尺寸.

Objective : The objective of the project is to measure the dimensions of a component with high accuracy.

主要硬件:

  • Basler 5MP 相机 (aca-2500-14gm)

  • Basler 5MP camera (aca-2500-14gm)

红色背光灯(100 mm x 100 mm)(我的组件尺寸约为 60mm)

A red backlight (100 mm x 100 mm) (Size of my component is around 60mm)

实验

由于我正在查看非常严格的公差限制,因此我首先进行了精确研究.我将组件保留在背光源上,并在不移动部件的情况下拍摄了 100 张图像(想象一下,就像一个 100 帧的视频).我测量了所有 100 张图像的外径(OD).我的毫米/像素比率是 0.042.我测量了测量的标准偏差以找出精度,结果发现它在 0.03 mm 左右,这很糟糕.组件和设置都没有被触及,因此我期望的精度为 0.005 毫米.但我差了一个数量级.我正在使用 OpenCV 的霍夫圆来计算组件的外径.

Since I am Looking at very tight tolerance limits, I first did a precision study. I kept the component on the backlight source and took 100 images without moving the part (imagine like a video with 100 frames). I measured the Outer Diameter(OD) of all the 100 images. My mm/pixel ratio is 0.042. I measured the standard deviation of the measurement to find out the precision, which turned out to be around 0.03 mm which is bad. The component nor the setup is touched thus I was expecting a precision of 0.005 mm. But I am off by an order of magnitude. I am using OpenCV's Hough circle to calculate the OD of the component.

代码:

import sys
import pickle
import cv2
import matplotlib.pyplot as plt
import glob
import os
import numpy as np
import pandas as pd

def find_circles(image,dp=1.7,minDist=100,param1=50,param2=50,minRadius=0,maxRadius=0):
    """ finds the center of circular objects in image using hough circle transform

    Keyword arguments
    image -- uint8: numpy ndarray of a single image (no default).
    dp -- Inverse ratio of the accumulator resolution to the image resolution (default 1.7).
    minDist -- Minimum distance in pixel distance between the centers of the detected circles (default 100).
    param1 -- First method-specific parameter (default = 50).
    param2 -- Second method-specific parameter (default = 50).
    minRadius -- Minimum circle radius in pixel distance (default = 0).
    maxRadius -- Maximum circle radius in pixel distance (default = 0).

    Output
    center -- tuple: (x,y).
    radius -- int : radius.
    ERROR if circle is not detected. returns(-1) in this case    
    """

    circles=cv2.HoughCircles(image, 
                             cv2.HOUGH_GRADIENT, 
                             dp = dp, 
                             minDist = minDist, 
                             param1=param1, 
                             param2=param2, 
                             minRadius=minRadius, 
                             maxRadius=maxRadius)
    if circles is not None:
            circles = circles.reshape(circles.shape[1],circles.shape[2])
            return(circles)
    else:
        raise ValueError("ERROR!!!!!! circle not detected try tweaking the parameters or the min and max radius")

def find_od(image_path_list):
    image_path_list.sort()
    print(len(image_path_list))
    result_df = pd.DataFrame(columns=["component_name","measured_dia_pixels","center_in_pixels"])
    for i,name in enumerate(image_path_list):
        img = cv2.imread(name,0) # read the image in grayscale
        ret,thresh_img = cv2.threshold(img, 50, 255, cv2.THRESH_BINARY_INV)
        thresh_img = cv2.bilateralFilter(thresh_img,5,91,91) #smoothing
        edges = cv2.Canny(thresh_img,100,200)
        circles = find_circles(edges,dp=1.7,minDist=100,param1=50,param2=30,minRadius=685,maxRadius=700)
        circles = np.squeeze(circles)
        result_df.loc[i] = os.path.basename(name),circles[2]*2,(circles[0],circles[1])
    result_df.sort_values("component_name",inplace=True)
    result_df.reset_index(drop=True,inplace=True)
    return(result_df)

df = find_od(glob.glob("./images/*"))
mean_d = df.measured_dia_pixels.mean()
std_deviation = np.sqrt(np.mean(np.square([abs(x-mean_d) for x in df.measured_dia_pixels])))

mm_per_pixel = 0.042
print(std_deviation * mm_per_pixel)

输出:0.024

组件的图片:

由于图像是在不干扰设置的情况下拍摄的,因此我预计测量的可重复性约为 0.005 毫米(5 微米)(对于 100 张图像).但事实并非如此.是霍夫圆的问题吗?或者我在这里错过了什么

Since the Images are taken without disturbing the setup, I expect the measurement's repeatability to be around 0.005 mm (5 microns) (For 100 images).But this is not so. Is it a problem of hough circle? or what am I missing here


解决方案

霍夫设计用于检测,而不是量化.如果您想要精确测量,则必须使用为此设计的库.OpenCV 不适合量化,因此在这方面的能力很差.

Hough is designed for detecting, not for quantifying. If you want precise measures, you'll have to use a library designed for that. OpenCV is not meant for quantification, and consequently has poor capabilities there.

很久以前,我写了一篇关于使用 Radon 变换更精确地估计大小的论文(Hough 变换是对 Radon 变换进行离散化的一种方法,在某些情况下速度很快,但并不精确):

A long time ago I wrote a paper about more precise estimates of size using the Radon transform (the Hough transform is one way of discretizing the Radon transform, it's fast for some cases, but not precise):

  • C.L.Luengo Hendriks, M. van Ginkel, P.W.Verbeek 和 LJ van Vliet,广义 Radon 变换:采样、精度和内存考虑,模式识别 38(12):2494–2505, 2005,doi:10.1016/j.patcog.2005.04.018.这是一个 PDF.

但由于您的设置控制得非常好,因此您实际上并不需要所有这些来获得精确的测量值.这是一个非常简单的 Python 脚本来量化这些漏洞:

But because your setup is so well controlled, you don't really need all that to get a precise measure. Here is a very straight-forward Python script to quantify these holes:

import diplib as dip
import math

# Load image and set pixel size
img = dip.ImageRead('N6uqw.jpg')
img.SetPixelSize(0.042, "mm")

# Extract object
obj = ~dip.Threshold(dip.Gauss(img))[0]
obj = dip.EdgeObjectsRemove(obj)

# Remove noise
obj = dip.Opening(dip.Closing(obj,9),9)

# Measure object area
lab = dip.Label(obj)
msr = dip.MeasurementTool.Measure(lab,img,['Size'])
objectArea = msr[1]['Size'][0]

# Measure holes
obj = dip.EdgeObjectsRemove(~obj)
lab = dip.Label(obj)
msr = dip.MeasurementTool.Measure(lab,img,['Size'])
sz = msr['Size']
holeAreas = []
for ii in sz.Objects():
   holeAreas.append(sz[ii][0])

# Add hole areas to main object area
objectArea += sum(holeAreas)

print('Object diameter = %f mm' % (2 * math.sqrt(objectArea / math.pi)))
for a in holeAreas:
   print('Hole diameter = %f mm' % (2 * math.sqrt(a / math.pi)))

这给了我输出:

Object diameter = 57.947768 mm
Hole diameter = 6.540086 mm
Hole diameter = 6.695357 mm
Hole diameter = 15.961935 mm
Hole diameter = 6.511002 mm
Hole diameter = 6.623011 mm

请注意,上面的代码中有很多假设.还有一个问题是相机没有正好位于物体上方,您可以看到孔的右侧反射光.这肯定会增加这些测量的不精确性.但还要注意,我在测量物体时没有使用物体是圆形的知识(仅在将面积转换为直径时).或许可以使用圆度标准来克服一些成像缺陷.

Note that there are a lot of assumptions in the code above. There is also an issue with the camera not being centered exactly above the object, you can see the right side of the holes reflecting light. This will certainly add imprecision to these measurements. But note also that I didn't use the knowledge that the object is round when measuring it (only when converting area to diameter). It might be possible to use the roundness criterion to overcome some of the imaging imperfections.

上面的代码使用了 DIPlib,这是一个 C++ 库,具有相当粗糙的 Python 接口.Python 语法是 C++ 语法的直接翻译,有些东西还是很别扭和非 Pythonic.但它专门针对量化,因此我建议您在自己的应用程序中尝试一下.

The code above uses DIPlib, a C++ library with a rather rough Python interface. The Python syntax is a direct translation of the C++ syntax, and some things are still quite awkward and non-Pythonic. But it is aimed specifically at quantification, and hence I recommend you try it out for your application.

相关文章