赞
踩
目录
基于JAKA ZU3机器人平台与固定位置的彩色相机(非垂直),完成不同颜色木块的识别与抓取。编写图像处理算法获取图像中方块的位姿和颜色,并用机器人抓取。
图1:实验主要对象
本次实验中,视觉处理主要分为两部分,即图像坐标系同机器人 XY 平面坐标系之间的单应矩阵计算及坐标转换;方块图像采集、处理,最终提取图像中所有方块的属性,包括位置、角度和颜色。
单应性矩阵的计算原理同“文档矫正”类似,但本实验中要注意三维平面的映射关系。如下图2,定义世界坐标系的原点在Jaka机器人底座,经验证,Jaka坐标系与世界坐标系之间无旋转映射,仅有Z坐标中的差距,两坐标系之间的刚体变换关系如下:
图2:平面机器人视觉系统坐标关系
因此,仅需要求出图像像素坐标系与世界坐标系之间的单应性矩阵,就能得出识别到的物块点在Jaka坐标系下的值。如下图2所示,使用目标相机得到照片,分别取标定点1-4的坐标值作为图像中已知点,而后使用平板采集四个点对应的孔的X坐标与Y坐标,作为世界坐标系已知点的X坐标与Y坐标。注意,本篇中容易忽视高度带来的单应性矩阵误差,误差造成原因如下图3所示,解决方法为采集图像点与标定时均选择物块上表面的点。
图3:高度带来的单应性矩阵误差图
将两个点组对应的代入进去,利用OpenCV库中findHomography()函数即可求得单应性矩阵,代码如下图4所示。
图4:标定点图
求解单应性矩阵代码:
- /*
- *函数名称:calculate_Homo
- *功能:计算单应性矩阵
- *输入参数:要得到的单应性矩阵
- */
- void calculate_Homo(Mat* Homo) {
- vector<Point2f> ori;//三个目标点世界坐标,z:100,单位mm,rx=180,ry=0,rz=0
- ori.push_back(Point(294.923, 48.434));
- ori.push_back(Point(418.584, 32.481));
- ori.push_back(Point(435.834, 155.045));
- ori.push_back(Point(310.961, 172.286));
-
- vector<Point2f> dst;//三个目标点图像坐标
- dst.push_back(Point(708, 22));
- dst.push_back(Point(446, 104));
- dst.push_back(Point(595, 377));
- dst.push_back(Point(908, 246));
- *Homo = findHomography(dst, ori);//求得单应性矩阵
- }
单应性矩阵求得后,尝试给定一点进行验证。已知靶标图像中所有特征点的坐标 ;相应的特征点在机器人坐标系中的点,上述算法得到的单应性矩阵H,其三者满足。因此,撰写代码对下面要求得的坐标进行求解,代码如下所示。
坐标变换函数
- /*
- *函数名称:turn_coordinate
- *功能:将识别得到的图像坐标转换为机械臂的x,y,z世界坐标
- *输入参数1:识别到的点在图像坐标系下的坐标点集(vector)<Point2f>*
- *输入参数2:识别到的点在世界坐标系下的坐标点集(vector)<Point2f>*
- *输入参数3:单应性矩阵(Mat)
- */
- void turn_coordinate(vector<Point2f>* obj_image, Mat* Homo, vector<Point2f> &world_image) {
- short number = 5;
- Mat temp;
- for (int i = 0; i < number; i++) {
- temp = (Mat_<double>(3, 1) << (*obj_image)[i].x, (*obj_image)[i].y, 1);//图像坐标点坐标强行转换成1*3
- temp = *Homo * temp;
- temp = temp / temp.at<double>(2, 0);//得到世界坐标系值
- cout << "obj_world[" << i << "]=" << temp<< endl;//输出监控
- world_image.push_back(Point(temp.at<double>(0, 0), temp.at<double>(1, 0)));//推入world坐标系,供后续使用
- }
- cout << "*********************************************" << endl;
- }
要得到目标坐标值与旋转角度,最简单的方法就是利用阈值分割。坐标点与旋转角度的提取思路如下图5所示:
图5:图像识别思路
最终识别效果如下图6所示。
图6:最终识别效果
上述识别过程中,根据得到的坐标点与旋转角度,完成机器人运动控制,如图7所示,为最终识别到的物块坐标与旋转角度。
图7:物块坐标与旋转角度
运动控制选用封装好的moveP()函数方式运动,即输入目标位姿自动求逆解而后运动。运动之前,先执行一遍气爪松开的函数以防气爪异常,根据识别到的世界坐标系坐标组(vector容器)obj_world与旋转角度差值angle_obj来对位姿数组中每个物块的信息进行赋值,采用“门”运动的方式,并定义抓取物块的Z坐标值为105,安全高度为200。定义要搬运的位置坐标组obj_jaka,逐步完成物块搬运,并设置相应断点供操控。
图像识别中,最终识别到的效果如上图6所示,绿色边框为轮廓绘制,蓝色边框为轮廓外接矩形。蓝色数字为识别到的角点序号,黑色字体表示识别到的颜色。
输出监控中得到的中心坐标点在世界坐标系下如下图8所示。
图 8 输出监控:中心坐标
输出监控中得到的对角点组的坐标值在世界坐标系下如下图9所示。
图 9 输出监控:对角点组
输出监控中得到在世界坐标系下角度差值如下图10所示。
图 10 输出监控:角度差值
如上图所示,基本能够完成识别。但使用最小外接矩形来计算对角点时,橙色的识别没有特别准确。而此时它的二值化图像如下图11所示,经过分析可推测其最小外接矩形在拟合时并没有将点1和点3拟合正确,反而拟合到了点2和点4为准确的角点。
图 11 橙色二值化图像
对此问题,提出两种解决方法:
1.source.cpp 存放编写的各个函数的实现
- #include "stdafx.h"
- #include "source.h"
- #include <iostream>
- #include <opencv2/opencv.hpp>
- #include<vector>
-
- using namespace std;
- using namespace cv;
- #define PI 3.1415926
-
- /*
- *函数名称:calculate_Homo
- *功能:计算单应性矩阵
- *输入参数:要得到的单应性矩阵
- */
- void calculate_Homo(Mat* Homo) {
- vector<Point2f> ori;//三个目标点世界坐标,z:100,单位mm,rx=180,ry=0,rz=0
- ori.push_back(Point(294.923, 48.434));
- ori.push_back(Point(418.584, 32.481));
- ori.push_back(Point(435.834, 155.045));
- ori.push_back(Point(310.961, 172.286));
-
- vector<Point2f> dst;//三个目标点图像坐标
- dst.push_back(Point(708, 22));
- dst.push_back(Point(446, 104));
- dst.push_back(Point(595, 377));
- dst.push_back(Point(908, 246));
- *Homo = findHomography(dst, ori);//求得单应性矩阵
- }
-
- /*
- *函数名称:turn_coordinate
- *功能:将识别得到的图像坐标转换为机械臂的x,y,z世界坐标
- *输入参数1:识别到的点在图像坐标系下的坐标点集(vector)<Point2f>*
- *输入参数2:识别到的点在世界坐标系下的坐标点集(vector)<Point2f>*
- *输入参数3:单应性矩阵(Mat)
- */
- void turn_coordinate(vector<Point2f>* obj_image, Mat* Homo, vector<Point2f> &world_image) {
- short number = 5;
- Mat temp;
- for (int i = 0; i < number; i++) {
- temp = (Mat_<double>(3, 1) << (*obj_image)[i].x, (*obj_image)[i].y, 1);//图像坐标点坐标强行转换成1*3
- temp = *Homo * temp;
- temp = temp / temp.at<double>(2, 0);//得到世界坐标系值
- cout << "obj_world[" << i << "]=" << temp<< endl;//输出监控
- world_image.push_back(Point(temp.at<double>(0, 0), temp.at<double>(1, 0)));//推入world坐标系,供后续使用
- }
- cout << "*********************************************" << endl;
- }
-
-
- /*
- *函数名称:pre_image
- *功能:图像预处理、包括重设大小、改变颜色空间、均值化
- *输入参数1:初始图象
- *输入参数2:要得到的最终图像
- */
- void pre_image(Mat* InputImage, Mat* OutputImage) {
- //重设大小
- int h = (*InputImage).rows;
- int w = (*InputImage).cols;
- resize(*InputImage, *InputImage, Size(w / 2, h / 2), 0, 0, INTER_LINEAR);//对照片进行缩小
-
- //色彩空间处理
- cvtColor(*InputImage, *OutputImage, COLOR_BGR2HSV); //转成hsv空间
-
- }
-
-
- /*
- *函数名称:color_find
- *功能:颜色识别、返回图像坐标
- *输入参数1:坐标集合
- *输入参数2:输入图像
- * 输入参数3:输入图像的处理后的图像
- * 输入参数4:要得到的斜率点集的起始点的集合
- * 输入参数5:要得到的斜率点集的末尾点的集合
- */
- void color_find(vector<Point2f>& obj_image, Mat& image, Mat& image_hsv, vector<Point2f>& first_image, vector<Point2f>& third_image) {
- Mat image_copy = image.clone();//绘制用,copy
- //imshow("image", image);
- string color_string[5] = { "purple","yellow","red","blue","orange" };//按序显示字体使用
- //颜色识别
- Mat imgThresholded;
- for (int i = 0; i <= 4; i++) {
- //依次二值化
- switch (i) {
- case 0:
- inRange(image_hsv, Scalar(128, 7, 130), Scalar(179, 82, 161), imgThresholded); //紫色
- break;
- case 1:
- inRange(image_hsv, Scalar(20, 119, 208), Scalar(82, 255, 255), imgThresholded); //黄色
- break;
- case 2:
- inRange(image_hsv, Scalar(0, 168, 242), Scalar(7, 212, 255), imgThresholded); //红色
- break;
- case 3:
- inRange(image_hsv, Scalar(82, 157, 0), Scalar(104, 255, 197), imgThresholded); //蓝色
- break;
- case 4:
- inRange(image_hsv, Scalar(11, 168, 245), Scalar(14, 189, 255), imgThresholded); //橙色
- break;
- }
-
- //形态学操作 ,根据情况选择是否要进行形态学操作
- Mat kernel = getStructuringElement(MORPH_RECT, Size(2, 2));
- Mat image_erode= imgThresholded;
- //morphologyEx(imgThresholded, image_erode, MORPH_CLOSE, kernel);
- //erode(image_erode, image_erode, kernel);
-
- //找最大轮廓、
- vector <vector<Point>> contours;//输出轮廓图像,是一个向量,向量的每个元素都是一个轮廓
- vector<Vec4i> hierachy;//各个轮廓的继承关系
- findContours(image_erode, contours, hierachy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
- int c_max_index=0;//最大轮廓的下标
- find_max_area(contours, c_max_index);//得到最大下标用于绘制
- drawContours(image_copy, contours, c_max_index, Scalar(0,255,0), 1, 8);
-
- //得到对应颜色外接矩形
- Rect rect = boundingRect(Mat(contours[c_max_index]));//被轮廓包围的矩形
- //rectangle(image_copy, rect, (0, 0, 0), 2, 8, 0);//绘制它
-
- //找到中心点位置,
- Moments mu = moments(contours[c_max_index]);
- Point centerPos = cv::Point(mu.m10 / mu.m00, mu.m01 / mu.m00);//轮廓矩求质心
- circle(image_copy, centerPos, 1, cv::Scalar(0, 0, 255),2);
- obj_image.push_back(centerPos);//推入图像坐标组中
-
- //绘制,做提示识别到了哪种颜色
- putText(image_copy, color_string[i], Point(rect.x + rect.width, rect.y+40), FONT_HERSHEY_COMPLEX, 0.5, (0, 255, 0), 1, LINE_8);
-
- //求最小外接矩形 青色
- vector<string> v_num;//绘制用 字符串容器
- v_num.push_back("1");
- v_num.push_back("2");
- v_num.push_back("3");
- v_num.push_back("4");
- RotatedRect rotateRect = minAreaRect(contours[c_max_index]);//最小外接矩形
- vector<Point2f> boxPts(4);
- rotateRect.points(boxPts.data());
-
- //利用最小外接矩形求得边角点
- for (int j = 0; j < 4; j++)
- {
- cv::line(image_copy, boxPts[j], boxPts[(j + 1) % 4], cv::Scalar(128, 128, 0), 1, 8); //绘制最小外接矩形每条边
- //绘制,做提示得到点的位置,发现1和3总是对应的
- putText(image_copy, v_num[j], boxPts[j], FONT_HERSHEY_COMPLEX, 0.4, (0, 255, 255), 1, LINE_8);
- }
- first_image.push_back(boxPts[0]);
- third_image.push_back(boxPts[2]);//将点1和3分别推入其中
-
- //打印查看点是否正确
- //for (vector<Point2f>::iterator it = first_image.begin(); it != first_image.end(); it++) {
- // cout << "第一个点x=" << (*it).x << " 第一个点y=" << (*it).y << endl;
- //}
- //for (vector<Point2f>::iterator it = third_image.begin(); it != third_image.end(); it++) {
- // cout << "第三个点x=" << (*it).x << " 第三个点y=" << (*it).y << endl;
- //}
-
- //循环 等待按键Esc按下
- while (true) {
- imshow("image_erode", image_erode);
- imshow("image", image_copy);
- if (waitKey(0) == 27)
- break;
- }
-
- }
- }
-
- /*函数名称:寻找最大面积
- * 输入:当前图像求得的轮廓,要得到的最大面积下标
- * 功能:根据寻找到的轮廓,得到最大的轮廓
- */
- void find_max_area(vector <vector<Point>>& contours, int& max_index) {
- vector<double> g_dConArea(contours.size());
- for (int i = 0; i < contours.size(); i++)//计算面积
- {
- g_dConArea[i] = contourArea(contours[i]);
- //cout << "【用轮廓面积计算函数计算出来的第" << i << "个轮廓的面积为:】" << g_dConArea[i] << endl;
- }
- for (int i = 1; i < contours.size(); i++) {//遍历数组,返回最大下标
- if (g_dConArea[i] > g_dConArea[max_index]) {
- max_index = i;
- }
- }
- //cout <<"当前最大轮廓下标:"<< max_index<<endl;
- }
-
-
- /*函数名称:输入点得到要旋转的角度
- * 输入参数1:起始点组
- * 输入参数2:结束点组
- * 输入参数3:要得到的角度组vector<double>
- * 输入参数4:单应性矩阵Homo
- */
- void get_angle(vector<Point2f>& first_image, vector<Point2f>& third_image, vector<double> &angle_obj, Mat *Homo) {
- short number = 5;
- Mat temp,temp_re;
- //vector<Point3f> p;
- vector<Point2f> first_world;
- vector<Point2f> third_world;
-
- //得到起始和末尾在世界坐标系下的点组
- for (int i = 0; i < number; i++) {
- //图像坐标点坐标强行转换成1*3
- temp = (Mat_<double>(3, 1) << first_image[i].x, first_image[i].y, 1);//first组操作
- temp_re = (Mat_<double>(3, 1) << third_image[i].x, third_image[i].y, 1);//third组操作
-
- //单应性变化
- temp = (*Homo) * temp;//first组操作
- temp_re = (*Homo) * temp_re;//third组操作
-
- //得到世界坐标系值
- temp = temp / temp.at<double>(2, 0);//first组操作
- cout << "first_world[" << i + 1 << "]=" << temp << endl;
- temp_re = temp_re / temp_re.at<double>(2, 0);//third组操作
- cout << "third_world[" << i + 1 << "]=" << temp_re << endl;
- //推入world组中
- first_world.push_back(Point(temp.at<double>(0, 0), temp.at<double>(1, 0)));
- third_world.push_back(Point(temp_re.at<double>(0, 0), temp_re.at<double>(1, 0)));
- }
- cout << "*********************************************" << endl;
-
- //得到斜率与角度
- double k_temp;//定义临时斜率值
- double angle_temp;//定义临时角度
- for (int i = 0; i < number; i++) {
- k_temp = (first_world[i].y - third_world[i].y) / (first_world[i].x - third_world[i].x);
- angle_temp = abs(atan(k_temp) * 180 / PI)+45;//解出角度
- angle_temp = angle_temp - 90.0;//得到角度差值
- cout << "angle ["<<i <<"] - 90° = " << angle_temp << endl;//45是自给的,
- angle_obj.push_back(angle_temp);//推入目标组中
- }
- }
-
-
2.source.h 存放编写的函数声明
- #pragma once
- #include <iostream>
- #include <opencv2/opencv.hpp>
- #include<cmath>
- using namespace std;
- using namespace cv;
- void calculate_Homo(Mat* Homo);//根据现已知的点得到单应性矩阵
- void turn_coordinate(vector<Point2f>* obj_image, Mat* Homo, vector<Point2f>& world_image);//将识别得到的图像坐标转换为机械臂的x,y,z世界坐标
-
- void pre_image(Mat* InputImage, Mat* OutputImage);
- void color_find(vector<Point2f>& obj_image, Mat& image, Mat& image_hsv, vector<Point2f>& first_image, vector<Point2f>& third_image);
- void find_max_area(vector <vector<Point>>& contours, int& max_index);
- void get_angle(vector<Point2f>& first_image, vector<Point2f>& third_image, vector<double>& angle_obj, Mat* Homo);
3. main.cpp 完成运动控制
- // JAKAAPI.cpp : 定义控制台应用程序的入口点。
- //
-
- #include "stdafx.h"
- #include <iostream>
- #include <fstream>
- #include "JAKARobot_API.h"
- #include "source.h"
- #include<string.h>
- #include <opencv2/opencv.hpp>
-
- struct array_obj {
- int array[6];
- };
-
-
-
-
- using namespace std;
- using namespace cv;
-
- #define JAKA_Work 1//正常跑用这个
- #define test 0//测试用
-
-
- #if defined(__GNUC__)
- #pragma GCC diagnostic push
- #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
- #elif defined(_MSC_VER)
- #pragma warning(disable : 4996)
- #endif
-
- int main()
- {
-
- #if JAKA_Work
- //读取图片
- Mat image = imread("C://opencv_picture//3.jpg");
- Mat image_hsv;
- pre_image(&image, &image_hsv);//预处理
- vector<Point2f> obj_image;//图像坐标集,颜色依次为:紫、黄、绿 、蓝、红
- vector<Point2f> obj_world;//世界坐标系坐标
-
- vector<Point2f> first_image, third_image;//定义斜率点在图像坐标系中的起始点
- vector<double> angle_obj;//定义旋转角度组
-
- //颜色处理,得出坐标
- color_find(obj_image, image, image_hsv, first_image, third_image);
-
- //坐标变换
- Mat Homo;
- calculate_Homo(&Homo);//计算单应性矩阵
- turn_coordinate(&obj_image, &Homo,obj_world);//输入图像五个坐标求JAKA坐标系下的坐标
-
- //得到每个目标的旋转角度,并打印输出
- get_angle(first_image, third_image, angle_obj, &Homo);//得到每个目标的旋转角度 angle=0-90°
-
- cout << endl << endl << endl;
- system("pause");
-
- //定义要搬运到的点组
- vector<Point2f> obj_jaka;//世界坐标系坐标
- obj_jaka.push_back(Point(365.573, -105.260));
- obj_jaka.push_back(Point(295.559, -171.921));
- obj_jaka.push_back(Point(365.573, -150.260));
- obj_jaka.push_back(Point(315.573, -150.260));
- obj_jaka.push_back(Point(315.573, -105.260));
-
- //下面是机器人操作
- cout << "下面是机器人操作"<<endl;
- JAKAZuRobot jakaRob;
- int is_enable = 0, is_powered = 0;
- int ret = jakaRob.login_in("192.168.1.100");
- if (ret) printf("%s\n", jakaRob.m_errMsg);
- jakaRob.getState(is_enable, is_powered);
- if (!is_powered) {
- ret = jakaRob.power(1);
- Sleep(6);
- }
- if (!is_enable) {
- ret = jakaRob.enable(1);
- Sleep(6);
- }
-
- cout << "等待机器人初始化" << endl;
- jakaRob.initSetting();
- Sleep(10);
- double jointpos1[6], jointpos2[6];
- jakaRob.getJoints(jointpos1);
- double jointsHome[6] = { 0, 0, 0, -180, 0, 90 };
- cout << "开始搬运各个色块" << endl;
-
- ret = jakaRob.setDout(7, 1);
- cout << "气爪松开" << endl;//气爪松开
-
- for (int k = 0; k < 5; k++) {//循环完成五个点的搬运
-
-
- jointsHome[0] = obj_world[k].x;
- jointsHome[1] = obj_world[k].y;
- jointsHome[2] = 200;
- ret = jakaRob.moveP(jointsHome, 10.0);//移动到目标点上方
- ret = jakaRob.waitEndMove();
- Sleep(200);
-
- jointsHome[5] = 90-angle_obj[k];//旋转、这里是“-”是根据得到的差值来进行机器人旋转的补偿
- ret = jakaRob.moveP(jointsHome, 10.0);//移动到目标点上方
- ret = jakaRob.waitEndMove();
- Sleep(500);
-
- jointsHome[2] = 120;//移动到色块处
- ret = jakaRob.moveP(jointsHome, 10.0);
- ret = jakaRob.waitEndMove();
-
- system("pause");//断点式可控
-
- jointsHome[2] = 105;//移动到色块处
- ret = jakaRob.moveP(jointsHome, 10.0);
- ret = jakaRob.waitEndMove();
- Sleep(100);
-
- ret = jakaRob.setDout(7, 0);
- cout << "气爪合上" << endl;//气爪合
-
- Sleep(500);
- jointsHome[2] = 200;//抬起来
- ret = jakaRob.moveP(jointsHome, 10.0);
- ret = jakaRob.waitEndMove();
-
- Sleep(500);
- jointsHome[0] = obj_jaka[k].x;
- jointsHome[1] = obj_jaka[k].y;//移动到要放置的点的上方
- ret = jakaRob.moveP(jointsHome, 10.0);
- ret = jakaRob.waitEndMove();
-
- Sleep(100);
- jointsHome[2] = 108;//移动到要放置的点
- ret = jakaRob.moveP(jointsHome, 10.0);
- ret = jakaRob.waitEndMove();
- Sleep(100);
- ret = jakaRob.setDout(7, 1);
- cout << "气爪松开" << endl;//气爪松开
- Sleep(200);
- jointsHome[2] = 200;//抬起来
- ret = jakaRob.moveP(jointsHome, 10.0);
- ret = jakaRob.waitEndMove();
- cout << "第" << k+1 << "个色块已经放置完毕" << endl;
-
-
- system("pause");//断点式可控
-
- }
-
- jakaRob.getJoints(jointpos2);
-
- jakaRob.login_out();
- #endif
-
- #if test
- //读取图片
- Mat image = imread("C://opencv_picture//5.jpg");
- Mat image_hsv;
- pre_image(&image, &image_hsv);//预处理
- vector<Point2f> obj_image;//图像坐标集,颜色依次为:紫、黄、绿 、蓝、红
- vector<Point2f> obj_world;//世界坐标系坐标
-
- vector<Point2f> first_image, third_image;//定义斜率点在图像坐标系中的起始点
- vector<double> angle_obj;//定义旋转角度组
-
- color_find(obj_image, image, image_hsv, first_image, third_image);
-
- //坐标变换
- Mat Homo;
- calculate_Homo(&Homo);//计算单应性矩阵
- turn_coordinate(&obj_image, &Homo, obj_world);//输入图像五个坐标求JAKA坐标系下的坐标
-
- get_angle(first_image, third_image, angle_obj, &Homo);//得到每个目标的旋转角度 angle=0-90°
-
-
-
- #endif
- return 0;
- }
-
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。