圆口缺陷检测

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/renegade_m/article/details/82292313

目标:圆形物体缺陷检测,见下图,右边物体的右上角部分缺了一块,为不合格产品。

主要思想是通过坐标转换将圆形物体“拉”成方形,再经过均值滤波后与其相减便可得到缺口区域。如下:

                                                                       

从左至右依次是从图中抠出来的圆环,经过坐标系转换后的目标图像,对转换后的图像进行均值滤波,最后(2)(3)相减。

根据相减后的连通域的面积判断产品是否合格。

在halcon的例程中有一个这样的例子,实现并不难,弄明白笛卡尔坐标到极坐标转换和线性插值即可。

坐标转换套公式:

                                                                  x =\rho cos\Theta , y=\rho sin\Theta

在目标图像中,以x方向为半径,即\rho,以y方向为角度,即\Theta。以目标图像中的\rho\Theta的值计算出其在原图中的位置,得出其像素值。

def linearPolar(src, center, radius):
    '''
    x方向为半径(column) 
    y方向为角度(row), from 0 to 2 * pi 
    '''
    dst = np.zeros(src.shape, dtype=np.uint8)
    ascale, pscale = (2 * np.pi) / src.shape[1], radius / src.shape[0]
    rho = list(map(lambda x : x * pscale, range(src.shape[1])))
    for row in range(src.shape[0]):
        phi = row * ascale
        for col in range(src.shape[1]):
            #calculate coordinate in src
            x, y = rho[col] * cos(phi), rho[col] * sin(phi)        
            #calculate the gray value at point(col, row) by bilinear Interpolation
            dst[row][col] = bilinearInterpolation(src, x + center[0], y + center[1])
    return dst

需要注意的是求出的x,y并不一定为整数,为保证准确度,可以用线性插值的方法来进行计算。

说白了线性插值就是,已知一条直线,及任意一点的x坐标,求这个点的y坐标(详见维基百科)。

我这里用的是双线性插值,原理类似。代码如下:

def bilinearInterpolation(img, src_x, src_y):
    val = 0
    lCoor, rCoor = int(np.floor(src_x)), int(np.ceil(src_x))
    tCoor, bCoor = int(np.floor(src_y)), int(np.ceil(src_y))

    if lCoor < 0 or rCoor < 0 or tCoor < 0 or bCoor < 0:
        return 0 
    #print(lCoor, rCoor, tCoor, bCoor)
    if lCoor == rCoor and tCoor != bCoor:   #deal with rim in y-axis
        val1, val2 = img[tCoor][lCoor], img[bCoor][lCoor]
        val = (bCoor - src_y) * val1 + (src_y - lCoor) * val2
    elif tCoor == bCoor and lCoor != rCoor: #deal with rim in x-axis
        val1, val2 = img[tCoor][lCoor], img[tCoor][rCoor]
        val = (rCoor - src_x) * val1 + (src_x - lCoor) * val2
    else:                                   #bilinear 
        val11, val12 = img[tCoor][lCoor], img[tCoor][rCoor]
        val21, val22 = img[bCoor][lCoor], img[bCoor][rCoor]
        a, b, c, d = rCoor - src_x, src_x - lCoor, bCoor - src_y, src_y - tCoor
        aa, bb = a * val11 + b * val21, a * val12 + b * val22
        val = aa * c + bb * d 
    return val

其他部分:

dyn_threshold()这个函数用过halcon的肯定不会陌生,感觉挺好用的,于是根据函数说明重新写了一个,没有测试过(还是懒)。结果发现这个函数跟cv::absdiff()好像是差不多的一个意思。 

另外linearPolar函数在opencv中已经有了现成的,直接拿来用就好。看了一下opencv的源码实现,模仿着写了一个,效果惨不忍睹,关键是还不知道是哪里的问题,可惜找不到那段代码了,不然也贴上来。

我在代码里还把产品掏成了圆环,多此一举没必要,但是也懒得改了,纯当练手。

