利用Python OpenCV进行圆孔检测、计算半径、圆心到四边距离

先上最终检测视频:

OpenCV实时检测圆孔

本文重在实操,就不赘述背景意义等内容。

给定问题:假如有一个配件,此时需要检测配件上的圆孔坐标、半径、个数、圆孔到配件四边的距离。

由于没有实物,就在纸上画一个方框,表示配件边缘,花几个圆,表示圆孔:

利用Python OpenCV进行圆孔检测、计算半径、圆心到四边距离_第1张图片

首先导入要用到的库:

import cv2
import numpy as np

接着定义一些小工具:

##### 配置摄像头捕捉框的参数  #####
# 分辨率
framewidth = 640 
frameheight = 480
cap = cv2.VideoCapture(0)
cap.set(3,framewidth)
cap.set(4,frameheight)
​
def empty(a):
    pass
    
# 使用Trackbar调参,效率提高一大截
cv2.namedWindow('param')
cv2.createTrackbar('thresh1','param',150,255,empty) # cv2.Canny参数
cv2.createTrackbar('thresh2','param',255,255,empty) # cv2.Canny参数
cv2.createTrackbar('area','param',20,50000,empty) # cv2.contourArea参数
cv2.createTrackbar('param1','param',1,100,empty) # cv2.HoughCircles参数​
cv2.createTrackbar('param2','param',1,100,empty) # cv2.HoughCircles参数

接着定义一个函数,使得能够在同一个窗口显示处理后的图片:

def stackImages(scale,imgArray):
    rows = len(imgArray)
    cols = len(imgArray[0])
    rowsAvailable = isinstance(imgArray[0], list)
    width = imgArray[0][0].shape[1]
    height = imgArray[0][0].shape[0]
    if rowsAvailable:
        for x in range ( 0, rows):
            for y in range(0, cols):
                if imgArray[x][y].shape[:2] == imgArray[0][0].shape [:2]:
                    imgArray[x][y] = cv2.resize(imgArray[x][y], (0, 0), None, scale, scale)
                else:
                    imgArray[x][y] = cv2.resize(imgArray[x][y], (imgArray[0][0].shape[1], imgArray[0][0].shape[0]), None, scale, scale)
                if len(imgArray[x][y].shape) == 2: imgArray[x][y]= cv2.cvtColor( imgArray[x][y], cv2.COLOR_GRAY2BGR)
        imageBlank = np.zeros((height, width, 3), np.uint8)
        hor = [imageBlank]*rows
        hor_con = [imageBlank]*rows
        for x in range(0, rows):
            hor[x] = np.hstack(imgArray[x])
        ver = np.vstack(hor)
    else:
        for x in range(0, rows):
            if imgArray[x].shape[:2] == imgArray[0].shape[:2]:
                imgArray[x] = cv2.resize(imgArray[x], (0, 0), None, scale, scale)
            else:
                imgArray[x] = cv2.resize(imgArray[x], (imgArray[0].shape[1], imgArray[0].shape[0]), None,scale, scale)
            if len(imgArray[x].shape) == 2: imgArray[x] = cv2.cvtColor(imgArray[x], cv2.COLOR_GRAY2BGR)
        hor= np.hstack(imgArray)
        ver = hor
    return ver

接着定义获取轮廓信息的函数:

这里要讲一下,if area > areamin: 和if w > 40 and w < 400: 这两个地方,由于cv2.findContours不仅会获取配件(矩形框)的轮廓,还会获取其它任何形状的轮廓,包括背景、圆孔的轮廓,从而使用cv2.boundingRect计算边界框的时候,会出现很多个边界框,导致后面计算圆孔到边框的距离时,会有问题,因为可能计算的是自己到自己的距离,例如:

利用Python OpenCV进行圆孔检测、计算半径、圆心到四边距离_第2张图片

def getContours(img,imgcontour):
    # 第二个参数挺重要,由于仙子只想获取矩形框的轮廓,所以要用RETR_EXTERNAL
    _,contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
​
    # 存储矩形框的长宽和坐标
    xy = [] # 左上坐标
    wh = [] # 右下坐标为x+w,y+h
​
    for i, cnt in enumerate(contours):
        area = cv2.contourArea(cnt)
        areamin = cv2.getTrackbarPos('area','param')
        if area > areamin: # 通过面积来移除一些噪声造成的误检
​
            peri = cv2.arcLength(cnt,True) # True指轮廓是闭合的
            approx = cv2.approxPolyDP(cnt, 0.02*peri, True)
            x, y, w, h = cv2.boundingRect(approx) # 获取矩形框的参数
