自动摆盘机之二维码处理算法


背景

项目中,有个摆盘机器,要摆放模块到盘里去,要求摆放方向一致,要识别二维码内容,进行校验。

难点

  • 根据模块的特征,确定模块的正放位置,其他位置要控制机器旋转

    • 一种算法是,拍照根据二维码的三个定位点,构成等腰直角三角形,来确定摆放位置(暂时使用这种算法)
    • 另一种是yolo5训练模型,然后是识别特征点,根据特征点的相对位置去确定摆放位置
  • 代码控制通过PLC编程,控制机器摆放,丢弃不良品操作
  • 逻辑控制,拍照,识别,调动机器,数量统计等

实际操作

最终目前采用的方案是,识别二维码的定位点,要求稍微高一些(要求拍照的图片清晰)

  • 拍照,去优化图片(灰度化,二值化,二域化,高斯模糊等)
  • 提取符合条件的定位点,计算符合等腰直角三角形的三个点坐标
  • 根据三个点坐标的位置,计算跟正位相比较,需要旋转多少度,然后发给机器指令,去控制旋转
  • 识别二维码内容,校验,不合格的,发出不良品指令,二维码没有识别出来或旋转角度没有计算出来,也做不良品处理,进入后面的人工检查阶段

核心代码

拍照

使用了Python的OpenCV库

  1. def loop(self):
  2. if not os.path.exists(self.OUTPUT_DIR):
  3. os.mkdir(self.OUTPUT_DIR)
  4. start = time.perf_counter()
  5. # 调用摄像头
  6. camera = cv2.VideoCapture(0)
  7. # 读取当前帧
  8. ret, frame = camera.read()
  9. # 转为灰度图像
  10. gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  11. img_path = self.OUTPUT_DIR + time.strftime("%Y%m%d-%H-%M-%S", time.localtime(time.time())) + '.jpg'
  12. cv2.imwrite(img_path, gray)
  13. # 如果运行结束,释放摄像头
  14. camera.release()
  15. cv2.destroyAllWindows()
  16. end = time.perf_counter()
  17. print(end-start)
  18. return img_path

