0.废话
距离上次的栈板识别的思考已经过去3个月,中间根据客户的需求和自己的思考,对软件又重新做了调整。但是整体上还是不满意。
0.1 老生常谈的工业视觉落地架构
对于软件架构,我实在没有太多的参考。没办法,公司根本不关心软件是什么玩意。对于一个销售型公司,在2023年了,还认为软件是硬件的附赠品。所以对于软件,公司的态度就是很随意。所以根本也不会有软件框架可以参考,或者类似的需求。但是我们可以看看雷卡慢的。不说具体的细节,因为我也没用过。之前我提出要公司弄一套给我们参考,但是显然不现实,
视觉工程最重要的就是相机拍照,视觉算法,通讯。
其实这个软件就是一个图形化的工程工具,他把视觉工程通过拖拉ui的方式搭建成一整个流程图。但是内置的算法,我理解的就是一深度学习推理端的部署。因为他把训练的模块又弄了一个软件
模型训练工具
这个我最感兴趣的是他的标定工具。当然这种类似的软件其实是每一个视觉厂家必须有的东西。当然我们除外。
0.2 一穷二白的公司需要的技术
我们公司可以说以上3种软件全部没有,那么作为视觉工程师。我们接到任务后,需要哪方面的技术呢。
- 软件前端UI,我就用pyqt
- tcp/ip , socket,要会多线程,非阻塞,心跳机制,连包分包问题的解决
- 相机的SDK,通读SDK的文档,做到了解相机的每个函数的作用。而不是只去看demo
- 标定,熟悉相机的内参外参,熟悉旋转矩阵,欧拉角,四元素法。熟悉相机的9点标定。熟悉机器人的3个坐标系的关系
- 机器视觉算法。不限于2D,3D 熟悉对应的算法库。opencv pcl open3d cloudcompare
- 深度学习视觉算法。不限于2D 3D 分类,检测,实例分割,熟悉pytorch
- 打包工具。log日志。ubuntu脚本。
1。接着栈板识别再思考
之前我们说过,就不再赘述。现在出现的新问题,需要解决
1。1 相机图像的适应性
之前使用了各种方式去增强图像数据的对比度。使得我们要检测的1500-3000范围内的栈板有很好的对比度。结论就是还是对数变换。
1。2模型的适应性
经过了几个客户的测试,我最终还是换成了yolov5。但是瞎折腾也有瞎折腾的好处不是么。yolov5的适应性显然强过yolov7。
推理后端我还是改成了onnx。相比pytorch的真的是体积小速度快。当然win下还是openvino快一点。但是我现在不仅要考虑win linux 还要考虑 树莓派。真的是一个简单的软件弄得特别复杂。具体的测试结果看其他文章。
1。3软件太大
这里我不得不吐槽一下open3d ,我使用pyinstaller打包竟然一个open3d就占了900m 打包的软件加上权重,足足1。5g 客户直接骂娘。而我只用了它其中的一个拟合平面的函数。所以果断去找源码,照着改了一份。这里参考我的其他文章。
1。4 相机的SDK
我是真的服了老外的SDK,真的不当人。当然也怪我,水平不够,看Demo完全没用。前前后后我又把相机的SDK 看了2遍。算是懂了一大半,但是他还是很多漏洞。。。。
2。重点,获取3D点云坐标系
之前我一直通过拟合直线获取平面的法向量。
但是这种方式有个问题,那就是另外一个方向是不知道的。所以如果我的相机的安装方式不规整,那么我们就需要3个坐标轴的旋转角度。
2。1 拟合直线
我的想法是通过找的矩形框去拟合对应的平面
2。2 特征向量
其实这个是让我最着迷的,研究了2天,最后发现瞎折腾。我偶然间发现cloudcomepare 中有个拟合平面的工具竟然拟合的平面是带有旋转矩阵的。这个发现让我欣喜若狂。这样问题不久解决了么。我去看看了他妈的源码。竟然是计算点云的协方差矩阵,然后算矩阵的特征值和特征向量。其中特征值最小的的特征向量就是z轴,特征值最大的特征向量是x轴。z,x叉乘就是y轴。简直不要太牛逼。然后我发现误差贼大。
2。3组合
那就把之前的拟合平面的算法加上去,筛选出正确的点云数据。
然后通过求解协方差矩阵的特征向量获取坐标轴。
def getboxfrompoints(points, inliers):# 计算点云的协方差矩阵cov_matrix = np.cov(points[inliers], rowvar=False)eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)# 创建特征向量z_indx = np.argmin(eigenvalues)z = eigenvectors[:, z_indx]x_indx = np.argmax(eigenvalues)x = eigenvectors[:, x_indx]y = np.cross(z, x)# 创建 Open3D 点云对象point_cloud = o3d.geometry.PointCloud()point_cloud.points = o3d.utility.Vector3dVector(points[inliers])# 创建坐标轴coordinate_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=100.0, origin=np.array([0, 0, 0]))# 计算旋转角度theta_x = np.arctan2(y[2], z[2])theta_y = np.arctan2(-x[2], np.sqrt(x[0]**2 + x[1]**2))theta_z = np.arctan2(x[1], x[0])# 获取旋转矩阵rotation_matrix = np.eye(3)# rotation_matrix = np.dot(rotation_matrix, np.array([x,y,z]))rotation_matrix = np.dot(rotation_matrix, o3d.geometry.get_rotation_matrix_from_xyz([theta_x, theta_y, theta_z]))# 旋转坐标轴coordinate_frame.rotate(rotation_matrix)# 缩放坐标轴coordinate_frame.scale(1.0, center=np.mean(points[inliers], axis=0))# 平移坐标系coordinate_frame.translate(np.mean(points[inliers], axis=0))# 将点云和坐标轴合并到一个列表中geometries = [point_cloud, coordinate_frame]# 显示点云和坐标轴o3d.visualization.draw_geometries(geometries)# return matrix_to_euler(np.array([x,y,z]))return np.degrees(np.array([theta_x,theta_y,theta_z]))