先上最终检测视频:
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。
小结:调参挺繁琐,慢慢调吧。
如有新的想法,期待交流探讨