• 正文
  • 相关推荐
申请入驻 产业图谱

使用模板匹配检测PCB上的基准Mark点

05/29 13:36
395
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

如果您仔细观察过印刷电路板 (PCB),您可能会注意到一些金色小圆盘,它们没有与任何其他结构进行电气连接。这些是基准标记(或基准点):它们的目的是让视觉系统尽可能轻松地找到它们,从而在图像中定位 PCB。

在本文中,我们将介绍使用 OpenCV 的matchTemplate()函数定位基准标记的步骤 。代码和测试图像下载链接:

https://github.com/sebastiengilbert73/pattern_matching_tutorial

为什么 PCB 需要基准标记?

在 PCB 组装过程中,自动化系统会将分立元件(芯片电容器、连接器等)放置在 PCB 上的相应位置,然后再焊接到焊盘上。基准标记的检测允许将元件精确放置在 PCB 上。放置后,自动检查系统将检查所有元件是否都放置在应在的位置并放置在公差范围内。同样,基准标记将提供将物理点从 PCB 图纸(以毫米为单位)转换为图像点(以像素为单位)所需的参考点。

预处理    我们首先将图像转换为灰度,因为我们依赖于基准标记的独特形状,而不是它们的具体颜色。这是通过 OpenCV 的cvtColor()函数完成的。

该PCB上的基准标记由两个同心圆组成,其直径分别为 26 像素和 68 像素。

OpenCV 的matchTemplate()函数需要目标对象的模板图像。在本例中,它将是基准标记的图像。我们可以裁剪图像中的三个基准标记之一并将其用作模板,但这种方法很容易遗漏与任意选择的模板图像外观差异较小的真实基准标记。相反,我们将创建理想基准标记的合成图像:

# Create a synthetic fiducial image  pattern_sizeHW = [args.fiducialOuterDiameterInPixels, args.fiducialOuterDiameterInPixels]  if args.fiducialOuterDiameterInPixels %2 == 0:  # Make sure the pattern size is odd      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)  # The outer disk is dark gray  cv2.circle(fiducial_pattern, (pattern_sizeHW[1]//2, pattern_sizeHW[0]//2), args.fiducialInnerDiameterInPixels//2, 255, cv2.FILLED)  # The inner disk is white  # Standardize the pattern image  standardized_fiducial_pattern = (fiducial_pattern.astype(np.float32) - fiducial_pattern.mean())/fiducial_pattern.std()

基准标记图像被标准化为零均值和单位标准差,因为我们希望matchTemplate()的结果在搜索图像中均匀值的区域中接近于零,并且正匹配的强度值接近于 1。

模板匹配

现在我们已经拥有调用matchTemplate()所需的所有要素:

# Pattern matchmatch_img = cv2.matchTemplate(grayscale_img.astype(np.float32), standardized_fiducial_pattern, cv2.TM_CCOEFF_NORMED)# Create an 8-bit version of the match image for visualization, padded with zeros to get an image the same size as the originalpadded_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)

值得注意的是,我们用零填充了匹配结果图像,以使图像的尺寸与原始图像相同。这是必要的,因为matchTemplate()基于卷积,输出图像的尺寸为 (Wₒᵣᵢ-Wₚₐₜₜₑᵣₙ+1,Hₒᵣᵢ-Hₚₐₜₜₑᵣₙ+1)。在我们的例子中,图案图像的尺寸为 (69, 69),matchTemplate()的输出尺寸比原始图像窄 68 像素,短 68 像素。为了补偿这种影响,我们在图像外围用零填充了 34 个像素。我们还重新调整了匹配图像的灰度级,从 [-1, 1] 到 [0, 255],以便于可视化。

我们感兴趣的是匹配图像强度的峰值,即上图中的亮点。确定哪些亮点是我们要搜索的基准标记,就是设置正确的阈值。    我们不会手动设置阈值,而是利用这样一个事实:我们知道此匹配图像中应该有三个基准标记,因此应用正确的阈值应该会产生一个包含三个斑点¹ 的二值图像。为此,我们将逐渐降低阈值,从 255 开始,并计算结果阈值图像中的斑点数量。当我们达到三个斑点时,我们停止。

# Find the optimal threshold to detect the expected number of fiducialsblob_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)    # Count the number of 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)))

变换矩阵的计算    PCB 平面上的参考点(以毫米为单位)与图像中它们各自的位置(以像素为单位)之间的三个对应关系是计算单应性矩阵所需的最低限度,即允许我们将毫米坐标转换为像素坐标的变换矩阵。使用此变换矩阵,我们可以用 PCB 边缘注释图像。

上面带注释的图像证实了三个基准标记被正确找到,并且计算出的变换矩阵是准确的。现在可以裁剪图像中任何应该有我们要检查的给定组件的区域,假设我们有它在 PCB 上的物理坐标。    在制造环境中,被检测物体在传送带上移动是很常见的。我们不能假设被检测物体相对于相机总是精确地位于同一位置。通过检测基准标记计算出的变换矩阵使我们能够检索 PCB 上给定的关注区域,即使从一个物体到另一个物体有平移和旋转。

相关推荐