经验记录:利用传统机器视觉检测 IGBT 车间芯片的好坏

一、 前言

该项目是我进BYD以来的第二个义务性的项目,与前一个一样,技术难度都不高,但是看见自己写的代码能帮助到车间的同事,减少他们的工作量,我还是很自豪,正如以前看到的一句话:“代码也是有温度的”,与诸君共勉。

先上图看下待处理的图片样子,如下图:
在这里插入图片描述
台面后面可能要变,所以除了晶圆之外的东西先不要管。中间每块白色的小块就是我们检测的对象——芯片。个别芯片上面有黑点点,那是质检的同事做的标记,意为坏芯片。我们的任务就是识别出一块晶圆上的所有坏芯片的位置,并将坏芯片的位置信息写入相关的txt文件。

demo经过两次优化,单帧图片运行时长的优化记录为 1.0+s -> 0.75s -> 0.33s。检测效果上也较1.0版提升许多。详细代码见:IGBT项目代码 。由于代码较多,正文中的代码只做简单展示,即只贴出对应操作的核心代码,具体还是下载我的源码来阅读调试。

二、 正文

由上图可看出,芯片很小,整张图片又很大,所以第一步我要将晶圆部分裁剪出来。这里我利用HSV颜色筛选来做,通过定位黄色区域,裁剪出晶圆部分的图片。
先给出查看图片HSV值的博客 ,用链接里的脚本查看黄色区域的HSV值范围。

(一)颜色初筛、截取出 ROI

# 很明显,没头没尾,只做展示
img = cv2.imread("../img/%d.jpg" % i)  # 读取图片路径   
    
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

lower_color = np.array([20, 105, 100])  # 分别对应着HSV中的最小值
upper_color = np.array([36, 255, 255])  # 分别对应着HSV中的最大值

# 初次筛选颜色,意图是得到黄色晶圆的 ROI 区域
mask_1 = cv2.inRange(img_hsv, lower_color, upper_color)
x1, y1, x2, y2 = get_roi(mask_1)
img_hsv_roi = img_hsv[y1:y2, x1:x2]

最后的效果如下图:
在这里插入图片描述
注意,这一步只是初筛,可以看出芯片之间的黄色缝没有很好地检测出来,但是没关系,我们目的是裁剪晶圆的ROI,如果缝全被检测出来了,下面的ROI反而不好找,具体你们可以自己试试。

# 该函数是为了定位上图白色区域的边界,然后好裁剪 ROI
def get_roi(mask, img=None):
    _, contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    max_w = 0
    max_h = 0
    max_x = 0
    max_y = 0

    for c in contours:
        # 找到边界坐标
        x, y, w, h = cv2.boundingRect(c)  # 计算点集最外面的矩形边界
        if ((w > max_w) and (h > max_h)):
            max_w = w
            max_h = h
            max_x = x
            max_y = y
        
    return max_x, max_y, (max_x + max_w), (max_y + max_h)

经过上面函数的努力,我们得到:
在这里插入图片描述
上面这张图只是给你们看下我们这步的目的,其实我只对HSV图做了裁剪,取出了对应的ROI区域。下面进行二次颜色筛选。

(二)颜色二次筛选

# 下面代码看看出 只对 HSV 图做了裁剪
img_hsv_roi = img_hsv[y1:y2, x1:x2]
    
# 取 ROI 后再进行一次颜色筛选,条件更宽松,意图是得到每个芯片的掩模
lower_color_1 = np.array([18, 85, 30])  # 分别对应着HSV中的最小值
upper_color_1 = np.array([40, 255, 255])

mask_2 = cv2.inRange(img_hsv_roi, lower_color_1, upper_color_1)
mask_2 = cv2.medianBlur(np.uint8(mask_2), 5)  # 中值滤波,降噪
h, w = mask_2.shape

最后得到mask_2的图片为下图,可以看见一些边边角角没处理好,不过没关系,它不是我们的重点,模糊就模糊吧。
在这里插入图片描述

(三) 去除晶圆轮廓

接下来我的做法是针对上图的圆形轮廓做个处理,免得其带来干扰,具体为

mask = np.full((img.shape), 255)
#print(img.shape)
h, w = img.shape
# 消除晶圆的轮廓,也是降噪,其轮廓干扰较大,不需要它
radius = max(w, h) // 2
center = (radius, radius)
cv2.circle(mask, center, radius - 10, (0, 0, 0), -1)
#test_img = img.copy()
#cv2.circle(test_img, center, radius - 15, (128, 128, 128), 2)
img = img + mask 
img[img > 200] = 255
img[img <= 0] = 0

