基于形状匹配的螺丝识别(完整代码)


前言

物品的分拣是许多工业生产线必不可少的部分。最初的物品分拣工作由人工完成,分拣效率低,需要消耗大量的人力,对工人的安全和健康也存在一定的威胁,并且受到很多因素的干扰,有可能出现错误分拣或者损坏物料的情况。所以,有必要设计一种自动分拣系统来解决这个问题。本文以螺丝的分拣为例,设计一种基于形状匹配的螺丝识别算法

一、算法设计

流水线工作环境应处于明亮的室内,流水线的输送皮带颜色应该是暗色系,黑色最佳,相机摄取到的图片如图
在这里插入图片描述
对其进行阈值分割,可得到结果
在这里插入图片描述
结果较好,但这种算法过于简单,很难应对更复杂的情况。所以,本文对该算法做两点改进。
第一, 增强系统对光照不均匀的鲁棒性。
第二, 当图片中出现其他物体时,系统也能准确识别出螺丝,而不会出现误检的情况

1.1改进一

倘若系统工作环境光照突然变的不均匀,那么图片中的某些区域将会变的很亮,某些区域又比较暗,这会极大地干扰阈值分割。
为解决此问题,可考虑采用形态学的方法,先将灰度图像进行开操作,然后再用灰度图像减去开操作之后的图像,便可使得整张图片的亮度均匀。原图和结果分别见下图
在这里插入图片描述
在这里插入图片描述
从上图中可以看出,左半部分亮度明显比右半部分亮,经过形态学操作之后,整张图片亮度变得均匀了,便于后续阈值分割

1.2改进二

实际工作中,流水线上可能会混入其他的物品,此时该系统还应该保证能准确的定位到螺丝,并且不会误定位其他物品。
在对原图做了光照均匀化处理之后,再采用基于形状匹配的算法,利用python+opencv进行编程。结果见图

在这里插入图片描述

二、完整代码

from cv2 import cv2
import numpy as np
import matplotlib.pyplot as plt

def tem(path):
    '''
    该函数功能是生成一个用于matchshape的轮廓,返回值是一个螺丝轮廓。
    参数说明,
    path:模板图片,要求只有一个螺丝目标,背景无干扰,不然影响后续匹配精度
    '''
    tem = cv2.imread(path)
    #  灰度化
    gray_tem = cv2.cvtColor(tem,cv2.COLOR_BGR2GRAY)
    # mask = np.zeros(gray_tem.shape,np.uint8)
    #  定义一个101尺寸的矩形核,用于减弱光照不均匀的影响。这个核的尺寸要足够大,小了不行
    k = np.ones((101,101),np.uint8)
    #  定义一个15尺寸的矩形核,用于滤波
    k1 = np.ones((15,15),np.uint8)
    #  对灰度图进行开操作,将局部很亮的区域扩大,很暗的地方基本不变。这类似在二值图上进行开操作,只不过在这里是在灰度图上
    #  操作,效果就是扩大亮区域,对于暗区域影响不大
    opend_tem = cv2.morphologyEx(gray_tem,cv2.MORPH_OPEN,k)
    #  将灰度图减去开操作之后的图,相当于亮的减亮的,暗的减暗的,减完之后整张图灰度值就均匀了,为了相减之后灰度值不至于太小
    #  加一个偏量100
    add_tem = cv2.addWeighted(gray_tem,1,opend_tem,-1,100)
    #  阈值化
    _,binary_tem =cv2.threshold(add_tem,180,255,cv2.THRESH_BINARY)
    #  用开操作去噪
    opening_tem = cv2.morphologyEx(binary_tem,cv2.MORPH_OPEN,k1)
    _,contours,_ = cv2.findContours(opening_tem,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)

    n = len(contours)
    s = []
    #  将轮廓面积值放入列表s中
    for i in range(n):
        s.append(cv2.contourArea(contours[i]))
    #  为了防止去噪后依然有一些小块干扰,通过一个循环只返回轮廓面积最大的那个作为模板
    for i in range(n):
        if s[i] == max(s):
            #  这一步是为了查看轮廓图片,可不要
            # mask = cv2.drawContours(mask,contours,i,(255,255,255),-1)
            return contours[i]
    

