知识准备

       进入案例前,要求具备基本的 python 代码使用能力,否则很可能看不懂代码的逻辑导致事倍功半。与此同时,要求了解pymycobot控制机械臂运动的大致流程,若还没有使用过 pymycobot 可以简单参考以下教程:pymycobot API参考文档pymycobot教程

       由于案例的实现是基于 ROS 实现的,所以在进入教程之前需要简单了解ROS的一些工作原理。可以简单参考一下教程:ROS官方入门

注意事项

  1. 镜像中的密码为:123

  2. 图片识别模型可识别的物体可查看文件:~/catkin_mycobot/src/mycobot/mycobot_ai/功能包名称/scripts/labels.json

    Tips:功能包有三个,分别是 ai_mycobot_280, ai_mecharm_270, ai_mypalletizer_260。

  3. 若摄像头打开的电脑自带的,则需调整程序中的打开摄像头的参数 cap_num ,可选择修改为0或1。

  4. 颜色识别以及图像识别案例修改如下:

  5. 颜色和图像识别的时候 二维码白板 的实际摆放位置一定要按照下图,贴置正确。

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)

results matching ""

    No results matching ""