这可能很容易忘记,但我们的电子设备内部发生的一切并不是魔法。
有些产品是如此复杂,以至于没有一个人能够准确地理解每个零件是如何相互作用的,而制造过程是存在的,因此出厂的产品能够正常工作,这一想法让我感到惊讶。任何涉及集成电路的东西都属于这一类。如果你仔细观察了一块电路板(PCB),你可能会注意到一些小的金色磁盘,它们没有电连接到任何其他结构上。这些是基准标记:它们的目的是让视觉系统尽可能容易地找到它们,在图像中定位PCB。
在本文中,我们将使用OpenCV的matchTemplate函数完成定位基准标记的步骤。你可以在此存储库中找到代码和图像。
为什么PCB需要基准标记?
在PCB组装过程中,自动化系统将在焊接之前,将分立元件(芯片、电容器、连接器等)放置在PCB上各自的位置。基准标记的检测允许在PCB上精确放置元件。放置后,自动检查系统将检查是否所有东西都在其应放置的位置。再次,基准标记将提供将物理点从PCB图纸(以毫米为单位)转换为图像点(以像素为单位)所需的参考点。
我们首先将图像转换为灰度,因为我们依赖于基准标记的不同形状,而不是它们的特定颜色。这是通过OpenCV的cvtColor函数完成的。
该PCB上的基准标记由两个同心圆组成,其各自的直径分别为26和68像素。手动测量内圆盘的直径:
OpenCV的matchTemplate函数需要目标对象的模板图像。在这种情况下,它将是一个基准标记的图像。我们可以裁剪图像中三个基准标记中的一个,并将其用作模板,但这种方法与真实基准标记容易有微小外观差异。相反,我们将创建理想基准标记的合成图像:
登录后复制
# 创建一个合成基准图像 pattern_sizeHW = [args.fiducialOuterDiameterInPixels, args
.fiducialOuterDiameterInPixels] if args.fiducialOuterDiameterInPixels %2 == 0:
# 确保图案大小是奇数 pattern_sizeHW[0] += 1 pattern_sizeHW[1] += 1
fiducial_pattern = np.zeros(pattern_sizeHW, dtype=np.uint8) cv2.circle(fiducial_pattern,
(pattern_sizeHW[1]//2, pattern_sizeHW[0]//2), args.fiducialOuterDiameterInPixels//2, 70,
cv2.FILLED) # 外层是深灰色 cv2.circle(fiducial_pattern, (pattern_sizeHW[1]//2,
pattern_sizeHW[0]//2),
args.fiducialInnerDiameterInPixels//2, 255, cv2.FILLED) # 内部是白色的
# 标准化模式图像 standardized_fiducial_pattern = (fiducial_pattern.astype(np.float32)
- fiducial_pattern.mean())/fiducial_pattern.std()1.2.3.4.5.6.7.8.9.10.
基准标记图像被标准化为零平均值和单位标准差.
我们现在拥有调用matchTemplate所需的所有成分:
登录后复制
# 模式匹配match_img = cv2.matchTemplate(grayscale_img.astype(np.float32),
standardized_fiducial_pattern, cv2.TM_CCOEFF_NORMED)# 用于可视化,
填充0以获得与原始图像相同大小的图像padded_match_8bits_img = np.zeros((img_shapeHWC[0],
img_shapeHWC[1]), dtype=np.uint8)padded_match_8bits_img[fiducial_pattern.shape[0]//2: fiducial_pattern
.shape[0]//2 + match_img.shape[0], fiducial_pattern.shape[1]//2: fiducial_pattern.shape[1]//2 + match_img
.shape[1]] = (128 * (match_img + 1.0)).astype(np.uint8)1.2.3.4.5.6.
需要注意的是,我们用零填充得到的匹配图像,使图像具有与原始图像相同的尺寸。这是必要的,因为matchTemplate基于卷积,输出图像具有尺寸( Wₒᵣᵢ-Wₚₐₜₜₑᵣₙ+1, Hₒᵣᵢ-Hₚₐₜₜₑᵣₙ+1 ).在我们的例子中,模式图像具有尺寸(69,69),matchTemplate的输出比原始图像的尺寸窄68像素,短68像素。为了补偿这种影响,我们对图像外围的34个像素进行了零填充。我们还重新调整了匹配图像的灰度级别,从[-1,1]到[0,255],以便于可视化。
我们感兴趣的是匹配图像的强度峰值,即上图中的亮点。决定哪些亮点是我们正在寻找的基准标志,关键在于设置正确的阈值。与手动设置阈值不同,我们将使用这样一个事实,即我们知道此匹配图像中应该有三个基准标记,因此应用正确的阈值应该会产生一个正好有三个斑点的二值图像。为此,我们将逐渐降低阈值,从255开始计算得到的阈值图像中的斑点。当我们到达三个斑点时,我们停下来。通过降低阈值直到找到预期数量的对象来找到最佳阈值:
登录后复制
# 找到最优阈值,以检测预期的基准数blob_detector = blob_analysis
.BinaryBlobDetector()optimal_threshold = Noneoptimal_seedPoint_boundingBox_list = Noneoptimal
_annotated_blobs_img = Nonefor threshold in range(255, 1, -1): _,
thresholded_img = cv2.threshold(padded_match_8bits_img, threshold, 255,
cv2.THRESH_BINARY) # 计算blobs的数量 seedPoint_boundingBox_list, annotated_blobs_img = blob_detector
.DetectBlobs(thresholded_img) logging.info("threshold = {}; len(seedPoint_boundingBox_list) = {}"
.format(threshold, len(seedPoint_boundingBox_list) )) if len(seedPoint_boundingBox_list) >= args
.numberOfFiducials: optimal_threshold = threshold
optimal_seedPoint_boundingBox_list = seedPoint_boundingBox_list
optimal_annotated_blobs_img = annotated_blobs_img breaklogging
.info("The optimal match threshold is {}. The number of found blobs is {}"
.format(optimal_threshold, len(optimal_seedPoint_boundingBox_list)))1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.
PCB平面上的参考点(以毫米为单位)和它们在图像中的各自位置(以像素为单位)之间的对应关系是计算单应矩阵所需的,即允许我们将以毫米为单位的坐标系转换为以像素为单位的坐标系的变换矩阵。利用这个变换矩阵,我们可以标注图像。
上面的注释图像证实了三个基准标记被正确找到,并且计算的变换矩阵是准确的。现在可以裁剪图像的任何区域,其中应该有我们想要检查的给定组件。在制造环境中,要检查的对象在输送机上移动是很常见的。我们不能假设被检查的物体总是精确地位于相对于摄像机的同一位置。通过检测基准标记计算出的变换矩阵允许我们检索PCB上给定的感兴趣区域。我们刚刚做的是自动化检查流程的第一部分。这可能不是最困难的部分,但肯定是最关键的部分:如果对象定位失败,其他任何事情都没有意义。下一次,当你看到PCB时——提醒自己它不是一个神奇的人工制品,而是一个工程奇迹——试着找出便于组装和自动检查的基准标记。
免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删