GRCNN抓取网络1【Jacquard数据集等效抓取集制作】
请先完成【机械臂视觉抓取从理论到实战】,这样有利于下面操作的理解!
GRCNN源码:https://github.com/skumra/robotic-grasping
完整UR机械臂的GRCNN抓取网络教程参考以下博客:
0. 【机械臂视觉抓取从理论到实战】
- GRCNN抓取网络学习1【Jacquard数据集等效制作】
- GRCNN抓取网络学习2【自制Jacquard数据集训练】
- GRCNN抓取网络学习3【自制Jacquard数据集模型调优】
- GRCNN抓取网络学习4【手眼标定】
所需硬件:Realsense D435i相机,python3.8
在执行程序之前先检查是否有对应的库,避免报错!
1. 前言
首先了解Jacquard数据集的参数组成
Jacquard dataset:https://jacquard.liris.cnrs.fr/
1.1 请求访问权限
可以从“数据库”页面下载 10 个对象的一小部分样本。要获取对整个数据集的访问权限,请执行以下操作:
请打印最终用户许可协议(EULA,在此处下载)). 签名并扫描。 向 jacquard@liris.cnrs.fr 发送邮件,并将已签名的 EULA 作为 pdf 格式包含在内。
在网站管理员验证并批准您的请求后,您将收到电子邮件通知。请注意,来自免费电子邮件地址(hotmail,yahoo,gmail等)的任何请求都将被拒绝。
利用Jacquard数据集,我们提出了一种基于模拟的新标准,随后称为模拟抓取试验标准(SGT)。具体来说,当应该评估新的抓取时,在模拟环境中重建相应的场景,并由模拟机器人在与生成注释期间相同的条件下执行抓取。如果模拟抓取的结果是成功的,即模拟机器人使用预测的抓取位置成功地抬起并移开物体,则预测被认为是一个好的抓取。
除了访问整个数据集外,注册用户还可以使用界面将他们的预测提交到我们的模拟器,并获得论文中介绍的基于模拟抓取试验标准的结果。
1.2 引文
如果您在研究中使用提花数据集,请引用以下论文:
A. Depierre, E. Dellandrea, and L. Chen, “Jacquard: A Large Scale Dataset for Robotic Grasp Detection” in IEEE International Conference on Intelligent Robots and Systems, 2018.
1.3 Jacquard数据库
数据集由来自 11 619 个不同对象的 54 485 个不同场景组成,共有 4 967 454 个抓取注释(1 181 330 个唯一位置)。对于每个场景,都提供了一个渲染的RGB图像,一个分割蒙版,两个深度图像和抓取注释。有关数据集创建的更多详细信息,请参阅本文。
1.4 演示示例
可以在此处下载包含数据集 10 个对象子集的存档 (55.2 MB)。要下载整个数据集,请按照主页上的说明进行操作。
1.5 文件内容
每个可下载的 zip 文件都包含数据集的独立子部分,因此您可以像处理整个数据集一样处理 10 个演示对象。提取后,数据集的结构如下:
nameOfObject1:包含对象所有数据的文件夹。该名称与 ShapenetSem 数据集一致。此文件夹中的所有文件都以 * = i_nameOfObject1 为前缀,其中 i 是当前对象的场景索引(介于 0 和 4 之间)。
*_grasps.txt:带有 grasps 注释的文本文件。文件中的每一行都是一个抓握,写成 x;y;theta 以度为单位;开口;下颚大小。请注意,所有值都在图像坐标中,因此它们以像素表示,y 朝向图像底部(因此角度水平镜像)。当多个连续行上的位置相同时,第一个对应于默认下颚大小为 2 厘米的抓握,以下只是以不同尺寸重复此抓握。
*_mask.png:带有对象的二进制掩码(0表示背景)的PNG图像,可用于对数据执行背景增强。
*_perfect_depth.tiff:具有完美深度图像的float32 tiff图像。
*_RGB.png:使用Blender渲染的场景的PNG图像。
*_stereo_depth.tiff:一个float32 tiff图像,其深度来自现成的立体视觉算法。深度不确定的像素(由于立体视觉过程中的遮挡或匹配不良)的值为 -1。
名称对象2
*_grasps.txt
*_mask.png
*_perfect_depth.tiff
*_RGB.png
*_stereo_depth.tiff
名称对象3
*_grasps.txt
*_mask.png
*_perfect_depth.tiff
*_RGB.png
*_stereo_depth.tiff
...
1.6 制作成员
提花数据集由法国研究团队创建:
Amaury Depierre, Siléane & Ecole Centrale de Lyon, LIRIS, 法国 伊曼纽尔·德尔兰德里亚,里昂中央理工学院,LIRIS,法国 陈黎明,里昂中央理工学院,LIRIS,法国
我们要感谢Matthieu Grard和Romain Brégier的帮助。当然,我们还要感谢分享他们工作的Shapenet的创作者。
查看GRCNN源码解析Jacquard源码如下:
jacquard_data.py
import glob
import os
from utils.dataset_processing import grasp, image
from .grasp_data import GraspDatasetBase
class JacquardDataset(GraspDatasetBase):
""" Dataset wrapper for the Jacquard dataset. """
def __init__(self, file_path, ds_rotate=0, **kwargs):
""" :param file_path: Jacquard Dataset directory. :param ds_rotate: If splitting the dataset, rotate the list of items by this fraction first :param kwargs: kwargs for GraspDatasetBase """
super(JacquardDataset, self).__init__(**kwargs)
self.grasp_files = glob.glob(os.path.join(file_path, '*', '*', '*_grasps.txt'))
self.grasp_files.sort()
self.length = len(self.grasp_files)
if self.length == 0:
raise FileNotFoundError('No dataset files found. Check path: {}'.format(file_path))
if ds_rotate:
self.grasp_files = self.grasp_files[int(self.length * ds_rotate):] + self.grasp_files[
:int(self.length * ds_rotate)]
self.depth_files = [f.replace('grasps.txt', 'perfect_depth.tiff') for f in self.grasp_files]
self.rgb_files = [f.replace('perfect_depth.tiff', 'RGB.png') for f in self.depth_files]
#change1 add _get_crop_attrs(self, idx)
def _get_crop_attrs(self, idx):
gtbbs = grasp.GraspRectangles.load_from_jacquard_file(self.grasp_files[idx])
center = gtbbs.center
left = max(0, min(center[1] - self.output_size // 2, 640 - self.output_size))
top = max(0, min(center[0] - self.output_size // 2, 480 - self.output_size))
return center, left, top
def get_gtbb(self, idx, rot=0, zoom=1.0):
gtbbs = grasp.GraspRectangles.load_from_jacquard_file(self.grasp_files[idx])
center, left, top = self._get_crop_attrs(idx)
gtbbs.rotate(rot, center)
gtbbs.offset((-top, -left))
gtbbs.zoom(zoom, (self.output_size // 2, self.output_size // 2))
return gtbbs
# gtbbs = grasp.GraspRectangles.load_from_jacquard_file(self.grasp_files[idx], scale=self.output_size / 1024.0)
# c = self.output_size // 2
# gtbbs.rotate(rot, (c, c))
# gtbbs.zoom(zoom, (c, c))
# return gtbbs
def get_depth(self, idx, rot=0, zoom=1.0):
depth_img = image.DepthImage.from_tiff(self.depth_files[idx])
center, left, top = self._get_crop_attrs(idx)
depth_img.rotate(rot, center)
depth_img.crop((top, left), (min(480, top + self.output_size), min(640, left + self.output_size)))
depth_img.normalise()
depth_img.zoom(zoom)
depth_img.resize((self.output_size, self.output_size))
return depth_img.img
# depth_img.rotate(rot)
# depth_img.normalise()
# depth_img.zoom(zoom)
# depth_img.resize((self.output_size, self.output_size))
# return depth_img.img
def get_rgb(self, idx, rot=0, zoom=1.0, normalise=True):
rgb_img = image.Image.from_file(self.rgb_files[idx])
center, left, top = self._get_crop_attrs(idx)
rgb_img.rotate(rot, center)
rgb_img.crop((top, left), (min(480, top + self.output_size), min(640, left + self.output_size)))
rgb_img.zoom(zoom)
# rgb_img.rotate(rot)
# rgb_img.zoom(zoom)
rgb_img.resize((self.output_size, self.output_size))
if normalise:
rgb_img.normalise()
rgb_img.img = rgb_img.img.transpose((2, 0, 1))
return rgb_img.img
def get_jname(self, idx):
return '_'.join(self.grasp_files[idx].split(os.sep)[-1].split('_')[:-1])
总结Jacquard数据集输入到GRCNN中是三种文件
grasps.txt
perfect_depth.tiff
RGB.png
2. 采集RGBD图像
结合上面的数据格式需求,首先需要获取代抓取物体的RGBD对齐图像,这样就得到perfect_depth.tiff
/RGB.png
图像。根据自身抓取要求选择合适的抓取框生成grasps.txt
。
2.1 采集RGBD图像源码
realsense_save_rgbd.py
''' Description: Author: vor Date: 2023/8/26 '''
import pyrealsense2 as rs
import numpy as np
import cv2
import time
import os
from imageio import imsave
def depth2Gray(im_depth):
""" 将深度图转至三通道8位灰度图 (h, w, 3) """
# 16位转8位
x_max = np.max(im_depth)
x_min = np.min(im_depth)
if x_max == x_min:
print('图像渲染出错 ...')
raise EOFError
k = 255 / (x_max - x_min)
b = 255 - k * x_max
ret = (im_depth * k + b).astype(np.uint8)
return ret
def depth2RGB(im_depth):
""" 将深度图转至三通道8位彩色图 先将值为0的点去除,然后转换为彩图,然后将值为0的点设为红色 (h, w, 3) im_depth: 单位 mm或m """
im_depth = depth2Gray(im_depth)
im_color = cv2.applyColorMap(im_depth, cv2.COLORMAP_JET)
return im_color
def inpaint(img, missing_value=0):
""" Inpaint missing values in depth image. :param missing_value: Value to fill in teh depth image. """
img = cv2.copyMakeBorder(img, 1, 1, 1, 1, cv2.BORDER_DEFAULT)
mask = (img == missing_value).astype(np.uint8)
# Scale to keep as float, but has to be in bounds -1:1 to keep opencv happy.
scale = np.abs(img).max()
img = img.astype(np.float32) / scale # Has to be float32, 64 not supported.
img = cv2.inpaint(img, mask, 1, cv2.INPAINT_NS)
# Back to original size and value range.
img = img[1:-1, 1:-1]
img = img * scale
return img
def run():
pipeline = rs.pipeline()
#Create a config并配置要流式传输的管道
config = rs.config()
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)
align_to = rs.stream.color
align = rs.align(align_to)
# 按照日期创建文件夹
save_path = os.path.join(os.getcwd(), "out", time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime()))
os.mkdir(save_path)
# 保存的图片和实时的图片界面
cv2.namedWindow("live", cv2.WINDOW_AUTOSIZE)
cv2.namedWindow("save", cv2.WINDOW_AUTOSIZE)
saved_count = 1000
pipeline.start(config)
# 主循环
try:
while True:
frames = pipeline.wait_for_frames()
aligned_frames = align.process(frames)
# 获取RGB图像
color_frame = aligned_frames.get_color_frame()
color_image = np.asanyarray(color_frame.get_data())
# 获取深度图
aligned_depth_frame = aligned_frames.get_depth_frame()
depth_image = np.asanyarray(aligned_depth_frame.get_data()).astype(np.float32) / 1000. # 单位为m
# 可视化图像
depth_image = inpaint(depth_image) # 补全缺失值
depth_image_color = depth2RGB(depth_image)
cv2.imshow("live", np.hstack((color_image, depth_image_color)))
key = cv2.waitKey(30)
# change s 保存图片
if key & 0xFF == ord('s'):
cv2.imwrite(os.path.join((save_path), "{:04d}_label1_RGB.png".format(saved_count)), color_image) # 保存RGB为png文件
imsave(os.path.join((save_path), "{:04d}_label1_perfect_depth.tiff".format(saved_count)), depth_image) # 保存深度图为tiff文件
saved_count+=1
cv2.imshow("save", np.hstack((color_image, depth_image_color)))
# q 退出
if key & 0xFF == ord('q') or key == 27:
cv2.destroyAllWindows()
break
finally:
pipeline.stop()
if __name__ == '__main__':
run()
2.2 操作方法
- 修改保存路径
# 按照日期创建文件夹
save_path = os.path.join(os.getcwd(), "out", time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime()))
- 修改
{:04d}_label1_RGB.png
中label后面的数字,一个数字一种类别
# change s 保存图片
if key & 0xFF == ord('s'):
cv2.imwrite(os.path.join((save_path), "{:04d}_label1_RGB.png".format(saved_count)), color_image) # 保存RGB为png文件
imsave(os.path.join((save_path), "{:04d}_label1_perfect_depth.tiff".format(saved_count)), depth_image) # 保存深度图为tiff文件
- 按s保存,按q退出
效果如下
0000_label1_RGB.png
0000_label1_perfect_depth.tiff
visual_tiff.py
可视化程序,修改对应文件
''' Description: Author: vor Date: 2023/8/26 '''
import cv2
import os
from realsense_save_rgbd import depth2RGB, inpaint
path = 'out/label1/0000_label1_perfect_depth.tiff'
if not os.path.exists(path):
print(path + ' not exists')
raise os.error
im_depth = cv2.imread(path, -1)
cv2.imshow('depth', depth2RGB(im_depth))
cv2.waitKey()
0000_label1_perfect_depth.tiff
3. 打抓取标签
3.1 先配置标签库
label.py
是配置标签库
# -*- coding: utf-8 -*-
''' Description: 三角表示法 标注类文件 Author: vor Date: 2023/8/26 '''
import cv2
import glob
import os
import copy
import time
import math
import numpy as np
from skimage.draw import polygon
from skimage.draw import disk as skicircle
#注意如果`scikit-image==0.15.0`版本以后circle改成了disk,不然找不到,画不出圆
class Label:
def __init__(self):
# ---------------- 设置宏定义 ---------------- #
# 设置标记颜色
self.COLOR_RED = (0, 0, 255)
self.COLOR_GREEN = (0, 255, 0)
self.COLOR_BLUE = (255, 255, 0)
self.COLOR_PINK = (255, 0, 255)
self.COLOR_LIGHT_BLUE = (255, 0, 0)
# 键盘按键
self.KEY_UP = 2490368
self.KEY_DOWN = 2621440
self.KEY_LEFT = 2424832
self.KEY_RIGHT = 2555904
self.KEY_SPACE = 32
self.KEY_ESC = 27
self.KEY_W = 119
self.KEY_S = 115
self.KEY_Q = 113
self.KEY_E = 101
# 抓取宽度显示状态
self.GRASP_RED = 1101
self.GRASP_GREEN = 1102
self.GRASP_BOTH = 1103
self.GRASP_CIRCLE = 1104
self.GRASP_NULL = 0
# ---------------- 初始化变量 ---------------- #
self.LABELING_POINTS = False # 抓取位置标注标志
self.LABELING_POINTS_WIDTH = False # 抓取位置标注的宽度 标志
self.LABELING_GRASP_WIDTH = False # 抓取宽度设置 标志
self.LABEL_UPDATE = False # 更新静态标注图
self.LABEL_SHOW_ANGLE_CIRCLE = False # 显示直线两侧的圆
self.LABEL_SHOW_GRASP_LINE = self.GRASP_NULL # 显示抓取宽度
self.LINE_SIDE_POINTS = []
# 抓取位置标注 [起点x 起点y 终点x 终点y angle1 angle2 线宽 抓取宽度]
self.LABEL_POINTS = [None, None, None, None, None, None, None, None]
self.LABEL_POINTS_ALL = [] # 记录每一次标注的信息 [self.LABEL_POINTS]
def update(self):
if self.LABEL_POINTS[2] is not None:
print('>> 已标注', self.LABEL_POINTS)
self.LABEL_POINTS_ALL.append(self.LABEL_POINTS) # 更新 LABEL_POINTS_ALL !!!
self.LABEL_UPDATE = True
self.LABELING_POINTS = False # 抓取位置标注标志
self.LABELING_POINTS_WIDTH = False # 抓取位置标注的宽度 标志
self.LABEL_SHOW_ANGLE_CIRCLE = False # 显示直线两侧的圆
self.LABEL_SHOW_GRASP_LINE = self.GRASP_NULL # 显示抓取宽度
self.LABEL_POINTS = [None, None, None, None, None, None, None, None]
def getRectPoints(self, x1, y1, x2, y2, w):
""" 返回给定矩形内的所有点坐标 :param x1:直线一端的x坐标 :param y1:直线一端的y坐标 :param x2:直线另一端的x坐标 :param y2:直线另一端的y坐标 :param w:线宽 :return:rr, cc 分别是矩形内点的x和y,每个是一维的array """
if x2 == x1:
angle = math.pi / 2
else:
angle = np.arctan((y1 - y2) / (x2 - x1))
rect = []
rect.append([y1 - w / 2 * np.cos(angle), x1 - w / 2 * np.sin(angle)])
rect.append([y2 - w / 2 * np.cos(angle), x2 - w / 2 * np.sin(angle)])
rect.append([y2 + w / 2 * np.cos(angle), x2 + w / 2 * np.sin(angle)])
rect.append([y1 + w / 2 * np.cos(angle), x1 + w / 2 * np.sin(angle)])
rect = np.array(rect)
return polygon(rect[:, 0], rect[:, 1], (480, 640))
def getCirclePoints(self, x, y, r):
""" 返回给定圆内的所有点坐标 :param x: x :param y: y :param r: 半径 :return: """
return skicircle((float(y), float(x)), float(r))
def getLineSidePoint(self):
"""获取标记线两侧圆的中心点"""
x1 = self.LABEL_POINTS[0]
y1 = self.LABEL_POINTS[1]
x2 = self.LABEL_POINTS[2]
y2 = self.LABEL_POINTS[3]
length = 20 # 画的球离直线的距离
cx = (x1 + x2) / 2 # 垂线中心点 x
cy = (y1 + y2) / 2 # 垂线中心点 y
if y1 == y2:
ccx1 = cx
ccy1 = cy + length
ccx2 = cx
ccy2 = cy - length
else:
k = (x1 - x2) / (y2 - y1) # 垂线的斜率
dx = length / pow(k ** 2 + 1, 0.5) # l在水平方向的投影长度
ccx1 = cx + dx
ccy1 = cy + dx * k
ccx2 = cx - dx
ccy2 = cy - dx * k
self.LINE_SIDE_POINTS.clear()
self.LINE_SIDE_POINTS.append([ccx1, ccy1])
self.LINE_SIDE_POINTS.append([ccx2, ccy2])
def writelabel(self, filename):
""" 创建并写入标签文件 :param filename:[../01/pcd0***Label1.txt, ../01/pcd0***Label2.txt] """
# 根据端点和线宽获取线上的点
# 获取的点是(x, y),要替换成(row, col)
label_points = None
for label_line in self.LABEL_POINTS_ALL:
x1 = label_line[0]
y1 = label_line[1]
x2 = label_line[2]
y2 = label_line[3]
angle1 = label_line[4]
angle2 = label_line[5]
w = label_line[6] # 对直线来说是线宽,对圆形来说是直径
grasp_w = label_line[7] # 抓取宽度
# 如果起点和终点相同,表示当前标注的是球形抓取,根据圆形来获取抓取点坐标
if x1 == x2 and y1 == y2:
rr, cc = self.getCirclePoints(x1, y1, w/2)
else: # 根据矩形获取抓取点
rr, cc = self.getRectPoints(x1, y1, x2, y2, w)
rows = rr.reshape((-1, 1)) # row
cols = cc.reshape((-1, 1)) # col
if angle1 is None: # angle1
angles1 = [['None'] for _ in range(rows.shape[0])]
angles1 = np.array(angles1)
else:
angles1 = np.zeros(rows.shape)
angles1[:, :] = angle1
if angle2 is None: # angle2
angles2 = [['None'] for _ in range(rows.shape[0])]
angles2 = np.array(angles2)
else:
angles2 = np.zeros(rows.shape)
angles2[:, :] = angle2
# 获取抓取宽度
ws = np.zeros(rows.shape)
ws[:, :] = grasp_w * 2 # grasp_w是抓取点一侧的宽度
points = np.hstack((rows, cols, angles1, angles2, ws)) # 横向合并坐标点标注信息
if label_points is None:
label_points = points
else:
label_points = np.vstack((label_points, points))
# label_points 为二维array,每一行为一个点,数据分别是 row, col, ...
# 方案1
with open(filename, 'w', encoding="utf-8") as f:
for idx in range(label_points.shape[0]):
point = label_points[idx]
for item in point:
if item != 'None':
f.write('{} '.format(item))
f.write('\n')
def angle(self, pt1, pt2):
""" 计算pt1到pt2连线与水平轴的夹角[0, 2π] :param pt1: 起点[x, y] :param pt2: 终点[x, y] :return: """
dx = pt2[0] - pt1[0]
dy = pt1[1] - pt2[1] # math.atan2是在正常的坐标系中计算角的
angle = math.atan2(dy, dx)
# angle = math.atan2(dy, dx)*180/math.pi
if angle < 0:
angle += 2 * math.pi
# angle += 2 * 180
return angle
def key_monitor(self, key):
"""键盘监听"""
# 下一张
if key == self.KEY_SPACE:
self.update() # 初始化标志位并更新标注数据
# 停止标注
elif key == 27:
self.update() # 初始化标志位并更新标注数据
# 上 下 调整线宽
elif key in [self.KEY_UP, self.KEY_DOWN]:
if self.LABEL_POINTS[6] is None:
return
self.LABEL_POINTS[6] -= int(key / 2621440) * 2 - 1 # 设置抓取位置标注 线宽
self.LABEL_POINTS[6] = max(1, int(self.LABEL_POINTS[6]))
print('\r>> 线宽 {}'.format(self.LABEL_POINTS[6]))
self.LABELING_POINTS_WIDTH = True
# 左 右 调整抓取宽度
elif key in [self.KEY_LEFT, self.KEY_RIGHT]:
if self.LABEL_POINTS[7] is None:
return
self.LABEL_POINTS[7] -= int(key / 2555904) * 4 - 2 # 设置抓取宽度
self.LABEL_POINTS[7] = max(6, int(self.LABEL_POINTS[7])) # 抓取宽度不小于10 抓取点一侧的宽度
# 区分抓取方向
# W对应红色点 LINE_SIDE_POINTS[1], S对应绿色点 LINE_SIDE_POINTS[0], Q对应球形抓取,E对应对称抓取
elif key in [self.KEY_W, self.KEY_S, self.KEY_Q, self.KEY_E]: # W S Q E
if key == self.KEY_W:
# print('>> W')
# 计算从标注线中心点到 LINE_SIDE_POINTS[1]的角
x1 = (self.LABEL_POINTS[0] + self.LABEL_POINTS[2]) / 2 # 标注线中心点 x
y1 = (self.LABEL_POINTS[1] + self.LABEL_POINTS[3]) / 2 # 标注线中心点 y
x2 = self.LINE_SIDE_POINTS[1][0]
y2 = self.LINE_SIDE_POINTS[1][1]
self.LABEL_POINTS[4] = self.angle([x1, y1], [x2, y2])
self.LABEL_POINTS[5] = None
self.LABEL_SHOW_GRASP_LINE = self.GRASP_RED
print('>> 抓取角 ', self.LABEL_POINTS[4] / math.pi * 180)
elif key == self.KEY_S:
# print('>> S')
# 计算从标注线中心点到 LINE_SIDE_POINTS[0]的角
x1 = (self.LABEL_POINTS[0] + self.LABEL_POINTS[2]) / 2 # 标注线中心点 x
y1 = (self.LABEL_POINTS[1] + self.LABEL_POINTS[3]) / 2 # 标注线中心点 y
x2 = self.LINE_SIDE_POINTS[0][0]
y2 = self.LINE_SIDE_POINTS[0][1]
self.LABEL_POINTS[4] = self.angle([x1, y1], [x2, y2])
self.LABEL_POINTS[5] = None
self.LABEL_SHOW_GRASP_LINE = self.GRASP_GREEN
print('>> 抓取角 ', self.LABEL_POINTS[4] / math.pi * 180)
elif key == self.KEY_Q:
# print('>> Q')
self.LABEL_POINTS[4] = None
self.LABEL_POINTS[5] = None
self.LABEL_SHOW_GRASP_LINE = self.GRASP_CIRCLE
print('>> 抓取角 [0, 2π]')
# pass
elif key == self.KEY_E:
# print('>> E')
# 计算从标注线中心点到 LINE_SIDE_POINTS[1]的角
x1 = (self.LABEL_POINTS[0] + self.LABEL_POINTS[2]) / 2 # 标注线中心点 x
y1 = (self.LABEL_POINTS[1] + self.LABEL_POINTS[3]) / 2 # 标注线中心点 y
x2 = self.LINE_SIDE_POINTS[1][0]
y2 = self.LINE_SIDE_POINTS[1][1]
self.LABEL_POINTS[4] = self.angle([x1, y1], [x2, y2])
# 计算从标注线中心点到 LINE_SIDE_POINTS[0]的角
x1 = (self.LABEL_POINTS[0] + self.LABEL_POINTS[2]) / 2 # 标注线中心点 x
y1 = (self.LABEL_POINTS[1] + self.LABEL_POINTS[3]) / 2 # 标注线中心点 y
x2 = self.LINE_SIDE_POINTS[0][0]
y2 = self.LINE_SIDE_POINTS[0][1]
self.LABEL_POINTS[5] = self.angle([x1, y1], [x2, y2])
self.LABEL_SHOW_GRASP_LINE = self.GRASP_BOTH
print('>> 抓取角 ', self.LABEL_POINTS[4] / math.pi * 180, self.LABEL_POINTS[5] / math.pi * 180)
self.LABEL_POINTS[7] = 20 # 初始化抓取宽度
图片工具tool.py
''' Description: 将深度图转至8位灰度图,补齐灰度图,裁剪图片 Author: vor Date: 2023/8/26 '''
import numpy as np
import cv2
def depth2Gray(im_depth):
""" 将深度图转至8位灰度图 """
# 16位转8位
x_max = np.max(im_depth)
x_min = np.min(im_depth)
if x_max == x_min:
print('图像渲染出错 ...')
raise EOFError
k = 255 / (x_max - x_min)
b = 255 - k * x_max
return (im_depth * k + b).astype(np.uint8)
def inpaint(img, missing_value=0):
""" Inpaint missing values in depth image. :param missing_value: Value to fill in teh depth image. """
img = cv2.copyMakeBorder(img, 1, 1, 1, 1, cv2.BORDER_DEFAULT)
mask = (img == missing_value).astype(np.uint8)
# Scale to keep as float, but has to be in bounds -1:1 to keep opencv happy.
scale = np.abs(img).max()
img = img.astype(np.float32) / scale # Has to be float32, 64 not supported.
img = cv2.inpaint(img, mask, 1, cv2.INPAINT_NS)
# Back to original size and value range.
img = img[1:-1, 1:-1]
img = img * scale
return img
def crop(img, crop_w, crop_h):
""" 裁剪深度图像 """
img_1 = img.copy()
h, w = img.shape
l = int((w - crop_w) / 2)
r = l + crop_w
t = int((h - crop_h) / 2)
b = t + crop_h
return img_1[t:b, l:r]
打标签主程序main_label.py
# -*- coding: utf-8 -*-
''' Description: 三角表示法标注执行文件 class Author: vor Date: 2023/8/26 '''
import cv2
import glob
import os
import copy
import math
from label import Label
import time
import tool
import numpy as np
from skimage.draw import polygon
from skimage.draw import disk as skicircle
#注意如果`scikit-image==0.15.0`版本以后circle改成了disk,不然找不到,画不出圆
""" ****** 操作说明 ****** 1 用鼠标左键画出一条适合夹持器抓取的直线 2 按 上/下 调整线宽(抓取高度即夹爪的厚度) 上: 增加线宽 下: 减小线宽 3 按 W/S/Q/E 设置抓取模式: w: 朝向红色点单向抓取 S: 朝向绿色单向抓取 E: 对称抓取 Q: 圆形抓取 4 按 左/右 调整抓取宽度 (抓取宽度比物体宽2cm,两侧各1cm) 左: 增加 右: 减小 换下一张:空格 退出标注:Esc 在换下一张时,当前图像上标注的数据会保存至pcd****label.txt, 退出时,当前图像的标注不保存 """
ESC = False # 标注停止标志
def mouse_monitor(event, x, y, flags, param):
"""鼠标监听"""
if event == cv2.EVENT_LBUTTONDOWN: # 按下左键
label.update() # 初始化标志位并更新标注数据
print('>> 抓取位置标注 ... ')
label.LABEL_POINTS[0] = x # 设置抓取位置标注 起点
label.LABEL_POINTS[1] = y
label.LABEL_POINTS[6] = 1
elif event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_LBUTTON: # 左键拖动
label.LABEL_POINTS[2] = x # 设置抓取位置标注 终点
label.LABEL_POINTS[3] = y
label.LABELING_POINTS = True
elif event == cv2.EVENT_LBUTTONUP: # 抬起右键
# print('>> 抓取位置标注 结束')
label.LABEL_POINTS[2] = x # 设置抓取位置标注 终点
label.LABEL_POINTS[3] = y
label.LABELING_POINTS = False
label.LABEL_SHOW_ANGLE_CIRCLE = True
## change1 RGBD picture path
if __name__ == '__main__':
path = 'out/label1'#'/home/robot/UR_grasping_net/realsense/out/2023_07_24_15_32_18'# '/home/robot/UR_grasping_net/realsense/out/2023_07_24_14_47_27' # 数据集图片路径
label_win_name = 'labeling'
files = glob.glob(os.path.join(path, '*[0-9]_label1_RGB.png')) # 匹配所有的符合条件的文件,并将其以list的形式返回
files.sort()
cv2.namedWindow(label_win_name)
cv2.setMouseCallback(label_win_name, mouse_monitor)
n = 0 # 记录当前标注个数
# 依次标注
for file in files:
n += 1
# 可视化深度图
# depth_file = file.replace('r.png', 'd.png')
# if os.path.exists(depth_file):
# im_depth = cv2.imread(depth_file, -1)
# im_depth = tool.crop(im_depth, 400, 400)
# im_depth = tool.inpaint(im_depth)
# cv2.imshow('depth', tool.depth2Gray(im_depth))
##change2 RGBD picture label name
# 标签文件名
# label_file = file.replace('r.png', 'Label.txt')
label_file = file.replace('_label1_RGB.png', '_label1_grasps.txt')
# 跳过已经标注的文件
if os.path.exists(label_file):
continue
# label_file = label_file.replace('data_1', 'data_new')
# if os.path.exists(label_file):
# continue
# png_file = file.replace('.txt', 'r.png') # RGB图像文件名
png_file = file # RGB图像文件名
print('= '*50)
print('正在标注... {} {}/{}'.format(png_file, n, len(files)))
png = cv2.imread(png_file)
png_visual_static = copy.deepcopy(png) # 静态可视化的图像
png_visual_dynamic = copy.deepcopy(png) # 动态可视化的图像
cv2.imshow(label_win_name, png_visual_dynamic) # 显示动态标注图
label = Label()
while 1:
# cv2.imshow(win_name, png)
# 标注可视化
if label.LABEL_UPDATE:
if len(label.LABEL_POINTS_ALL):
# 更新静态标注图
if label.LABEL_POINTS[0] == label.LABEL_POINTS[2] and label.LABEL_POINTS[1] == label.LABEL_POINTS[3]: # 标注的是球形
cv2.circle(png_visual_static, (label.LABEL_POINTS_ALL[-1][0], label.LABEL_POINTS_ALL[-1][1]),
label.LABEL_POINTS_ALL[-1][6], label.COLOR_BLUE, -1)
else: # 标注的是矩形
cv2.line(png_visual_static, (label.LABEL_POINTS_ALL[-1][0], label.LABEL_POINTS_ALL[-1][1]),
(label.LABEL_POINTS_ALL[-1][2], label.LABEL_POINTS_ALL[-1][3]),
label.COLOR_BLUE, label.LABEL_POINTS_ALL[-1][6])
label.LABEL_UPDATE = False
# 动态显示标注情况
if label.LABELING_POINTS or label.LABELING_POINTS_WIDTH or label.LABEL_SHOW_ANGLE_CIRCLE or label.LABEL_SHOW_GRASP_LINE:
png_visual_dynamic = copy.deepcopy(png_visual_static) # 将标注好的图案更新到动态图像上
if label.LABEL_POINTS[0] == label.LABEL_POINTS[2] and label.LABEL_POINTS[1] == label.LABEL_POINTS[3]: # 标注的是球形
cv2.circle(png_visual_dynamic, (label.LABEL_POINTS[0], label.LABEL_POINTS[1]),
int(label.LABEL_POINTS[6] / 2), label.COLOR_BLUE, -1)
# 显示标记点外的圆框
if label.LABEL_SHOW_ANGLE_CIRCLE:
cv2.circle(png_visual_dynamic, (label.LABEL_POINTS[0], label.LABEL_POINTS[1]), 20, label.COLOR_RED, 1)
else: # 标注的是矩形
cv2.line(png_visual_dynamic, (label.LABEL_POINTS[0], label.LABEL_POINTS[1]),
(label.LABEL_POINTS[2], label.LABEL_POINTS[3]),
label.COLOR_BLUE, label.LABEL_POINTS[6])
# 显示标记线两侧的圆
if label.LABEL_SHOW_ANGLE_CIRCLE:
# 获取线两侧圆球的坐标
label.getLineSidePoint()
# LINE_SIDE_POINTS[0]对应绿色点,LINE_SIDE_POINTS[1]对应红色点
cv2.circle(png_visual_dynamic, (int(label.LINE_SIDE_POINTS[0][0]), int(label.LINE_SIDE_POINTS[0][1])), 5, label.COLOR_GREEN, -1) # 注意颜色
cv2.circle(png_visual_dynamic, (int(label.LINE_SIDE_POINTS[1][0]), int(label.LINE_SIDE_POINTS[1][1])), 5, label.COLOR_RED, -1) # 注意颜色
# 显示抓取宽度
if label.LABEL_SHOW_GRASP_LINE:
cx = int((label.LABEL_POINTS[0] + label.LABEL_POINTS[2]) / 2) # 垂线中心点 x
cy = int((label.LABEL_POINTS[1] + label.LABEL_POINTS[3]) / 2) # 垂线中心点 y
if label.LABEL_SHOW_GRASP_LINE in [label.GRASP_RED, label.GRASP_GREEN]:
# 画一边
angle = label.LABEL_POINTS[4]
k = math.tan(angle)
if k == 0:
dx = label.LABEL_POINTS[7]
dy = 0
else:
dx = k / abs(k) * label.LABEL_POINTS[7] / pow(k ** 2 + 1, 0.5)
dy = k * dx
if angle < math.pi:
cv2.line(png_visual_dynamic, (cx, cy), (int(cx + dx), int(cy - dy)), label.COLOR_LIGHT_BLUE, 1)
else:
cv2.line(png_visual_dynamic, (cx, cy), (int(cx - dx), int(cy + dy)), label.COLOR_LIGHT_BLUE, 1)
elif label.LABEL_SHOW_GRASP_LINE == label.GRASP_BOTH:
# 画两边
angle1 = label.LABEL_POINTS[4]
angle2 = label.LABEL_POINTS[5]
k = math.tan(angle1)
if k == 0:
dx = label.LABEL_POINTS[7]
dy = 0
else:
dx = k / abs(k) * label.LABEL_POINTS[7] / pow(k ** 2 + 1, 0.5)
dy = k * dx
if angle1 < math.pi:
cv2.line(png_visual_dynamic, (cx, cy), (int(cx + dx), int(cy - dy)), label.COLOR_LIGHT_BLUE, 1)
else:
cv2.line(png_visual_dynamic, (cx, cy), (int(cx - dx), int(cy + dy)), label.COLOR_LIGHT_BLUE, 1)
if angle2 < math.pi:
cv2.line(png_visual_dynamic, (cx, cy), (int(cx + dx), int(cy - dy)), label.COLOR_LIGHT_BLUE, 1)
else:
cv2.line(png_visual_dynamic, (cx, cy), (int(cx - dx), int(cy + dy)), label.COLOR_LIGHT_BLUE, 1)
elif label.LABEL_SHOW_GRASP_LINE == label.GRASP_CIRCLE:
# 画圆形
cv2.circle(png_visual_dynamic, (cx, cy), label.LABEL_POINTS[7], label.COLOR_LIGHT_BLUE, 1)
label.LABELING_POINTS_WIDTH = False
cv2.imshow(label_win_name, png_visual_dynamic) # 显示动态标注图
# 显示
cv2.imshow('labeled', png_visual_static)
# 监听键盘按键
k = cv2.waitKeyEx(10)
label.key_monitor(k)
if k == label.KEY_SPACE:
# 创建标签文件并写入
label.writelabel(label_file)
break
elif k == label.KEY_ESC:
ESC = True
break
# 停止标注
if ESC:
break
print('标注结束!')
运行main_label.py
此时有两个窗口
注意如果scikit-image==0.15.0
版本以后circle改成了disk,不然找不到,画不出圆
labeling是可以开始标注的窗口
labeled是显示标注结果的窗口
- 配置文件路径
path = 'out/label1'#'/home/robot/UR_grasping_net/realsense/out/2023_07_24_15_32_18'# '/home/robot/UR_grasping_net/realsense/out/2023_07_24_14_47_27' # 数据集图片路径
label_win_name = 'labeling'
files = glob.glob(os.path.join(path, '*[0-9]_label1_RGB.png')) # 匹配所有的符合条件的文件,并将其以list的形式返回
- 查看与上方源文件是否一至
# 标签文件名
# label_file = file.replace('r.png', 'Label.txt')
label_file = file.replace('_label1_RGB.png', '_label1_grasps.txt')
3.2 操作说明
1 用鼠标左键画出一条适合夹持器抓取的直线
2 按 上/下 调整线宽(抓取高度即夹爪的厚度)
上: 增加线宽
下: 减小线宽
3 按 W/S/Q/E 设置抓取模式:
w: 朝向红色点单向抓取
S: 朝向绿色单向抓取
E: 对称抓取
Q: 圆形抓取
4 按 左/右 调整抓取宽度 (抓取宽度比物体宽2cm,两侧各1cm)
左: 增加
右: 减小
换下一张:空格
退出标注:Esc
在换下一张时,当前图像上标注的数据会保存至`*_label1_grasps.txt`,
退出时,当前图像的标注不保存
3.3 查看标签
前面我们标注数据集,有时需要检查对应的标签是否正确,下面给出查看标注可视化代码
showlabel.py
,依然需要注意文件位置
# -*- coding: utf-8 -*-
''' Description: label可视化 Author: vor Date: 2023/8/26 '''
import cv2
import glob
import os
import copy
import time
import math
import numpy as np
path = '/home/robot/UR_grasping_net/realsense/out/label1' # 数据集路径
savepath = '/home/robot/UR_grasping_net/realsense/out/label1'
label_win_name = 'labeling'
label_files = glob.glob(os.path.join(path, '*_label1_grasps.txt'))
for labelfile in label_files:
pngfile = labelfile.replace('_label1_grasps.txt', '_label1_RGB.png')
if not os.path.exists(pngfile):
continue
im = cv2.imread(pngfile)
# top = 113
# left = 113
# cv2.line(im, (top, left), (top + 300, left), (0, 255, 255), 2)
# cv2.line(im, (top + 300, left), (top + 300, left + 300), (0, 255, 255), 2)
# cv2.line(im, (top + 300, left + 300), (top, left + 300), (0, 255, 255), 2)
# cv2.line(im, (top, left + 300), (top, left), (0, 255, 255), 2)
f = open(labelfile)
points = f.readlines()
f.close()
n = 0
for point in points:
point_data = point.split(' ')
y = int(float(point_data[0]))
x = int(float(point_data[1]))
w = float(point_data[-2]) / 2
n += 1
if n % 10 == 0:
if len(point_data) == 4:
# 圆
cv2.circle(im, (x, y), int(w), (0, 255, 0), 1)
elif len(point_data) == 5:
# 单向抓取
angle = float(point_data[2])
k = math.tan(angle)
if k == 0:
dx = w
dy = 0
else:
dx = k / abs(k) * w / pow(k ** 2 + 1, 0.5)
dy = k * dx
if angle < math.pi:
cv2.line(im, (x, y), (int(x + dx), int(y - dy)), (0, 255, 0), 1)
else:
cv2.line(im, (x, y), (int(x - dx), int(y + dy)), (0, 255, 0), 1)
elif len(point_data) == 6:
# 对称抓取
angle1 = float(point_data[2])
angle2 = float(point_data[3])
k = math.tan(angle1)
if k == 0:
dx = w
dy = 0
else:
dx = k / abs(k) * w / pow(k ** 2 + 1, 0.5)
dy = k * dx
if angle1 < math.pi:
cv2.line(im, (x, y), (int(x + dx), int(y - dy)), (0, 255, 0), 1)
else:
cv2.line(im, (x, y), (int(x - dx), int(y + dy)), (0, 255, 0), 1)
if angle2 < math.pi:
cv2.line(im, (x, y), (int(x + dx), int(y - dy)), (0, 255, 0), 1)
else:
cv2.line(im, (x, y), (int(x - dx), int(y + dy)), (0, 255, 0), 1)
cv2.circle(im, (x, y), 1, (255, 0, 0), -1)
# im[y, x] = [0, 255, 0]
# savefile = os.path.join(savepath, pngfile.split('pcd')[-1])
# cv2.imwrite(savefile, im)
# print('generate {}'.format(savefile))
cv2.imshow('im', im)
key = cv2.waitKeyEx()
if key == 27:
cv2.destroyAllWindows()
break
效果如下
3.3.1 自制Jacquard数据集
这里采用对称抓取,其中分析grasps.txt
文件内容
217.0 371.0 3.079173843593836 6.22076649718363 40.0
218.0 371.0 3.079173843593836 6.22076649718363 40.0
对应上述代码中的point
points = np.hstack((rows, cols, angles1, angles2, ws)) # 横向合并坐标点标注信息
每一行就是一个抓取框
内容 | 数据 | 范围 |
---|---|---|
抓取中心点(x,y) | 218.0 371.0 |
0~480,0~640 |
图像夹角Θ1和Θ2 | 3.079173843593836 6.22076649718363 |
0~6.28 |
抓取框宽度width | 40.0 |
0~100 |
注意与标准的Jacquard数据集
有点区别,自制的这个是夹角Θ1和Θ2,没有Jacquard数据集
的抓取框高度height,这个是常量,就是机器人夹爪的厚度,自己结合实际情况拟定即可,可以观察后期我训练时设定为20固定值,影响不大。
3.3.1 Jacquard数据集
同样采用对称抓取,其中Jacquard数据集分析grasps.txt
文件内容
470.14815;253.89551;68.2615;25.5;25.6211
470.14815;253.89551;68.2615;25.5;38.4316
每一行就是一个抓取框
内容 | 数据 | 范围 |
---|---|---|
抓取中心点(x,y) | 218.0 371.0 |
0~480,0~640 |
图像x轴夹角Θ | 68.2615 |
0~360 |
抓取框高度height | 25.5 |
0~100 |
抓取框宽度width | 25.6211 |
0~100 |
4. 总结
通过上面的步骤可以获取自己抓取数据集,输入到GRCNN中是三种文件
grasps.txt
perfect_depth.tiff
RGB.png
接下来会教大家如何训练自己的GRCNN网络啦!
参考文献:
Jacquard dataset:https://jacquard.liris.cnrs.fr/
pybullet GGCNN数据集制作
GRCNN源码:https://github.com/skumra/robotic-grasping
今天的文章GRCNN抓取网络学习1【Jacquard数据集等效制作】[通俗易懂]分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/88420.html