def dyn_threshold(OrigImage, ThresholdImage, Offset, LightDark):
    ret = np.zeros(OrigImage.shape, np.uint8)

    def threshold(OrigImage, ThresholdImage, Expr):
        for i in range(OrigImage.shape[0]):
            for j in range(OrigImage.shape[1]):
                ret[i][j] = Expr(OrigImage[i][j], ThresholdImage[i][j])
        
    if 'light' == LightDark:
        threshold(OrigImage, ThresholdImage, lambda o, t : 255 if o >= t + Offset else 0)
    elif 'dark' == LightDark:
        threshold(OrigImage, ThresholdImage, lambda o, t : 255 if o <= t - Offset else 0)
    elif 'equal' == LightDark: 
        threshold(OrigImage, ThresholdImage, lambda o, t : 255 if o >= t + Offset and o <= t - Offset else 0)
    elif 'not_equal' == LightDark:  #gt - offset > go and go > gt + offset
        threshold(OrigImage, ThresholdImage, lambda o, t : 255 if t - Offset > o or o > t + Offset else 0)
    else:
        pass

    return ret

def imageProcess(img):
    thresh, process = cv.threshold(img, 0, 255, cv.THRESH_OTSU)
    _, contours, hierarchy = cv.findContours(process, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)
    plt.figure()
    n = 1
    for i, contour in enumerate(contours):
        center, radius = cv.minEnclosingCircle(contour)
        if radius < 100 or radius > 300:
            continue
        br = cv.boundingRect(contour)
        roi = [0, 0, 0, 0]
        offset, widen = 15, 30
        inner_radius = 100 
        roi[0], roi[1], roi[2], roi[3] = br[0] - offset, br[1] - offset, br[2] + widen, br[3] + widen
        product = np.copy(process[roi[1] : roi[1] + roi[3] + 1, roi[0] : roi[0] + roi[2] + 1])
        inner = np.zeros(product.shape, np.uint8)
        newCenter = (int(center[0] - roi[0]), int(center[1] - roi[1]))
        cv.circle(inner, newCenter, inner_radius, (255), 1, 8)
        cv.floodFill(inner, np.array([]), newCenter, 255)
        annular = cv.bitwise_or(inner, product)
        newCenter = (center[0] - roi[0], center[1] - roi[1])
        polarAnnular = linearPolar(annular, newCenter, radius)
        #polarAnnular = cv.linearPolar(annular, newCenter, radius, cv.INTER_LINEAR + cv.WARP_FILL_OUTLIERS)
        meanImage = cv.blur(polarAnnular, (3, 271))
        dyned = dyn_threshold(polarAnnular, meanImage, 70, 'not_equal')
        _, contoursDyn, _ = cv.findContours(dyned, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)
        for j, contourDyn in enumerate(contoursDyn):
            brDyn = cv.boundingRect(contourDyn)
            if brDyn[2] < 20:  #宽度筛选
                continue
            cv.putText(img, str(n) + "NG", (br[0] - offset, br[1] - offset), cv.FONT_HERSHEY_PLAIN, 8, (0), 3)
            break
        else:
            cv.putText(img, str(n) + "OK", (br[0] - offset, br[1] - offset), cv.FONT_HERSHEY_PLAIN, 8, (0), 3)

        plt.subplot(4, 10, n)
        plt.imshow(polarAnnular)
        plt.subplot(4, 10, n + 10)
        plt.imshow(annular)
        plt.subplot(4, 10, n + 20)
        plt.imshow(dyned)
        plt.subplot(4, 10, n + 30)
        plt.imshow(meanImage)
        n = n + 1

    plt.figure()
    plt.imshow(img)
    plt.show()
    
if __name__ == '__main__':
    img = cv.imread(".\\2018-08-12_18_00_51_957.bmp", cv.IMREAD_GRAYSCALE)
    #img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
    imageProcess(img)

开发环境是python3+opencv3.4.1,感觉代码应该还有不少优化空间,过段时间再回来看看。

猜你喜欢

转载自blog.csdn.net/renegade_m/article/details/82292313