自动摆盘机之二维码处理算法
背景
项目中,有个摆盘机器,要摆放模块到盘里去,要求摆放方向一致,要识别二维码内容,进行校验。
难点
根据模块的特征,确定模块的正放位置,其他位置要控制机器旋转
- 一种算法是,拍照根据二维码的三个定位点,构成等腰直角三角形,来确定摆放位置(暂时使用这种算法)
- 另一种是yolo5训练模型,然后是识别特征点,根据特征点的相对位置去确定摆放位置
- 代码控制通过PLC编程,控制机器摆放,丢弃不良品操作
- 逻辑控制,拍照,识别,调动机器,数量统计等
实际操作
最终目前采用的方案是,识别二维码的定位点,要求稍微高一些(要求拍照的图片清晰)
- 拍照,去优化图片(灰度化,二值化,二域化,高斯模糊等)
- 提取符合条件的定位点,计算符合等腰直角三角形的三个点坐标
- 根据三个点坐标的位置,计算跟正位相比较,需要旋转多少度,然后发给机器指令,去控制旋转
- 识别二维码内容,校验,不合格的,发出不良品指令,二维码没有识别出来或旋转角度没有计算出来,也做不良品处理,进入后面的人工检查阶段
核心代码
拍照
使用了Python的OpenCV库
def loop(self):if not os.path.exists(self.OUTPUT_DIR):os.mkdir(self.OUTPUT_DIR)start = time.perf_counter()# 调用摄像头camera = cv2.VideoCapture(0)# 读取当前帧ret, frame = camera.read()# 转为灰度图像gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)img_path = self.OUTPUT_DIR + time.strftime("%Y%m%d-%H-%M-%S", time.localtime(time.time())) + '.jpg'cv2.imwrite(img_path, gray)# 如果运行结束,释放摄像头camera.release()cv2.destroyAllWindows()end = time.perf_counter()print(end-start)return img_path
图片处理
def detect(self, image):"""提取所有轮廓,图片做一些预处理:param image: 图片:return: 轮廓集和轮廓嵌套索引集"""# TODO 目前方案"""1. 拉普算子边缘检测+二域化2. 自适应直方图均衡化+二域化3. 自适应二域化"""# 转为灰度值gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 高斯滤波blur = cv2.GaussianBlur(gray, (3, 3), 3)# # 对比度增强# scale = cv2.convertScaleAbs(gray, alpha=1.5, beta=0)# 自适应直方图均衡化clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(3, 3))res = clahe.apply(blur)# # 二域化# _, thresh = cv2.threshold(# res, 127, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)# 自适应二域化thresh = cv2.adaptiveThreshold(res, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 5, 3)"""findContours方法,接受的是二值图,所以图片要经过灰度图,再二值化"""contours, hierachy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# cv2.imshow('gray', gray)# cv2.imshow('thresh', thresh)# cv2.waitKey(0)return contours, hierachy
寻找二维码的定位点
def juge_angle(self, rec):"""判断寻找是否有三个点可以围成等腰直角三角形:param rec: 包含轮廓点坐标的列表:return: 返回三个轮廓序号"""if len(rec) < 3:return -1, -1, -1for i in range(len(rec)):for j in range(i + 1, len(rec)):for k in range(j + 1, len(rec)):distance_1 = np.sqrt((rec[i][0] - rec[j][0]) ** 2 + (rec[i][1] - rec[j][1]) ** 2)distance_2 = np.sqrt((rec[i][0] - rec[k][0]) ** 2 + (rec[i][1] - rec[k][1]) ** 2)distance_3 = np.sqrt((rec[j][0] - rec[k][0]) ** 2 + (rec[j][1] - rec[k][1]) ** 2)if abs(distance_1 - distance_2) < 3:if abs(np.sqrt(np.square(distance_1) +np.square(distance_2)) -distance_3) < 2:# print(abs(np.sqrt(np.square(distance_1)+np.square(distance_2)))-distance_3)return i, j, kelif abs(distance_1 - distance_3) < 3:if abs(np.sqrt(np.square(distance_1) +np.square(distance_3)) -distance_2) < 2:# print(abs(np.sqrt(np.square(distance_1) + np.square(distance_3))) - distance_2)return i, j, kelif abs(distance_2 - distance_3) < 3:if abs(np.sqrt(np.square(distance_2) +np.square(distance_3)) -distance_1) < 2:# print(abs(np.sqrt(np.square(distance_2) + np.square(distance_3))) - distance_1)return i, j, kreturn -1, -1, -1def compute_1(self, contours, i, j):"""最外面的轮廓和子轮廓的比例:param contours: 轮廓集:param i: 最外层轮廓序号:param j: 子轮廓序号:return: True or False"""area1 = cv2.contourArea(contours[i])area2 = cv2.contourArea(contours[j])if area2 == 0:return Falseratio = area1 * 1.0 / area2if abs(ratio - 49.0 / 25):return Truereturn Falsedef compute_2(self, contours, i, j):"""子轮廓和子子轮廓的比例:param contours: 轮廓集:param i: 子轮廓序号:param j: 子子轮廓序号:return: True or False"""area1 = cv2.contourArea(contours[i])area2 = cv2.contourArea(contours[j])if area2 == 0:return Falseratio = area1 * 1.0 / area2if abs(ratio - 25.0 / 9):return Truereturn Falsedef contour_center(self, contours, i):"""计算轮廓的中心点坐标:param contours: 轮廓:param i: 包含轮廓关系的点:return: 坐标"""M = cv2.moments(contours[i])cx = int(M['m10'] / M['m00'])cy = int(M['m01'] / M['m00'])return cx, cydef detect_contours(self, vec):"""判断这个轮廓和它的子轮廓以及子子轮廓的中心的间距是否足够小:param vec: 轮廓集:return: True or False"""distance_1 = np.sqrt((vec[0] - vec[2]) ** 2 + (vec[1] - vec[3]) ** 2)distance_2 = np.sqrt((vec[0] - vec[4]) ** 2 + (vec[1] - vec[5]) ** 2)distance_3 = np.sqrt((vec[2] - vec[4]) ** 2 + (vec[3] - vec[5]) ** 2)if sum((distance_1, distance_2, distance_3)) / 3 < 3:return Truereturn Falsedef find(self, contours, hierachy):"""找到符合条件的轮廓点:param contours: 未经筛选的点集:param hierachy: 包含轮廓关系的数据:return: 返回二维码的三个定位点坐标"""data = []rec = []for i in range(len(hierachy)):child = hierachy[i][2]child_child = hierachy[child][2]if child != -1 and child_child != -1:if self.compute_1(contours, i, child) and self.compute_2(contours, child, child_child):cx1, cy1 = self.contour_center(contours, i)cx2, cy2 = self.contour_center(contours, child)cx3, cy3 = self.contour_center(contours, child_child)if self.detect_contours([cx1, cy1, cx2, cy2, cx3, cy3]):rec.append([cx1, cy1, cx2, cy2, cx3,cy3, i, child, child_child])# print(rec)i, j, k = self.juge_angle(rec)if i == -1 or j == -1 or k == -1:returndata.append(rec[i][0:2])data.append(rec[j][0:2])data.append(rec[k][0:2])return data
二维码识别
二维码识别使用了腾讯微信开源的识别接口,效果很好
def __init__(self):# 腾讯开发wechat_qrcode配置文件self.depro = './config/detect.prototxt'self.decaf = './config/detect.caffemodel'self.srpro = './config/sr.prototxt'self.srcaf = './config/sr.caffemodel'def get_qrcode_info(self, img_path):""": param img_path: 图片路径: return: 图片二维码的内容"""img = cv2.imread(img_path)# 转为灰度图像gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 识别二维码,wechat_qrcodedetector = cv2.wechat_qrcode_WeChatQRCode(self.depro, self.decaf, self.srpro, self.srcaf)barcodes, point = detector.detectAndDecode(gray)for qrcode_info in barcodes:barcode_data = qrcode_inforeturn barcode_data
存在的问题
- 条件还是比较苛刻,然后效率不是很高,识别速度还算可以,调用摄像头比较慢,机器的交互比较慢
- 成熟yolo5算法和二维码识别的算法封装的都比较好,很难去使用别人更优秀的算法
