引入需要的工具包
我们会使用 matplotlib 进行绘图,并使用 numpy 处理数组相关的数值计算,以及使用 opencv 提供的计算机视觉工具,请确保工作环境可以正确引入并使用它们。
%matplotlib inline
import matplotlib.pyplot as plt
import cv2 as cv
import numpy as np
载入并缩放图像
读取一张图像,将其进行缩放,使最长边对齐image_max_side
的值,该值的设置可以根据经验,实际处理的图像和任务来做调整。
image_max_side = 1024
source_image = cv.imread('./samples/10.JPG')
scale = image_max_side / max(source_image.shape[:2])
source_image = cv.resize(source_image, None, fx=scale, fy=scale, interpolation=cv.INTER_AREA)
plt.imshow(source_image)
plt.show()
色彩处理
PCB板感光油的常见颜色为红,绿,蓝。我们可以借助这个特性使图像中集中分布感光油色彩的颜色通道相同,以此来使得非感光油区域与感光油区域之间的差异显著化。
例如:当前PCB的感光油为绿色,则我们将每一个像素中的第二个「下标为1」值设为255。
frame = source_image.copy()
frame[..., 1] = 255
gray = cv.cvtColor(frame, cv.COLOR_RGB2GRAY)
plt.imshow(gray, cmap='gray')
plt.show()
图像二值化特征
我们在色彩处理的基础上,首先对图像进行了归一化操作,然后使用7x7核的高斯模糊,接下来使用大律法进行图像二值化。
高斯模糊可以消除一些噪声的影响,使用7x7的核是一个经验,可以根据实际情况使用不同尺寸的核进行实验比对。
normal = ((gray - gray.min()) / max(1, (gray.max() - gray.min())) * 255).astype(np.uint8)
blur = cv.GaussianBlur(normal, (7, 7), 0)
ret, thresh = cv.threshold(blur, 127, 255, cv.THRESH_OTSU)
plt.imshow(thresh, cmap='gray')
plt.show()
形态学处理
我们使用形态学处理的闭合操作将个别二值化后出现破碎的焊点色块进行闭合。
kernel = np.ones((5, 5), np.uint8)
closing = cv.morphologyEx(thresh, cv.MORPH_CLOSE, kernel, iterations=1)
plt.imshow(closing, cmap='gray')
plt.show()
提取“拉丝”线
使用边缘检测的方法我们可以获取各个色块的边缘线,对这些线反复进行形态学闭合操作,可以将细长的色块「即“拉丝”线的色块」有效的提取出来。
edges = cv.Canny(closing, 100, 200)
edges = cv.morphologyEx(edges, cv.MORPH_CLOSE, kernel, iterations=3)
plt.imshow(edges, cmap='gray')
plt.show()
去除“拉丝”
将检测到的“拉丝”色块从之前形态学处理过的二值化图像中移除,得到最终的二值化特征。
final = closing * (edges < 127).astype(np.uint8)
plt.figure(figsize=(10, 10))
plt.subplot(121)
plt.imshow(final, cmap='gray')
plt.subplot(122)
plt.imshow(source_image, cmap='gray')
plt.show()
获取色块的位置和大小信息
我们使用 opencv 提供的findContours
工具可以快速提取出这些焊点色块的轮廓,然后调用boundingRect
之类的工具可以检测出焊点位置和大小等信息。
contours, _ = cv.findContours(final, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
boxes = []
for cont in contours:
x, y, w, h = cv.boundingRect(cont[:, 0])
if max(w, h) > min(final.shape) / 2: continue
if min(w, h) < 5: continue
boxes.append(cv.boxPoints(((x + w / 2, y + h / 2), (w, h), 0)).astype(np.int32))
view = source_image.copy()
cv.polylines(view, boxes, True, (255, 0, 0), 2, cv.LINE_AA)
plt.figure(figsize=(8, 8))
plt.imshow(view)
plt.show()