def detect(gray,o,tem):
    '''
    该函数用于将待匹配图片与模板图片进行比对,返回检测成功的目标图片和目标的最小矩形。
    参数说明,
    gray,待匹配图片的灰度图像
    o,原图
    tem,模板轮廓
    '''
    #  二值化
    _,binary = cv2.threshold(gray,210,255,cv2.THRESH_BINARY)
    #  用开操作滤波
    k = np.ones((8,8),np.uint8)
    opening = cv2.morphologyEx(binary,cv2.MORPH_OPEN,k)
    #  找图片中轮廓
    _,contours,_ = cv2.findContours(opening,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
    n = len(contours)
    #  存储形状匹配成功的轮廓面积
    s = []
    #  存储形状匹配成功轮廓的索引值
    b = []
    #  存储最终匹配成功的目标最小矩形
    rects = []
    #  存储轮廓的所有面积值
    s1 = []
    rets = []
    for i in range(n):
        s1.append(cv2.contourArea(contours[i]))

    for i in range(n):
        #  形状匹配
        ret = cv2.matchShapes(tem,contours[i],1,0)
        rets.append(ret)
        # print('第%d个轮廓形状匹配度:%f\n'%(i,ret))
        # print('第%d个轮廓形状面积:%f\n'%(i,cv2.contourArea(contours[i])))
        #  设置一个阈值,小于该阈值则认为与模板形状相似,可用于下一步比较。该阈值需要通过实验得到。这一步是粗过滤
        #  可以将那些形状明显不相符的目标过滤掉
        if ret < 5.4:
            s.append(cv2.contourArea(contours[i]))
            b.append(i)
    print('看一看过滤之后的轮廓索引和面积:\n')
    print(b)
    print(s)
    print('看一看过滤之前的轮廓匹配度和面积:\n')
    print(rets)
    print(s1)    

    #  设置面积判别的上下限。该上下限的值也是需要实验得到
    maxarea = max(s)
    area1 = maxarea-4200
    area2 = maxarea+2000
    #  精过滤,利用面积来筛选目标。因为上面已经粗过滤了一次,所以这里循环的次数就是s的长度
    for i in range(len(s)):
        #  轮廓在面积上下限之间的才被认为是目标
        if s1[b[i]] > area1 and s1[b[i]] < area2:
            rect = cv2.minAreaRect(contours[b[i]])
            #  rect格式不是cv2.drawContours要求的格式,所以需要用cv2.boxPoints将其转换一下
            points = cv2.boxPoints(rect)
            #  将其转化为整型,不然报错
            points = np.int0(points)
            o = cv2.drawContours(o,[points],0,(0,0,255),4)
            rects.append(rect)
    #  显示二值化之后的图
    # cv2.namedWindow('binary',cv2.WINDOW_GUI_NORMAL)
    # cv2.imshow('binary',binary)
    return rects,o


if __name__ == '__main__':
    #  用于匹配的图
    img = cv2.imread(r"C:\Users\Administrator\Desktop\boltimg\9.jpg")
    #  生成模板轮廓
    template = tem(r"C:\Users\Administrator\Desktop\boltimg\tem.jpg")
    o = img.copy()
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

    # _,binaryori = cv2.threshold(gray,210,255,cv2.THRESH_BINARY)
    #  减弱光照不均匀的影响
    k = np.ones((101,101),np.uint8)
    opend = cv2.morphologyEx(gray,cv2.MORPH_OPEN,k)
    addimg = cv2.addWeighted(gray,1,opend,-1,100)
    #  调用detect函数检测匹配图中的螺丝
    rects,objimg = detect(addimg,o,template)
    #  打印检测出的目标形心和旋转角度
    for i in range(len(rects)):
        print('第%s个目标中心\n' % str(i+1))
        print(rects[i][0])
        print('\n')
        print('第%s个目标旋转角度\n' % str(i+1))
        print(rects[i][2])
    #  显示原图
    cv2.namedWindow('original',cv2.WINDOW_GUI_NORMAL)
    cv2.imshow('original',img)
    #  显示检测成功图
    cv2.namedWindow('objimg',cv2.WINDOW_GUI_NORMAL)
    cv2.imshow('objimg',objimg)
    
    #  显示由灰度图直接二值化后的二值图,调试时需要
    # cv2.namedWindow('binaryori',cv2.WINDOW_GUI_NORMAL)
    # cv2.imshow('binaryori',binaryori)
    #  显示开操作之后的图像
    # cv2.namedWindow('open',cv2.WINDOW_GUI_NORMAL)
    # cv2.imshow('open',opend)
    # #  显示光照均匀化之后的图像
    # cv2.namedWindow('uniform',cv2.WINDOW_GUI_NORMAL)
    # cv2.imshow('uniform',addimg)
    
    cv2.waitKey()
    cv2.destroyAllWindows()

总结

笔者设计了一种基于形状匹配的的螺丝识别算法,在螺丝识别中提出了两点改进。视觉系统识别精度较高。但系统还有不足之处,比如,当流水线上螺丝互相靠到了一起,那该算法就无法精确地定位各个螺丝位置,还有待改进。
写算法不容易,希望各位能关注点赞一波。

猜你喜欢

转载自blog.csdn.net/bookshu6/article/details/111661839