形状识别——内六角螺丝内六角识别
注:本解决方案使用OpenCV4和OpenCV3实现过,写有非常详细的注释,现在贴出的是OpenCV4的代码,需要在OpenCV3中运行需要更改HOUGH_GRADIENT、COLOR_BGR2GRAY等宏定义,这些宏定义在OpenCV4中被更新。希望本工程能带给大家在形状识别上提供一些思路。
简介:本解决方案应用于机械臂拧内六角螺丝等需要内六角螺丝内六边形信息的项目,本解决方案可以提供内六角螺丝钉的信息,包括六个定点坐标,每条边的方程,每条边的边长,螺丝钉直径半径等。本解决方案消耗资源极低,经测试,一副250*250的图像跑完整个算法流程仅需要10ms
本方面技术方案包括以下几个部分:
1. 物体定位:使用霍夫圆变换确定图像中螺丝钉的具体位置、半径和圆心信息。
2. 图像预处理:将原RGB三通道图像压缩成灰度图,并调整图像至合适的对比度。
3. 图形拟合:
(1)进行轮廓检索,获得其边缘点集。
(2)使用approxPolyDP函数对点集进行拟合。
(3)螺丝正六边形形状大约处于半径的1/2处,所以关键点集应符合:点到圆心的距离在半径的1/3到2/3之间。
(4)将两条直线的角点也设为关键点,避免拟合后的关键点不是角点。如图
若有关键点a、b、c、d,这他们连线的角点k也是关键点,实现代码如下
/********************取交点********************************************************/for (int i = 0; i < src_point.size() - 2; i++) /*拓展关键点*/{key_point.push_back(key_line[i] & key_line[i + 2]); /*直线类&操作已重载,返回直线交点*/}key_point.push_back(key_line[src_point.size() - 1] & key_line[1]); /*倒数第一条线和第二条线的角点*/key_point.push_back(key_line[src_point.size() - 2] & key_line[0]); /*倒数第二条线和第一条线的角点*/
(5)对通过筛选的每一个关键点都拟合一个正六边形,拟合方法如下:
证六边形的两个连续角点和中心为一个等边三角形,即通过中心和边上任意一点可得正六边形的递推关系,如图
解方程组
获得正六边形下一个点坐标:
代码实现如下
Point2d Hexagon::nextPoint(Point2d p0, Point2d p1)
{double x0 = p0.x;double y0 = p0.y;double x1 = p1.x;double y1 = p1.y;double x = (x0*x0 - x1*x1 + y0*y0 - y1*y1 - (y0 - y1)*(y0 + y1 + pow(3., 0.5)*(x0 - x1))) / (2.*(x0 - x1)); /*matlab算出的正六边形递推公司*/double y = 0.5*(y0 + y1 + (x0-x1)*pow(3., 0.5)); /*matlab算出的正六边形递推公司*/return Point2d(x,y);
}
连续使用5次递推公式,即可得到一个正六边形。
4. 误差分析
本方案中,误差计算采用如下方案。
(1)角点误差:拟合出来的正六边形的角点和所有的关键点的距离,取最小值为该角点误差。
vector<int> err(6); /*误差容器*/for (int i = 0; i < 6; i++) /*遍历六边形6个理论角点*/{int err_tmp1 = INT_MAX; /*理论角点和关键点的最小误差*/int index = -1; /*关键点的下标*/int x2 = vertex[i].x;int y2 = vertex[i].y;for (int j = 0; j < points.size(); j++) /*遍历所有关键点*/{int x1 = points[j].x;int y1 = points[j].y;int err_tmp2 = (x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1); /*计算关键点和角点的误差*/if (err_tmp2 < err_tmp1) /*取最小误差作为角点和关键点误差*/{err_tmp1 = err_tmp2;index = j;}}points.erase(points.begin() + index); /*移除该关键点,避免一个关键点匹配多个角点的情况*/err[i] = err_tmp1; /*获得误差*/}
(2)拟合误差:取拟合的正六边形的前三个角点误差的和为拟合误差。
int err_all = 0; /*该正六边形的误差*/sort(err.begin(), err.end()); /*排序所有角点误差*/if (err.size() > 3) /*必须有4个点*/{err_all = abs(err[0]) + abs(err[1]) + abs(err[2]); /*取前3,返回误差为前三误差和*/return err_all;}
(3)最终误差:每一个关键点都会拟合出一个正六边形,所有关键点拟合出来的拟合误差的取最小值为最终误差。
效果图
工程源代码
main.cpp
#include <iostream>
#include <opencv2/opencv.hpp>
#include "line.h"
#include "hexagon.h"using namespace std;
using namespace cv;
/********************************************************************************/
/* 功能 : 角度转换成弧度 */
/* 入参 : DEG :角度 */
/* 出参 : 无 */
/* 返回值 : 弧度 */
/********************************************************************************/
double DEG2RAD(double DEG)
{double PI = 3.1415926535897932384626433832795;double RAD = DEG / 180. * PI;return RAD;
}
/********************************************************************************/
/* 功能 : 弧度转换成角度 */
/* 入参 : RAD :弧度 */
/* 出参 : 无 */
/* 返回值 : 角度 */
/********************************************************************************/
double RAD2DEG(double RAD)
{double PI = 3.1415926535897932384626433832795;double DEG = RAD / PI * 180.;return DEG;
}/********************************************************************************/
/* 功能 : 根据点集生成关键直线 */
/* 入参 : src_point :关键点集 */
/* 出参 : key_point :增加了关键点的点集 */
/* key_line :关键直线 */
/* 返回值 : 是否转换成功 */
/********************************************************************************/
bool getKeyPoint_Line(vector<Point> src_point,vector<Point>&key_point,vector<Line>&key_line)
{if (src_point.size() < 3) /*小于三个关键点直接返回*/{return false;}else{if (src_point.size() > 3) /*大于三个关键点*/{for (int j = 1; j < src_point.size(); j++){key_line.push_back(Line(src_point[j - 1], src_point[j])); /*将关键直线压入容器*/}key_line.push_back(Line(src_point[0], src_point[src_point.size() - 1]));/*连接最后一个关键点和第一个关键点*///内六角边缘和间隔为1的直线交点为关键点key_point = src_point; /*区边缘点*/
/********************取交点********************************************************/for (int i = 0; i < src_point.size() - 2; i++) /*拓展关键点*/{key_point.push_back(key_line[i] & key_line[i + 2]); /*直线类&操作已重载,返回直线交点*/}key_point.push_back(key_line[src_point.size() - 1] & key_line[1]); /*倒数第一条线和第二条线的角点*/key_point.push_back(key_line[src_point.size() - 2] & key_line[0]); /*倒数第二条线和第一条线的角点*/}return true;}
}/********************************************************************************/
/* 功能 : 筛选关键点,去除和圆心距离小于半径L_percent%和 */
/* 大于半径H_percent%的点 */
/* 入参 : in_point :关键点集 */
/* cent :中心坐标 */
/* rad :半径 */
/* L_percent :最小百分比 */
/* H_percent :最大百分比 */
/* 出参 : out_point :筛选后的关键点集 */
/* 返回值 : 是否转换成功 */
/********************************************************************************/
void checkPoint(vector<Point>in_point, vector<Point>&out_point, Point cent,int rad,double L_percent,double H_percent)
{out_point.clear(); /*清空输出容器*/for (int i = 0; i < in_point.size(); i++) /*遍历关键点*/{int x = in_point[i].x;int y = in_point[i].y;int dd = (x - cent.x)*(x - cent.x) + (y - cent.y)*(y - cent.y); /*计算关键点和中心的距离*/if (dd > (rad*rad*H_percent*H_percent) || dd < (rad*rad*L_percent*L_percent)){continue;}out_point.push_back(in_point[i]); /*合格的关键点装入容器*/}
}int main(void)
{Mat img = imread("5.jpg"); /*输入图片*/Mat show = img.clone(); /*克隆图像,最后图像显示用*/
/*************霍夫圆检测**********************************************************/Point cir_cent; /*圆心坐标*/int rad = 0; /*圆的半径*/cvtColor(img, img, COLOR_BGR2GRAY); /*图像灰度化*/vector<Vec3f> cir;HoughCircles(img, cir, HOUGH_GRADIENT, 2, 10000, 100, 20, 80, 120); /*霍夫圆*/cir_cent = Point(cir[0][0], cir[0][1]); /*获得圆心坐标*/rad = cir[0][2]; /*获得圆的半径*/
/*************边缘识别*************************************************************/vector<vector<Point>> contours; /*边缘点集*/Mat Contours_img;threshold(img, Contours_img, 25, 255, THRESH_BINARY_INV); /*图像二值化,使其出现内六边形轮廓*/imshow("threshold", Contours_img); /*展示二值化后的图像*/findContours(Contours_img, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); /*轮廓检测*/vector<Point>key_point; /*关键点*/vector<Line>key_line; /*关键直线*/for (int i = 0; i < contours.size(); i++) /*遍历轮廓*/{vector<Point>point; /*储存拟合后的轮廓*/approxPolyDP(contours[i], point, 6, true); /*拟合边缘*/if (point.size()<5 || point.size()>15) /*小于5个或大于15个边缘点的边缘舍去*/{continue;}checkPoint(point, key_point, cir_cent, rad, 1. / 3., 2. / 3.); /*到圆心距离为半径的2/3,舍去*/getKeyPoint_Line(key_point, key_point, key_line); /*获取关键直线*/checkPoint(key_point, key_point, cir_cent, rad, 1. / 3., 2. / 3.); /*再次检测*/
/************遍历计算拟合6边形,寻找最优解******************************************/vector<Hexagon> hexagons;for (int i = 0; i < key_point.size(); i++){hexagons.push_back(Hexagon(cir_cent,key_point[i]));circle(img, key_point[i], 10, 100); /*画关键点*/}for (int i = 0; i < key_line.size();i++){key_line[i].draw(img, 100); /*画线*/}vector<int> err; /*误差容器,储存说有六边形误差*/for (int i = 0; i < hexagons.size(); i++) /*计算所有关键点误差*/{err.push_back(hexagons[i].getError(key_point));}int hexagons_index = 0; /*容器中最优六边形下标*/for (int i = 0; i < err.size(); i++) /*取最小误差*/{if (hexagons_index > err[i]){hexagons_index = err[i];}}if (hexagons.size()>hexagons_index) /*防止没有关键点容器越界报错*/{hexagons[hexagons_index].draw(show, Scalar(0,255,0),1); /*画最优6边形*/hexagons[hexagons_index].draw(img, 150, 1); /*画最优6边形*/}}imshow("show_", show);circle(img, cir_cent, rad, 150); /*画霍夫圆*/imshow("img", img);waitKey();return 0;
}
hexagon.h
#ifndef _HEXAGON_H_
#define _HEXAGON_H_
#include <iostream>
#include <opencv2/opencv.hpp>using namespace std;
using namespace cv;
/********************************************************************************/
/* 功能 : 一个用于储存正六边形参数的类 */
/********************************************************************************/
class Hexagon
{public:Hexagon();Hexagon(Point2d cent, Point2d point); /*通过中心点和其中一个角点构造一个正六边形*/Hexagon(Point2d p1, Point2d p2, Point2d p3); /*通过三个连续的点构造一个正六边形*/void draw(Mat &img, const Scalar& color, /*画六边形*/int thickness = 1, int LineType = LINE_8, int shift = 0);int getError(vector<Point> points); /*计算误差六边形*/~Hexagon();private:Point2d nextPoint(Point2d p1, Point2d p2); /*计算正六边形的下一个点坐标*/
public:Point2d vertex[6]; /*正六边形六个角点*/
};#endif
hexagon.cpp
#include "Hexagon.h"Hexagon::Hexagon()
{}/********************************************************************************/
/* 功能 : 计算正六边形的下一个点坐标 */
/* 入参 : p0 :角点 */
/* p1 :角点 */
/* 出参 : 下一个角点 */
/********************************************************************************/
Point2d Hexagon::nextPoint(Point2d p0, Point2d p1)
{double x0 = p0.x;double y0 = p0.y;double x1 = p1.x;double y1 = p1.y;double x = (x0*x0 - x1*x1 + y0*y0 - y1*y1 - (y0 - y1)*(y0 + y1 + pow(3., 0.5)*(x0 - x1))) / (2.*(x0 - x1)); /*matlab算出的正六边形递推公司*/double y = 0.5*(y0 + y1 + (x0-x1)*pow(3., 0.5)); /*matlab算出的正六边形递推公司*/return Point2d(x,y);
}/********************************************************************************/
/* 功能 : 通过中心点和其中一个角点构造一个正六边形 */
/* 入参 : cent :中心 */
/* point :角点 */
/* 出参 : 无 */
/********************************************************************************/
Hexagon::Hexagon(Point2d cent, Point2d point)
{if (cent == point){return;}vertex[0] = point;for (int i = 1; i < 6; i++){vertex[i] = nextPoint(vertex[i - 1], cent); /*构造一个正六边形*/}
}/********************************************************************************/
/* 功能 : 通过三个连续的点构造一个正六边形 */
/* 入参 : p1 :角点 */
/* p2 :角点 */
/* p3 :角点 */
/* 出参 : 无 */
/********************************************************************************/
Hexagon::Hexagon(Point2d p1, Point2d p2, Point2d p3)
{if (p1 == p2 || p1 == p3 || p2 == p3) /*三个点不能相等*/{return;}vertex[0] = p1;Point2d cent0 = nextPoint(p1, p2); /*计算中心*/Point2d cent1 = nextPoint(p2, p3); /*计算中心*/Point2d cent = (cent0 + cent1) / 2.; /*僵持计算的中心取平均*/for (int i = 1; i < 6; i++){vertex[i] = nextPoint(vertex[i - 1], cent); /*构造一个正六边形*/}
}/********************************************************************************/
/* 功能 : 画六边形 */
/* 入参 : img :图像 */
/* color :颜色 */
/* thickness :线宽 */
/* LineType :线型 */
/* shift :点坐标中的小数位数 */
/* 出参 : 无 */
/* 返回值 : 无 */
/********************************************************************************/
void Hexagon::draw(Mat &img, const Scalar& color,int thickness, int LineType, int shift)
{for (int i = 1;i<6;i++){line(img, vertex[i-1], vertex[i], color, thickness, LineType, shift);}line(img, vertex[0], vertex[5], color, thickness, LineType, shift);
}/********************************************************************************/
/* 功能 : 计算误差六边形 */
/* 入参 : points :关键点集 */
/* 出参 : 无 */
/* 返回值 : 误差 */
/********************************************************************************/
int Hexagon::getError(vector<Point> points)
{vector<int> err(6); /*误差容器*/for (int i = 0; i < 6; i++) /*遍历六边形6个理论角点*/{int err_tmp1 = INT_MAX; /*理论角点和关键点的最小误差*/int index = -1; /*关键点的下标*/int x2 = vertex[i].x;int y2 = vertex[i].y;for (int j = 0; j < points.size(); j++) /*遍历所有关键点*/{int x1 = points[j].x;int y1 = points[j].y;int err_tmp2 = (x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1); /*计算关键点和角点的误差*/if (err_tmp2 < err_tmp1) /*取最小误差作为角点和关键点误差*/{err_tmp1 = err_tmp2;index = j;}}points.erase(points.begin() + index); /*移除该关键点,避免一个关键点匹配多个角点的情况*/err[i] = err_tmp1; /*获得误差*/}int err_all = 0; /*该正六边形的误差*/sort(err.begin(), err.end()); /*排序所有角点误差*/if (err.size() > 3) /*必须有4个点*/{err_all = abs(err[0]) + abs(err[1]) + abs(err[2]); /*取前3,返回误差为前三误差和*/return err_all;}else{return 0;}
}Hexagon::~Hexagon()
{}
line.h
#ifndef _LINE_H_
#define _LINE_H_#include <iostream>
#include <opencv2/opencv.hpp>using namespace std;
using namespace cv;
/********************************************************************************/
/* 功能 : 一个用于储存直线参数的类 */
/********************************************************************************/
class Line
{public:Line();Line(double k,double b); /*通过斜率和截距构造直线类*/Line(Point2d pt1, Point2d pt2); /*通过两点构造直线类*/void getFrom2pt(Point2d pt1, Point2d pt2); /*通过两个点来构造直线类*/void draw(Mat &img, const Scalar& color, /*画直线*/int thickness = 1, int LineType = LINE_8, int shift = 0);Point2d Line::operator &(Line L);friend ostream &operator << (ostream &os,const Line L);~Line();
public:double k; /*直线斜率*/double b; /*直线截距*/
};#endif
line.cpp
#include "Line.h"Line::Line()
{this->b = 0;this->k = 0;
}/********************************************************************************/
/* 功能 : 通过斜率和截距构造直线类 */
/* 入参 : k :斜率 */
/* b :截距 */
/* 出参 : 无 */
/********************************************************************************/
Line::Line(double k, double b)
{this->b = b;this->k = k;
}/********************************************************************************/
/* 功能 : 通过两点构造直线类 */
/* 入参 : pt1 :起点 */
/* pt2 :终点 */
/* 出参 : 无 */
/********************************************************************************/
Line::Line(Point2d pt1, Point2d pt2)
{double y0 = pt1.y, y1 = pt2.y, x0 = pt1.x, x1 = pt2.x;k = (y0 - y1) / (x0 - x1);b = y0 - (y0 - y1) / (x0 - x1)*x0;
}/********************************************************************************/
/* 功能 : 通过两个点来构造直线类 */
/* 入参 : pt1 :起点 */
/* pt2 :终点 */
/* 出参 : 无 */
/* 返回值 : 无 */
/********************************************************************************/
void Line::getFrom2pt(Point2d pt1, Point2d pt2)
{Line(pt1, pt2);
}/********************************************************************************/
/* 功能 : 重载与操作符 */
/* 入参 : L :直线 */
/* 出参 : 无 */
/* 返回值 : 交点 */
/********************************************************************************/
Point2d Line::operator &(Line L)
{Point2d Intersection;Intersection.x = (L.b - b) / (k - L.k);Intersection.y = k*Intersection.x + b;return Intersection;
}/********************************************************************************/
/* 功能 : 画直线 */
/* 入参 : img :图像 */
/* color :颜色 */
/* thickness :线宽 */
/* LineType :线型 */
/* shift :点坐标中的小数位数 */
/* 出参 : 无 */
/* 返回值 : 无 */
/********************************************************************************/
void Line::draw(Mat &img, const Scalar& color,int thickness, int LineType, int shift)
{Point2d draw_p1(10000, k * 10000 + b), draw_p2(-10000, k*(-10000) + b);line(img, draw_p1, draw_p2, color, thickness, LineType, shift);
}/********************************************************************************/
/* 功能 : 重载与左移操作符 */
/* 入参 : os :输出流对象 */
/* L :直线 */
/* 出参 : 无 */
/* 返回值 : 输出流对象 */
/********************************************************************************/
ostream &operator << (ostream &os, const Line L)
{if (L.b>0){os << "y = " << L.k << "x+" << L.b;}else{os << "y = " << L.k << "x" << L.b;}return os;
}Line::~Line()
{}
形状识别——内六角螺丝内六角识别相关推荐
- 【书法字识别】余弦形状相似度书法字识别【含Matlab源码 1356期】
⛄一.余弦形状相似度书法字识别简介 1 实验原理 以下理论来自于<根据形状相似性的书法内容检索>(中图分类号TP39) 1.1 轮廓点的形状属性 只判断轮廓的特征信息较判断所有像素信息计算 ...
- 鸟类识别,小鸟进食识别,小鸟归巢识别
鸟类识别,小鸟进食识别,小鸟归巢识别录标题) GZTAG-901 通道电子标识器 WX15989000855 1.低频识别器24小时工作,工作时长240个小时. 2.低频识别器,作用是读取动物身上的标 ...
- 国税局发票查验中英文验证码识别最新版,识别率99.9%
采用深度学习进行发票查验验证码模型的训练,在我电脑上模型训练的环境如下: 显卡:RTX 2080TI tensorflow-gpu:2.5.3 1 训练集和测试集的准备 发票查验的验证码分为4种类型, ...
- Python的开源人脸识别库:离线识别率高达99.38%【源码】
以往的人脸识别主要是包括人脸图像采集.人脸识别预处理.身份确认.身份查找等技术和系统.现在人脸识别已经慢慢延伸到了ADAS中的驾驶员检测.行人跟踪.甚至到了动态物体的跟踪.由此可以看出,人脸识别系统已 ...
- 常用的表格检测识别方法——表格结构识别方法 (下)
常用的表格检测识别方法--表格结构识别方法(下) 3.2表格结构识别方法 表格结构识别是表格区域检测之后的任务,其目标是识别出表格的布局结构.层次结构等,将表格视觉信息转换成可重建表格的结构描述信息. ...
- 【颜色识别】机器视觉RGB识别系统【含GUI Matlab源码 951期】
⛄一.机器视觉RGB识别简介 颜色是物体表面的固有特征, 在目标识别和图像分割中有着无法替代的作用.机器视觉是利用光电成像系统和图像处理模块对物体进行尺寸.形状.颜色等的识别.这样, 就把计算机的快速 ...
- 狗脸识别python_实现人脸识别、人脸68个特征点提取,或许这个 Python 库能帮到你!...
之前写过一篇关于实现人脸识别的文章,里面用到的技术是通过调用百度 API 实现的,本次将借助于 dlib 程序包实现人脸区域检测.特征点提取等功能, dlib 封装了许多优秀的机器学习算法, 可实现 ...
- 基于卷积神经网络的手写汉字识别[matlab版本][可识别509类汉字]
基于卷积神经网络的手写汉字识别[matlab版本][可识别509类汉字] ####一. 数据集的获取 数据集的获取来自模式识别国家重点实验室共享,这个不解释直接上网址http://www.nlpr.i ...
- python调用百度识别文字接口_python调用百度通用文字识别接口进行验证码识别
官方文档入口 https://cloud.baidu.com/doc/OCR/OCR-Python-SDK.html#.E7.AE.80.E4.BB.8B 安装 pip3 install baidu- ...
最新文章
- AI 能匹敌程序员了吗?OpenAI 新研究展​示 NLP 大模型的局限性
- (转)java.lang.OutOfMemoryError: Java heap space错误及处理办法(收集整理、转)
- oracle 孟硕_关于几大主机厂的阿里云论坛用户知识和技术交流
- 微信小游戏开发教程-游戏实现1
- 2018年超大规模数据中心总数达到430个
- SAP ITS mobile 简介
- DotNetty 实现 Modbus TCP 系列 (二) ModbusFunction 类图及继承举例
- 记一次解决问题的掉坑过程
- Microsoft.NET框架程序设计--18 异常
- (寒假集训)Mooo Moo (完全背包)
- ORA-12541 TNS:无监听程序问题解决
- wps 插件_【追加功能】OFFICE插件管理工具重整后再上路,更好用易用。
- java已解密的登录请求_使用https协议解决掉顽固不化的已解密的登录请求
- oracle11g数据库登录01017,连接oracle数据库报错ORA-01017、重置用户的密码
- 纠错码与魔术(一)——纠错码与汉明码简介
- 计算机伦理学案例分析,医药伦理学案例分析
- jquery.printarea.js 局部打印去掉页眉页脚
- Android专业DJ,著名音乐游戏《DJ英雄》登陆Android Market
- “Unexpected end of JSON input while parsing near···”错误解决方案
- excel 删除重复行数据,列数据