​
            if w > 40 and w < 400: # 只寻找宽在这个范围内的矩形框
                cv2.drawContours(imgcontour, cnt, -1, (255, 0, 255), 3) # 绘制轮廓
                cv2.rectangle(imgcontour, (x, y),(x+w, y+h), (0,255,0), 3) # 绘制边界框
                # cv2.putText用来在窗口显示信息
                cv2.putText(imgcontour, '(x1,y1):'+'('+str(x)+','+str(y)+')', (5, 30), cv2.FONT_HERSHEY_COMPLEX, 0.7,
                            (0, 0, 0), 2)
                cv2.putText(imgcontour, '(x2,y2):'+'('+str(x+w)+','+str(y+h)+')', (5, 90), cv2.FONT_HERSHEY_COMPLEX, 0.7,
                            (0, 0, 0), 2)
                cv2.putText(imgcontour, 'h:'+str(int(h)), (5,150), cv2.FONT_HERSHEY_COMPLEX, 0.7,
                            (0,0,0), 2)
                cv2.putText(imgcontour, 'w:'+str(int(w)), (5,210), cv2.FONT_HERSHEY_COMPLEX, 0.7,
                            (0, 0, 0), 2)
                # cv2.putText(imgcontour, 'area:' + str(int(area)), (100, 200), cv2.FONT_HERSHEY_COMPLEX, 0.7,
                #             (0, 0, 0), 2)
                xy.append((x,y))
                wh.append((w,h))
​
    return xy, wh

接着,定义主函数:

这里要注意,OpenCV计算圆孔大小、距离等指标时,只是计算像素有多少,而不会计算真实的距离,从而需要根据圆孔的真实大小和对应的像素大小计算一个比例系数,例如,圆孔真实半径为3mm,对应图像上的像素为r,那么需要计算比例系数factor_c=r/3,从而在计算其它圆孔的真实尺寸时,只需用r/factor_c即可。

def main():
​
    while True:
        # sucess, image = cap.read() # 要用摄像头时,用这个
        image = cv2.imread('D:/14.jpg') # 用本地图片检测
        imgContour = image.copy()
​
        # 图像预处理
        imgBlur = cv2.GaussianBlur(image, (5, 5), 1) # 高斯模糊
        imgGray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 转为灰度,减少剔除不必要的信息,从而进行轮廓检测
​
        # 获取最外面的轮廓
        thresh1 = cv2.getTrackbarPos('thresh1', 'param')
        thresh2 = cv2.getTrackbarPos('thresh2', 'param')
        imgCanny = cv2.Canny(imgGray, thresh1, thresh2) # 轮廓检测
        kernel = np.ones((3, 3))
        imgDil = cv2.dilate(imgCanny, kernel, iterations=1) # 膨胀,把边界变粗,有利于检测轮廓
​
        xy, wh = getContours(imgDil, imgContour)
        print('xy',xy)
        print('wh:',(wh))
​
        # 单独用霍夫圆检测圆圈
        param1= cv2.getTrackbarPos('param1', 'param')
        param2 = cv2.getTrackbarPos('param2', 'param')
        maxRadius = cv2.getTrackbarPos('maxRadius', 'param')
        # minDist = cv2.getTrackbarPos('minDist', 'param')
        circles = cv2.HoughCircles(imgDil, cv2.HOUGH_GRADIENT, 1, minDist=30, param1=param1, param2=param2, minRadius=0, maxRadius=100)
        detect_circles = np.uint16(np.around(circles))
​
        distances = []
        for (x,y,r) in detect_circles[0, :]:
            cv2.circle(imgContour, (x, y), r, (0, 255, 0), 3)
            # cv2.circle(imgContour, (x, y), 2, (0, 255, 255), 2)
            # area = 3.14159 * r * r
            if len(xy) == 0: # 实时检测时,要加上这句,不然只要没有检测到圆孔就会停止,此时可能只是还没有对齐摄像头
                continue
            print(x,'\n',y,'\n',r)
            # 获取圆心到矩形四边的距离,需要令最小为0,因为当圆孔边缘贴着矩形一边时,可能会因为误差导致检测到的距离为负数
            d_up = max(0, (y - xy[0][1]))
            d_down = max(0, (xy[0][1] + wh[0][1] - y))
            d_left = max(0, (x - xy[0][0]))
            d_right = max(0, (xy[0][0] + wh[0][0] - x))
​
            distances.append((d_up, d_down, d_left, d_right))
​
            factor_c = 18.666 # 比例因子
​
            cv2.putText(imgContour, 'r:' + str(round(int(r)/factor_c,2)), (x+20, y+80), cv2.FONT_HERSHEY_COMPLEX, 0.7,
                        (0, 0, 0), 2)
            cv2.putText(imgContour, 'd:'+'('+str(round(d_up/factor_c,2))+','+str(round(d_down/factor_c,2))+','+
                        str(round(d_left/factor_c,2))+','+str(round(d_right/factor_c,2))+')', (x+30, y+160), 
                        cv2.FONT_HERSHEY_COMPLEX, 0.7,(0, 0, 0), 2)
        print('nums=',len(distances))
​
        # 显示新图像
        imgStack = stackImages(0.8, [imgContour, imgDil])
        cv2.namedWindow('image', cv2.WINDOW_NORMAL)
        cv2.resizeWindow("image", 900, 600)
        cv2.imshow('image', imgStack)
​
        # 按任意键退出
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
​
        print('distances:\n',distances)
    #     break
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()
​
if __name__ == '__main__':
    main()

最终效果1:可以看到误差比较小,效果还是挺好的。

注:真实圆孔大小半径为3mm,矩形框为30×40mm。

利用Python OpenCV进行圆孔检测、计算半径、圆心到四边距离_第3张图片

小结:调参挺繁琐,慢慢调吧。


如有新的想法,期待交流探讨

你可能感兴趣的:(机器学习/深度学习,计算机视觉,图像处理,opencv,python)