点击上方“3D视觉工坊”,选择“星标”
干货第一时间送达
整理:公众号@图像处理与计算机视觉
本文仅做学术分享,如有侵权,请联系删除。
视觉的方法可以用来估计位置和姿态。最容易想到的是在目标上布置多个容易识别的特征,这样使用opencv相机标定和、相机畸变矫正、轮廓提取、solvepnp来获取目标相对于相机的位姿。在实际使用中只要相机和目标一方是估计的,那就可以得到全局坐标。如果相机和靶标都在移动,那只能获取到相对坐标。但是受限于相机视角和景深,这样多个特征的识别虽然精度可以很高,但是范围却很小。
对于如何扩大范围,使用二维码是一个很好的思路。首先,二维码本身具有多个特征,单个二维码可以用来实现上述方法的功能。其次,二维码本身带有信息,如果二维码的布置事先已知,那么位置和姿态估计的范围将只受限于二维码的数量。
本文主要是二维码的特征识别和信息识别。
二维码是在一个网站上生成的,经过手机的测试,生成的二维码没有问题。http://www.liantu.com/,该网站是免费的,生成的图片及二维码各种参数可以自定义。
本文的测试图片有20张,是数字1到5,每个数字隔90度旋转各一张。
二维码特征识别的思路是:第一步,寻找二维码的三个角的定位角点,需要对图片进行平滑滤波,二值化,寻找轮廓,筛选轮廓中有两个子轮廓的特征,从筛选后的轮廓中找到面积最接近的3个即是二维码的定位角点。第二步:判断3个角点处于什么位置,主要用来对图片进行透视校正(相机拍到的图片)或者仿射校正(对网站上生成的图片进行缩放拉伸旋转等操作后得到的图片)。需要判断三个角点围成的三角形的最大的角就是二维码左上角的点。然后根据这个角的两个边的角度差确定另外两个角点的左下和右上位置。第三步,根据这些特征识别二维码的范围。
具体的代码:
Mat src = imread( "pic\\456.jpg", 1 ); if(src.empty())
{
fprintf(stderr, "Can not load image!\n");
return 0;
}
Mat src_all=src.clone();
//彩色图转灰度图
Mat src_gray;
cvtColor( src, src_gray, CV_BGR2GRAY );
//对图像进行平滑处理
blur( src_gray, src_gray, Size(3,3) );
//使灰度图象直方图均衡化
equalizeHist( src_gray, src_gray );
namedWindow("src_gray");
imshow("src_gray",src_gray); //灰度图
//指定112阀值进行二值化
Mat threshold_output;
threshold( src_gray, threshold_output, 112, 255, THRESH_BINARY );
#ifdef DEBUG
namedWindow("二值化后输出");
imshow("二值化后输出",threshold_output); //二值化后输出#endif
//需要的变量定义
Scalar color = Scalar(1,1,255 );
vector> contours,contours2;
vector hierarchy;
Mat drawing = Mat::zeros( src.size(), CV_8UC3 );
//Mat drawing2 = Mat::zeros( src.size(), CV_8UC3 );
Mat drawingAllContours = Mat::zeros( src.size(), CV_8UC3 );
//利用二值化输出寻找轮廓
findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0) );
//寻找轮廓的方法
int tempindex1 = 0; int tempindex2 = 0; for(int i = 0;i::iterator it; for(it = vin.begin();it != vin.end();)
{ vector out1Contours = contours[it->a1]; vector out2Contours = contours[it->a2]; double lenth1 = arcLength(out1Contours,1); double lenth2 = arcLength(out2Contours,1); if(abs(lenth1/lenth2-2)>1)
{
it = vin.erase(it);
} else
{
drawContours( drawing, contours, it->a1, CV_RGB(255,255,255) , CV_FILLED, 8);
it++;
}
} //获取三个定位角的中心坐标
Point point[3]; int i = 0; vector pointthree; for(it = vin.begin(),i = 0;it != vin.end();i++,it++)
{
point[i] = Center_cal( contours, it->a1 );
pointthree.push_back(point[i]);
} if(pointthree.size() <3)
{ cout << "找到的定位角点不足3个"< 0) ccw1 = 0; else ccw1 = 1;
ca[0] = pointthree[0].x - pointthree[1].x;
ca[1] = pointthree[0].y - pointthree[1].y;
cb[0] = pointthree[2].x - pointthree[1].x;
cb[1] = pointthree[2].y - pointthree[1].y; double angle2 = 180/3.1415*acos((ca[0]*cb[0]+ca[1]*cb[1])/(sqrt(ca[0]*ca[0]+ca[1]*ca[1])*sqrt(cb[0]*cb[0]+cb[1]*cb[1]))); double ccw2; if(ca[0]*cb[1] - ca[1]*cb[0] > 0) ccw2 = 0; else ccw2 = 1;
ca[0] = pointthree[1].x - pointthree[2].x;
ca[1] = pointthree[1].y - pointthree[2].y;
cb[0] = pointthree[0].x - pointthree[2].x;
cb[1] = pointthree[0].y - pointthree[2].y; double angle3 = 180/3.1415*acos((ca[0]*cb[0]+ca[1]*cb[1])/(sqrt(ca[0]*ca[0]+ca[1]*ca[1])*sqrt(cb[0]*cb[0]+cb[1]*cb[1]))); double ccw3; if(ca[0]*cb[1] - ca[1]*cb[0] > 0) ccw3 = 0; else ccw3 = 1;
CvPoint2D32f poly[4]; if(angle3>angle2 && angle3>angle1)
{ if(ccw3)
{
poly[1] = pointthree[1];
poly[3] = pointthree[0];
} else
{
poly[1] = pointthree[0];
poly[3] = pointthree[1];
}
poly[0] = pointthree[2];
Point temp(pointthree[0].x + pointthree[1].x - pointthree[2].x , pointthree[0].y + pointthree[1].y - pointthree[2].y );
poly[2] = temp;
} else if(angle2>angle1 && angle2>angle3)
{ if(ccw2)
{
poly[1] = pointthree[0];
poly[3] = pointthree[2];
} else
{
poly[1] = pointthree[2];
poly[3] = pointthree[0];
}
poly[0] = pointthree[1];
Point temp(pointthree[0].x + pointthree[2].x - pointthree[1].x , pointthree[0].y + pointthree[2].y - pointthree[1].y );
poly[2] = temp;
} else if(angle1>angle2 && angle1 > angle3)
{ if(ccw1)
{
poly[1] = pointthree[1];
poly[3] = pointthree[2];
} else
{
poly[1] = pointthree[2];
poly[3] = pointthree[1];
}
poly[0] = pointthree[0];
Point temp(pointthree[1].x + pointthree[2].x - pointthree[0].x , pointthree[1].y + pointthree[2].y - pointthree[0].y );
poly[2] = temp;
}
CvPoint2D32f trans[4]; int temp = 50;
trans[0] = Point2f(0+temp,0+temp);
trans[1] = Point2f(0+temp,100+temp);
trans[2] = Point2f(100+temp,100+temp);
trans[3] = Point2f(100+temp,0+temp); //获取透视投影变换矩阵
CvMat *warp_mat = cvCreateMat(3, 3, CV_32FC1);
cvGetPerspectiveTransform(poly, trans, warp_mat); //计算变换结果
IplImage ipl_img(src_all);
IplImage *dst = cvCreateImage(cvSize(1000, 1000), 8, 3);
cvWarpPerspective(&ipl_img,dst,warp_mat); //=========================================#ifdef DEBUG
namedWindow("透视变换后的图");
cvShowImage("透视变换后的图",dst); //透视变换后的图
drawContours( drawingAllContours, contours, -1, CV_RGB(255,255,255) , 1, 8);
namedWindow("DrawingAllContours");
imshow( "DrawingAllContours", drawingAllContours );
namedWindow(pathtemp);
imshow(pathtemp , drawing ); //3个角点填充#endif
//接下来要框出这整个二维码
Mat gray_all,threshold_output_all;
vector > contours_all;
vector hierarchy_all;
cvtColor( drawing, gray_all, CV_BGR2GRAY );
threshold( gray_all, threshold_output_all, 45, 255, THRESH_BINARY );
findContours( threshold_output_all, contours_all, hierarchy_all, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(0, 0) );//RETR_EXTERNAL表示只寻找最外层轮廓
Point2f fourPoint2f[4];
//求最小包围矩形
RotatedRect rectPoint = minAreaRect(contours_all[0]);
//将rectPoint变量中存储的坐标值放到 fourPoint的数组中
rectPoint.points(fourPoint2f);
for (int i = 0; i < 4; i++)
{
line(src_all, fourPoint2f[i%4], fourPoint2f[(i + 1)%4],
Scalar(20,21,237), 3);
}
namedWindow(pathtemp);
imshow(pathtemp , src_all );
//截取二维码区域
CvSize size= cvSize(200,200);//区域大小
cvSetImageROI(dst,cvRect(0,0,size.width, size.height));//设置源图像ROI
IplImage* pDest = cvCreateImage(size,dst->depth,dst->nChannels);//创建目标图像
cvCopy(dst,pDest); //复制图像
cvSaveImage("Roi.jpg",pDest);//保存目标图像
二维码的信息识别使用的是zbar,一个开源的二维码识别库,经过测试,对图像进行平滑,灰度等处理后识别效率还是很高的。zbar的算法流程简介:http://blog.csdn.net/u013738531/article/details/54574262。
//对截取后的区域进行解码
Mat imageSource = cv::Mat(pDest);
cvResetImageROI(pDest);//源图像用完后,清空ROI
cvtColor( imageSource, imageSource, CV_BGR2GRAY ); //zbar需要输入灰度图像才能很好的识别
//Zbar二维码识别
ImageScanner scanner;
scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1);
int width1 = imageSource.cols;
int height1 = imageSource.rows;
uchar *raw = (uchar *)imageSource.data;
Image imageZbar(width1, height1, "Y800", raw, width1 * height1);
scanner.scan(imageZbar); //扫描条码
Image::SymbolIterator symbol = imageZbar.symbol_begin();
if(imageZbar.symbol_begin()==imageZbar.symbol_end())
{
cout<<"查询条码失败,请检查图片!"<get_type_name()<get_data()<
运行结果1:网站上生成的二维码,依次是原图,二值化,角点定位,旋转矫正,识别结果。
运行结果2:相机拍摄的名片上二维码,依次是灰度图,二值化,角点定位,透视矫正,识别结果。
推荐阅读:
专辑|相机标定
专辑|3D点云
专辑|SLAM
专辑|深度学习与自动驾驶
专辑|结构光
专辑|事件相机
专辑|OpenCV学习
专辑|学习资源汇总
专辑|招聘与项目对接
专辑|读书笔记
重磅!3DCVer-学术论文写作投稿 交流群已成立
扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。
同时也可申请加入我们的细分方向交流群,目前主要有3D视觉、CV&深度学习、SLAM、三维重建、点云后处理、自动驾驶、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流等微信群,请扫描下面微信号加群,备注:”研究方向+学校/公司+昵称“,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进去相关微信群。原创投稿也请联系。
▲长按加微信群或投稿
▲长按关注公众号
3D视觉从入门到精通知识星球:针对3D视觉领域的知识点汇总、入门进阶学习路线、最新paper分享、疑问解答四个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近1000+星球成员为创造更好的AI世界共同进步,知识星球入口:
学习3D视觉核心技术,扫描查看介绍,3天内无条件退款
圈里有高质量教程资料、可答疑解惑、助你高效解决问题