赞
踩
先上最终检测视频:
OpenCV实时检测圆孔
本文重在实操,就不赘述背景意义等内容。
给定问题:假如有一个配件,此时需要检测配件上的圆孔坐标、半径、个数、圆孔到配件四边的距离。
由于没有实物,就在纸上画一个方框,表示配件边缘,花几个圆,表示圆孔:
首先导入要用到的库:
- 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计算边界框的时候,会出现很多个边界框,导致后面计算圆孔到边框的距离时,会有问题,因为可能计算的是自己到自己的距离,例如:
- 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。
小结:调参挺繁琐,慢慢调吧。
如有新的想法,期待交流探讨
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。