效果大致如下图,计划把灰色圆的内部全部用0填充,圆外部是255,这样与原图相加就能得到更干净的芯片mask图。
在这里插入图片描述
最后结果为下图,可以看见,背景前景都干净许多了。
在这里插入图片描述

(四) 确定芯片群边界

对上图采用canny轮廓提取,得到下图,注意,图中的红绿黄白线是我画的,下面会讲它们的作用,原图就是一个个小格子。看起来是不是很接近我们的目标了?接近是接近,但还不是,由于摄像头角度原因,图片有点歪,而且不止是角度歪,芯片群的边界线连起来也不会是个矩形。所以我们需要做透视变换,但做透视变换需要获得四个点的坐标,这就是本节的作用——确定边界,然后确定四个顶点坐标。
在这里插入图片描述

大致方法为,用上图中 平行于x轴的红白二线(看起来不平行是因为我手画没画好),穿过芯片群的轮廓,会得到1、2、3、4四个交点,其中1、2连起来就是芯片群左边界,3、4连起来是芯片群右边界。再画两个平行于y轴的黄绿二线,同理得到芯片群的上边界、下边界。
再求出上面四条边界线延长线的交点,就可画出芯片群的bbox框(该框不是矩形框)。
在这里插入图片描述
可以看见,边界检测效果还是可以的,即使有偏差,也不是很大。如果你问,上面红绿黄白四根线有可能穿过小格子之间的空隙中,那样不是不准了吗?这也确实是个问题,我的方法是用膨胀方法填满空隙,代码:

# 下面是先 canny 处理,然后填满空隙
img_blur = cv2.medianBlur(np.uint8(img), 5)  # 降噪

canny = cv2.Canny(img_blur, 50, 50)
canny[h - 30:h, :] = 0  # 图片下部有轮廓的残留,去掉它们
# 芯片之间有很多空隙,填满空隙,方便下面找寻边界线
kerne_x = cv2.getStructuringElement(cv2.MORPH_RECT,(1, 15))  # w h
canny_x = cv2.dilate(canny, kerne_x, iterations = 2)

kerne_y = cv2.getStructuringElement(cv2.MORPH_RECT,(15, 1))  # w h
canny_y = cv2.dilate(canny, kerne_y, iterations = 2)

(五) 透视变换

边界知道了,就是做透视变换了,代码不贴了,具体自己去看我开源的代码,效果如下图:
在这里插入图片描述

(六) 得到每个芯片的bbox

对上图再做一次canny,提取其轮廓,如下图
在这里插入图片描述
一开始,直接用一根直线穿过每个小格子,取直线上值不为0的坐标,最后发现较多小格子的边并不是直线,使得最后效果不好。针对该问题,我的方法是,在宽、高方向上,用一个滤波进行过滤,语言不好形容,看代码吧。

# 遍历扫过,噪声处就全部置为 0,边界处就为 255
h, w = mask_canny.shape
dst_x = mask_canny.copy()
dst_y = mask_canny
max_sum_1 = np.sum(np.full((h, ), 255))
for i in range(w):  # 芯片边界处,和会很大,其他处会很小,消除噪声,下同
    temp_sum = np.sum((dst_x[:, i]))
    ratio = temp_sum / max_sum_1
    if (ratio > 0.2):   # 阈值需要仔细调
        dst_x[:, i] = 255
    else:
        dst_x[:, i] = 0

max_sum_2 = np.sum(np.full((w, ), 255))
for j in range(h):
    temp_sum = np.sum((dst_y[j, :]))
    ratio = temp_sum / max_sum_2
    if (ratio > 0.25):
        dst_y[j, :] = 255
    else:
        dst_y[j, :] = 0

最后的效果如下图,理论上是呈现20根竖线,图中每根竖线都是每块芯片的x方向上的边界。之所以会呈现明暗不一的视觉效果,是因为个别边界处的线不止一根(即竖线总数大于20根),好几根叠加才显得亮,多余的线对我们来说是噪声,所以要去掉它们。为此我采用的方法是聚类,直接聚为20个类,类中心就是我们最后输出的每块芯片边界。y方向上处理方法与之相同。
在这里插入图片描述
根据坐标,画出每块芯片的bbox,效果为下图,可以看出边界检测效果还是可以的。
在这里插入图片描述
剩下的芯片识别部分就不写了,个人以为没什么东西了,想必大伙都知道怎么搞,不班门弄斧了。如果觉得我讲的不清楚,可以去看看我开源的代码,想必会更加清楚。

三、 后记

仓促之下写成,如有错误,还望帮忙雅正,谢谢!

猜你喜欢

转载自blog.csdn.net/tangshopping/article/details/124337377
今日推荐