知识准备
进入案例前,要求具备基本的 python 代码使用能力,否则很可能看不懂代码的逻辑导致事倍功半。与此同时,要求了解pymycobot控制机械臂运动的大致流程,若还没有使用过 pymycobot 可以简单参考以下教程:pymycobot API参考文档、pymycobot教程。
由于案例的实现是基于 ROS 实现的,所以在进入教程之前需要简单了解ROS的一些工作原理。可以简单参考一下教程:ROS官方入门。
注意事项
镜像中的密码为:123
图片识别模型可识别的物体可查看文件:
~/catkin_mycobot/src/mycobot/mycobot_ai/功能包名称/scripts/labels.json
。Tips:功能包有三个,分别是 ai_mycobot_280, ai_mecharm_270, ai_mypalletizer_260。
若摄像头打开的电脑自带的,则需调整程序中的打开摄像头的参数 cap_num ,可选择修改为0或1。
颜色识别以及图像识别案例修改如下:
颜色和图像识别的时候 二维码白板 的实际摆放位置一定要按照下图,贴置正确。
1 吸泵连接
吸泵:
M5版本:连接机械臂左侧的 G2,G5,5V 和 GND 的引脚接口。
树莓派版本:杜邦线中的红、黑、黄、白线分别连接第一排引脚中的5V,GND,20,21引脚。
1.1 代码实现
接下来我们将使用代码来感受下吸泵的使用吧。
在 mycobot_ros 项目中,有一个 mycobot_ai 包,在包中的script中已将机械臂的一些动作封装了起来,其中包括了吸泵的使用。所以,只需在创建见类的时候继承它即可。在mycobot_ros项目中有给出一个吸泵使用案例,只需将所需的设备连接好后运行即可实现吸泵的使用。设备的连接请自行完成,接下来将以 mycobot_280 系列作为演示对象简述如何运行案例。
M5版本
下文以 <ros-workspace>
来代指电脑中ROS的工作空间路径,请确保在执行下面命令时将<ros-workspace>
替换为你本机的真实路径。
ls /dev/ttyUSB* # 查看机械臂设备名称
sudo chmod 777 /dev/ttyUSB0 # 给查看的设备赋予权限(默认设备名称为/dev/ttyUSB0)
roslaunch ai_mycobot_280 vision_m5.launch port:=/dev/ttyUSB0 baud:=115200 # 设备名称不是默认值时需修改port值
另外打开一个控制台终端(快捷键 Ctrl+Alt+T ),在命令行输入以下命令:
# 运行吸泵程序
python <ros-workspace>/src/mycobot/mycobot_ai/ai_mycobot_280/script/pump.py
修改 vision_m5.launch 中的内容,如下图所示,修改 port 的 default 值与查看到的机械臂设备名称保持一致。若不修改,可能会导致 vision_m5.launch 启动失败。
树莓派版本
吸泵控制:
# python3
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(20, GPIO.OUT)
GPIO.setup(21, GPIO.OUT)
# 打开吸泵
GPIO.output(20, 0)
GPIO.output(21, 0)
# 关闭吸泵
GPIO.output(20, 1)
GPIO.output(21, 1)
2 校准摄像头
不难知晓,摄像头的准确性对图像识别程序的重要性。为了确保摄像头不会出现图像失真严重的情况,可以通过摄像头拍摄国际象棋的棋盘,通过拍摄的棋盘图像计算摄像头的相机矩阵和失真系数。加载计算所得的相机矩阵和失真系数即可完成摄像头的校准。当然,由于使用使用平面摄像头,所以 教程中并未加载校准参数,若觉得校准很麻烦可直接跳过。
2.1 生成一张国际棋盘图
准备630*890的全黑图片,并将其重命名为3a4.bmp。运行以下程序即可获得一张国际象棋的棋盘图片。
#!/usr/bin/env python3
"""生成国际象棋棋盘图片"""
import os
import cv2
path = os.path.join(os.path.dirname(__file__), "3a4.bmp")
print(path)
frame = cv2.imread(path)
row, col, nc = frame.shape
width_of_roi = 90
# 这里是对全黑图片做处理,将图片以黑白间隔的形式zh
for j in range(row):
data = frame[j]
for i in range(col):
f = int(i / width_of_roi) % 2 ^ int(j / width_of_roi) % 2
if f:
frame[j][i][0] = 255
frame[j][i][1] = 255
frame[j][i][2] = 255
cv2.imshow("", frame)
cv2.waitKey(0) & 0xFF == ord("q")
cv2.imwrite(os.path.join(os.path.dirname(__file__), "1.png"), frame)
2.2 拍摄棋盘图片
将棋盘图片展示在电脑上,通过摄像头拍摄多张棋盘图片。
#!/usr/bin/env python3
"""
这是一个辅助文件,帮助校准相机。
它将调用相机,获取图片并实时显示。
您可以在控制台中输入任意值来保存图片。
"""
import os
import cv2
import threading
if_save = False
# 设置摄像头编号(由于电脑型号不同,分配给USB摄像头的编号也可能不同,一般为0或1)
cap_num = int(input("Input the camare number:"))
# 设置所存储的图片名称,设置为1,即表示从1开始累加存储。如:1.jpg,2.jpg,3.jpg......
name = int(input("Input start name, use number:"))
cap = cv2.VideoCapture(cap_num)
dir_path = os.path.dirname(__file__)
def save():
global if_save
while True:
input("Input any to save a image:")
if_save = True
# 开启线程进行摄像头拍摄
t = threading.Thread(target=save)
# 设置为异步运行
t.setDaemon(True)
t.start()
while cv2.waitKey(1) != ord("q"):
_, frame = cap.read()
if if_save:
# 设置名称为当前路径下,否则会因为运行环境的原因使得存储位置发生变化
img_name = os.path.join(dir_path,str(name)+".jpg")
# 存储图片
cv2.imwrite(img_name, frame)
print("Save {} successful.".format(img_name))
name += 1
if_save = False
cv2.imshow("", frame)
2.3 获得相机矩阵和失真系数
OpenCV自带相机校准函数,通过识别棋盘中的网格,自动生成我们所需的相机矩阵和失真系数。这里我们只需确保在第二步拍摄的图片能够被识别出来即可。若是都识别失效也没有关系,我们只需再次重复第二步即可。当然若不想如此麻烦的重复第二步和第三步,可以直接越过第二步,在第三步调用calibration_camera函数时设置cap_num参数即摄像头编号,进行实时处理获得相机举证以及失真系数。详细说明可仔细阅读代码中的注解。
#!/usr/bin/env python3
import os
import glob
import numpy as np
import cv2 as cv
from pprint import pprint
def calibration_camera(row, col, path=None, cap_num=None, saving=False):
"""校准摄像头
参数说明:
row (int): 网格中的行数。
col (int): 网格中的列数。
path (string): 存放校准图片的位置。
cap_num (int): 表示摄像头的编号,一般0或1
saving (bool): 是否存放相机矩阵和失真系数(.npz).
"""
# 终止准则/失效准则
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 准备物体点, 比如 (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
obj_p = np.zeros((row * col, 3), np.float32)
obj_p[:, :2] = np.mgrid[0:row, 0:col].T.reshape(-1, 2)
# 组用于存储来自所有图像的对象点和图像点。
obj_points = [] # 3d点在现实世界的位置。
img_points = [] # 2d点在图片中的位置。
gray = None
def _find_grid(img):
# 使用函数外的参数
nonlocal gray, obj_points, img_points
# 将图片转换为灰色度图片
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 寻找棋盘的角落
ret, corners = cv.findChessboardCorners(gray, (row, col), None)
# 如果找到,则添加处理后的2d点和3d点
if ret == True:
obj_points.append(obj_p)
corners2 = cv.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
img_points.append(corners)
# 在图片中绘制并展示所寻找到的角
cv.drawChessboardCorners(img, (row, col), corners2, ret)
# 要求必须选择使用图片校准或者摄像头实时捕获校准中的一种
if path and cap_num:
raise Exception("The parameter `path` and `cap_num` only need one.")
# 图片校准
if path:
# 获取当前路径中的所有图片
images = glob.glob(os.path.join(path, "*.jpg"))
pprint(images)
# 对获取的每张图片进行处理
for f_name in images:
# 读取图片
img = cv.imread(f_name)
_find_grid(img)
# 展示图片
cv.imshow("img", img)
# 图片展示等待0.5s
cv.waitKey(500)
# 摄像头实时捕获校准
if cap_num:
# 开启摄像头
cap = cv.VideoCapture(cap_num)
while True:
# 读取摄像头开启后的每帧图片
_, img = cap.read()
_find_grid(img)
cv.imshow("img", img)
cv.waitKey(500)
print(len(obj_points))
if len(obj_points) > 14:
break
# 销毁展示窗口
cv.destroyAllWindows()
# 通过计算获取的3d点和2d点得出相机矩阵和失真系数
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(
obj_points, img_points, gray.shape[::-1], None, None
)
print("ret: {}".format(ret))
print("matrix:")
pprint(mtx)
print("distortion: {}".format(dist))
# 决定是否存储所计算出的参数
if saving:
np.savez(os.path.join(os.path.dirname(__file__), "mtx_dist.npz"), mtx=mtx, dist=dist)
mean_error = 0
for i in range(len(obj_points)):
img_points_2, _ = cv.projectPoints(obj_points[i], rvecs[i], tvecs[i], mtx, dist)
error = cv.norm(img_points[i], img_points_2, cv.NORM_L2) / len(img_points_2)
mean_error += error
print("total error: {}".format(mean_error / len(obj_points)))
return mtx, dist
if __name__ == "__main__":
path = os.path.dirname(__file__)
mtx, dist = calibration_camera(8, 6, path, saving=True)
# 设置是否需要测试计算出的参数
if_test = input("If testing the result (default: no), [yes/no]:")
if if_test not in ["y", "Y", "yes", "Yes"]:
exit(0)
cap_num = int(input("Input camera number:"))
cap = cv.VideoCapture(cap_num)
while cv.waitKey(1) != ord("q"):
_, img = cap.read()
h, w = img.shape[:2]
# 相机校准
dst = cv.undistort(img, mtx, dist)
cv.imshow("", dst)
2.4 加载相机矩阵和失真系数
以下代码主要是为了说明如何加载上一步存储的校准参数,对相机进行校准。
"""加载校准参数对摄像头进行校准."""
import os
import numpy as np
import cv2 as cv
if __name__ == "__main__":
# 加载校准参数
load = np.load(os.path.join(os.path.dirname(__file__), "mtx_dist.npz"))
mtx = load["mtx"]
dist = load["dist"]
cap = cv.VideoCapture(0)
while cv.waitKey(1) != ord("q"):
_, img = cap.read()
h, w = img.shape[:2]
# 相机校准
dst = cv.undistort(img, mtx, dist, None)
cv.imshow("", dst)