自动摆盘机之二维码处理算法
背景
项目中,有个摆盘机器,要摆放模块到盘里去,要求摆放方向一致,要识别二维码内容,进行校验。
难点
根据模块的特征,确定模块的正放位置,其他位置要控制机器旋转
- 一种算法是,拍照根据二维码的三个定位点,构成等腰直角三角形,来确定摆放位置(暂时使用这种算法)
- 另一种是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, -1
for 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, k
elif 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, k
elif 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, k
return -1, -1, -1
def 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 False
ratio = area1 * 1.0 / area2
if abs(ratio - 49.0 / 25):
return True
return False
def 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 False
ratio = area1 * 1.0 / area2
if abs(ratio - 25.0 / 9):
return True
return False
def 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, cy
def 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 True
return False
def 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:
return
data.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_qrcode
detector = 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_info
return barcode_data
存在的问题
- 条件还是比较苛刻,然后效率不是很高,识别速度还算可以,调用摄像头比较慢,机器的交互比较慢
- 成熟yolo5算法和二维码识别的算法封装的都比较好,很难去使用别人更优秀的算法