图片处理

  1. def detect(self, image):
  2. """
  3. 提取所有轮廓,图片做一些预处理
  4. :param image: 图片
  5. :return: 轮廓集和轮廓嵌套索引集
  6. """
  7. # TODO 目前方案
  8. """
  9. 1. 拉普算子边缘检测+二域化
  10. 2. 自适应直方图均衡化+二域化
  11. 3. 自适应二域化
  12. """
  13. # 转为灰度值
  14. gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  15. # 高斯滤波
  16. blur = cv2.GaussianBlur(gray, (3, 3), 3)
  17. # # 对比度增强
  18. # scale = cv2.convertScaleAbs(gray, alpha=1.5, beta=0)
  19. # 自适应直方图均衡化
  20. clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(3, 3))
  21. res = clahe.apply(blur)
  22. # # 二域化
  23. # _, thresh = cv2.threshold(
  24. # res, 127, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)
  25. # 自适应二域化
  26. thresh = cv2.adaptiveThreshold(res, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 5, 3)
  27. """
  28. findContours方法,接受的是二值图,所以图片要经过灰度图,再二值化
  29. """
  30. contours, hierachy = cv2.findContours(
  31. thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
  32. # cv2.imshow('gray', gray)
  33. # cv2.imshow('thresh', thresh)
  34. # cv2.waitKey(0)
  35. return contours, hierachy

寻找二维码的定位点

  1. def juge_angle(self, rec):
  2. """
  3. 判断寻找是否有三个点可以围成等腰直角三角形
  4. :param rec: 包含轮廓点坐标的列表
  5. :return: 返回三个轮廓序号
  6. """
  7. if len(rec) < 3:
  8. return -1, -1, -1
  9. for i in range(len(rec)):
  10. for j in range(i + 1, len(rec)):
  11. for k in range(j + 1, len(rec)):
  12. distance_1 = np.sqrt(
  13. (rec[i][0] - rec[j][0]) ** 2 + (rec[i][1] - rec[j][1]) ** 2)
  14. distance_2 = np.sqrt(
  15. (rec[i][0] - rec[k][0]) ** 2 + (rec[i][1] - rec[k][1]) ** 2)
  16. distance_3 = np.sqrt(
  17. (rec[j][0] - rec[k][0]) ** 2 + (rec[j][1] - rec[k][1]) ** 2)
  18. if abs(distance_1 - distance_2) < 3:
  19. if abs(
  20. np.sqrt(
  21. np.square(distance_1) +
  22. np.square(distance_2)) -
  23. distance_3) < 2:
  24. # print(abs(np.sqrt(np.square(distance_1)+np.square(distance_2)))-distance_3)
  25. return i, j, k
  26. elif abs(distance_1 - distance_3) < 3:
  27. if abs(
  28. np.sqrt(
  29. np.square(distance_1) +
  30. np.square(distance_3)) -
  31. distance_2) < 2:
  32. # print(abs(np.sqrt(np.square(distance_1) + np.square(distance_3))) - distance_2)
  33. return i, j, k
  34. elif abs(distance_2 - distance_3) < 3:
  35. if abs(
  36. np.sqrt(
  37. np.square(distance_2) +
  38. np.square(distance_3)) -
  39. distance_1) < 2:
  40. # print(abs(np.sqrt(np.square(distance_2) + np.square(distance_3))) - distance_1)
  41. return i, j, k
  42. return -1, -1, -1
  43. def compute_1(self, contours, i, j):
  44. """
  45. 最外面的轮廓和子轮廓的比例
  46. :param contours: 轮廓集
  47. :param i: 最外层轮廓序号
  48. :param j: 子轮廓序号
  49. :return: True or False
  50. """
  51. area1 = cv2.contourArea(contours[i])
  52. area2 = cv2.contourArea(contours[j])
  53. if area2 == 0:
  54. return False
  55. ratio = area1 * 1.0 / area2
  56. if abs(ratio - 49.0 / 25):
  57. return True
  58. return False
  59. def compute_2(self, contours, i, j):
  60. """
  61. 子轮廓和子子轮廓的比例
  62. :param contours: 轮廓集
  63. :param i: 子轮廓序号
  64. :param j: 子子轮廓序号
  65. :return: True or False
  66. """
  67. area1 = cv2.contourArea(contours[i])
  68. area2 = cv2.contourArea(contours[j])
  69. if area2 == 0:
  70. return False
  71. ratio = area1 * 1.0 / area2
  72. if abs(ratio - 25.0 / 9):
  73. return True
  74. return False
  75. def contour_center(self, contours, i):
  76. """
  77. 计算轮廓的中心点坐标
  78. :param contours: 轮廓
  79. :param i: 包含轮廓关系的点
  80. :return: 坐标
  81. """
  82. M = cv2.moments(contours[i])
  83. cx = int(M['m10'] / M['m00'])
  84. cy = int(M['m01'] / M['m00'])
  85. return cx, cy
  86. def detect_contours(self, vec):
  87. """
  88. 判断这个轮廓和它的子轮廓以及子子轮廓的中心的间距是否足够小
  89. :param vec: 轮廓集
  90. :return: True or False
  91. """
  92. distance_1 = np.sqrt((vec[0] - vec[2]) ** 2 + (vec[1] - vec[3]) ** 2)
  93. distance_2 = np.sqrt((vec[0] - vec[4]) ** 2 + (vec[1] - vec[5]) ** 2)
  94. distance_3 = np.sqrt((vec[2] - vec[4]) ** 2 + (vec[3] - vec[5]) ** 2)
  95. if sum((distance_1, distance_2, distance_3)) / 3 < 3:
  96. return True
  97. return False
  98. def find(self, contours, hierachy):
  99. """
  100. 找到符合条件的轮廓点
  101. :param contours: 未经筛选的点集
  102. :param hierachy: 包含轮廓关系的数据
  103. :return: 返回二维码的三个定位点坐标
  104. """
  105. data = []
  106. rec = []
  107. for i in range(len(hierachy)):
  108. child = hierachy[i][2]
  109. child_child = hierachy[child][2]
  110. if child != -1 and child_child != -1:
  111. if self.compute_1(contours, i, child) and self.compute_2(contours, child, child_child):
  112. cx1, cy1 = self.contour_center(contours, i)
  113. cx2, cy2 = self.contour_center(contours, child)
  114. cx3, cy3 = self.contour_center(contours, child_child)
  115. if self.detect_contours([cx1, cy1, cx2, cy2, cx3, cy3]):
  116. rec.append([cx1, cy1, cx2, cy2, cx3,
  117. cy3, i, child, child_child])
  118. # print(rec)
  119. i, j, k = self.juge_angle(rec)
  120. if i == -1 or j == -1 or k == -1:
  121. return
  122. data.append(rec[i][0:2])
  123. data.append(rec[j][0:2])
  124. data.append(rec[k][0:2])
  125. return data

二维码识别

二维码识别使用了腾讯微信开源的识别接口,效果很好

  1. def __init__(self):
  2. # 腾讯开发wechat_qrcode配置文件
  3. self.depro = './config/detect.prototxt'
  4. self.decaf = './config/detect.caffemodel'
  5. self.srpro = './config/sr.prototxt'
  6. self.srcaf = './config/sr.caffemodel'
  7. def get_qrcode_info(self, img_path):
  8. """
  9. : param img_path: 图片路径
  10. : return: 图片二维码的内容
  11. """
  12. img = cv2.imread(img_path)
  13. # 转为灰度图像
  14. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  15. # 识别二维码,wechat_qrcode
  16. detector = cv2.wechat_qrcode_WeChatQRCode(self.depro, self.decaf, self.srpro, self.srcaf)
  17. barcodes, point = detector.detectAndDecode(gray)
  18. for qrcode_info in barcodes:
  19. barcode_data = qrcode_info
  20. return barcode_data

存在的问题

  • 条件还是比较苛刻,然后效率不是很高,识别速度还算可以,调用摄像头比较慢,机器的交互比较慢
  • 成熟yolo5算法和二维码识别的算法封装的都比较好,很难去使用别人更优秀的算法