[图像识别] 1、如何识别一个指针式的时种的时间?

目录

一、算法基本原理

1、图片预处理

2、找表盘

3、找指针

4、指针映射

5、求时间

二、算法流程图

三、程序关键函数说明

1Canny

2HoughCircles

3HoughLines2

4MyLine

5平面几何相关函数

四、运行结果

五、实验中遇到的主要问题及解决方法:

1、在处理速度方面

2、去除其他圆的影响

3、霍夫找到的直线转换为夹角表示的线段

六、实验中的缺陷和不足



一、算法基本原理

时钟识别,顾名思义:就是根据一张带有钟表的图片识别出钟表上所展示的时间。对于这个问题我把它分为四步处理:

  • 1、 图片预处理

  由于一张图片中包含的圆圈和直线的信息量较大,直接进行霍夫处理要浪费大量的运算量,而且也不利于去除各种干扰。于是这里所谓的预处理就是利用canny算法先对原图进行边缘提取,一方面滤掉部分干扰,另一方面将原图转换为边缘图后只剩下主要线条信息,有利于加快处理。实验证明,用此方法有用信息丢失较少,可以采用!

  • 2、 找表盘

  找表盘主要运用霍夫找圆,但是由于一张图里会找出很多圆,因此关键还是滤掉其他有影响的圆!这里采用的方法是找出所有用霍夫找到的圆中半径最大且整个圆都在当前图片中的那个圆作为表盘。实践证明,用这种方法能够较为准确的去除其他圆的影响并准确找到表盘所对应的圆(对了,这里的钟默认为圆形的表盘的指针式钟)。

  • 3、 找指针

  找指针主要运用霍夫找线段,但是可想而知一张图里除了指针外一定还存在其他线段,而这些线段会对指针识别造成影响。因此我采用判断线段到圆心的距离小于一定的距离才默认为指针。然后对获取求得的线段进行转换为我定义的用斜率表示的线段的形式。

  • 4、 指针映射

  由于上面求得的线段并不是严格上的指针,而是很多线段,但是如果仔细观察会发现虽然并不是严格的对应关系,但是他们会出现成簇的分组效果!即:多条线段会聚集在某一个指针左右,而这些线段的斜率和预求得指针的斜率相差不大,我正是利用它的这个特点首先根据指针与X轴正方向的夹角从小到大进行排序,然后每次遇到相邻两个线段的夹角存在较大的跳变就进行切割,最后所有的线段被分割为一些组,再利用一个特殊的公式重新计算这组所代表指针的长度:

                        Le[num-1]=Le_ping[num-1]*0.2+Le_max[num-1]*0.8;

  • 5、 求时间

  通过上面四步的计算我们已经能够将线段分为三组或者两组,这样再根据长短来和时分秒三个或者时分指针进行对应,再根据夹角就能求出对应的时间。

二、算法流程图

三、程序关键函数说明

1、 Canny

void cvCanny( const CvArr* image,CvArr* edges,double
threshold1,double threshold2, int aperture_size=3 );

说明: 开放计算机视觉(OpenCV)库库函数之一,用于对图像的边缘检测(采用canny算法)。

  • image
    输入单通道图像(可以是彩色图像)对于多通道的图像可以用cvCvtColor()修改。
  • edges
    输出的边缘图像 ,也是单通道的,但是是黑白的
  • threshold1
    第一个阈值
  • threshold2
    第二个阈值
  • aperture_size
    Sobel 算子内核大小

附加说明: 函数 cvCanny 采用
Canny 算法发现输入图像的边缘而且在输出图像中标识这些边缘。threshold1和threshold2
当中的小阈值用来控制边缘连接,大的阈值用来控制强边缘的初始分割。

2、 HoughCircles

CvSeq *cvHoughCircles(CvArr *image,void
*circle_storage,int method,double dp,double min_dist,double param1,double
param2,int min_radius,int max_radius);

说明:该函数用Hough变换在二值图像中中寻找圆,成功时返回CvSeq指针。

  • image:输入8bit(灰度)图像,其内容可被函数所改变
  • circle_storage:检测到的圆存储仓,可以是内存存储仓
    (此种情况下,一个线段序列在存储仓中被创建,并且由函数返回)或者是包含圆参数的特殊类型的具有单行/单列的CV_32FC3型矩阵(CvMat*).
    矩阵头为函数所修改,使得它的 cols/rows 将包含一组检测到的圆。如果
    circle_storage 是矩阵,而实际圆的数目超过矩阵尺寸,那么最大可能数目的圆被返回,每个圆由三个浮点数表示:圆心坐标(x,y)和半径.).
  • method:Hough
    变换方式,目前只支持CV_HOUGH_GRADIENT, which is basically 21HT,
    described in [Yuen03].
  • dp:寻找圆弧圆心的累计分辨率,这个参数允许创建一个比输入图像分辨率低的累加器。(这样做是因为有理由认为图像中存在的圆会自然降低到与图像宽高相同数量的范畴)。如果dp设置为1,则分辨率是相同的;如果设置为更大的值(比如2),累加器的分辨率受此影响会变小(此情况下为一半)。dp的值不能比1小。
  • min_dist:该参数是让算法能明显区分的两个不同圆之间的最小距离。
  • param1:用于Canny的边缘阀值上限,下限被置为上限的一半。
  • param2:累加器的阀值。
  • min_radius:最小圆半径。
  • max_radius:最大圆半径。

3、 HoughLines2

CvSeq* cvHoughLines2(CvArr* image,void*
line_storage,int mehtod,double rho,double theta,int threshold,double param1
=0,double param2 =0);

说明: 此函数是opencv图像变换函数中的一个,主要用来访问霍夫变换的两个算法———标准霍夫变换(SHT)和累计概率霍夫变换(PPHT)。

  • Image:输入
    8-比特、单通道 (二值) 图像,当用CV_HOUGH_PROBABILISTIC方法检测的时候其内容会被函数改变。
  • line_storage:检测到的线段存储仓.
    可以是内存存储仓 (此种情况下,一个线段序列在存储仓中被创建,并且由函数返回),或者是包含线段参数的特殊类型(见下面)的具有单行/单列的矩阵(CvMat*)。矩阵头为函数所修改,使得它的
    cols/rows 将包含一组检测到的线段。如果
    line_storage 是矩阵,而实际线段的数目超过矩阵尺寸,那么最大可能数目的线段被返回(线段没有按照长度、可信度或其它指标排序).
  • method
  • Hough
    变换变量,是下面变量的其中之一:

CV_HOUGH_STANDARD
- 传统或标准 Hough 变换.
每一个线段由两个浮点数 (ρ,
θ) 表示,其中 ρ 是直线与原点
(0,0) 之间的距离,θ 线段与
x-轴之间的夹角。因此,矩阵类型必须是 CV_32FC2 type.

CV_HOUGH_PROBABILISTIC
- 概率 Hough 变换(如果图像包含一些长的线性分割,则效率更高).
它返回线段分割而不是整个线段。每个分割用起点和终点来表示,所以矩阵(或创建的序列)类型是
CV_32SC4.

CV_HOUGH_MULTI_SCALE
- 传统 Hough 变换的多尺度变种。线段的编码方式与
CV_HOUGH_STANDARD 的一致。

  • Rho:与像素相关单位的距离精度
  • Theta:弧度测量的角度精度
  • Threshold:阈值参数。如果相应的累计值大于
    threshold, 则函数返回这条线段.
  • param1:第一个方法相关的参数:

对传统
Hough 变换,不使用(0).

对概率
Hough 变换,它是最小线段长度.

对多尺度
Hough 变换,它是距离精度 rho 的分母
(大致的距离精度是 rho 而精确的应该是
rho / param1 ).

  • param2:第二个方法相关参数:

对传统
Hough 变换,不使用 (0).

对概率
Hough 变换,这个参数表示在同一条直线上进行碎线段连接的最大间隔值(gap),
即当同一条直线上的两条碎线段之间的间隔小于param2时,将其合二为一。

对多尺度
Hough 变换,它是角度精度 theta 的分母
(大致的角度精度是 theta 而精确的角度应该是
theta / param2).

4、 MyLine

 1 //-----------------------------------------------------------------------------
 2 class MyLine{
 3 public:
 4     int id;//编号
 5     int k;//倾斜角[0-360)
 6     int l;//长度
 7 public:
 8     MyLine(int ID=0,int K=0,int L=0){id=ID,k=K,l=L;}//构造函数
 9     bool operator<(const MyLine &A){return k<A.k;}//重定义小于号
10     void print(){printf("id: %3d  k: %3d°  l: %3d\n",id,k,l);}//输出函数
11 };//自定义直线
12 //-----------------------------------------------------------------------------

5、 平面几何相关函数

 1 //-----------------------------------------------------------------------------
 2 //平面几何相关函数http://www.cnblogs.com/zjutlitao/p/3243883.html
 3 //-----------------------------------------------------------------------------
 4 #define eps 0.0000000001
 5 #define PI acos(-1.0)
 6 int dcmp(double x){
 7     if(fabs(x)<eps)return 0;
 8     else return x<0 ? -1:1;
 9 }
10 double Dot(Point A,Point B){return A.x*B.x+A.y*B.y;}//向量点积
11 double Length(Point A){return sqrt(Dot(A,A));}//向量模长
12 double Cross(Point A,Point B){return A.x*B.y-A.y*B.x;}//向量叉积
13 double Angle(Point A,Point B){return acos(Dot(A,B)/Length(A)/Length(B));}//求向量的夹角
14 double DistanceToLine(Point P,Point A,Point B)//点到直线的距离
15 {
16     Point v1=B-A,v2=P-A;
17     return fabs(Cross(v1,v2))/Length(v1);//如果不加绝对值是带有方向的距离
18 }
19 double DistancetoSegment(Point P,Point A,Point B){//点到线段的距离
20     if(A==B)return Length(P-A);
21     Point v1=B-A,v2=P-A,v3=P-B;
22     if(dcmp(Dot(v1,v2))<0)return  Length(v2);
23     else if(dcmp(Dot(v1,v3))>0)return Length(v3);
24     else return fabs(Cross(v1,v2))/Length(v1);
25 }
26 //-----------------------------------------------------------------------------

四、运行结果

PS:由于篇幅有限,这里就不把全部的图片列出了~

五、实验中遇到的主要问题及解决方法:

1、在处理速度方面:

如果直接用原图做霍夫变换计算量巨大而且干扰特别多,这里我先用canny进行边缘提取预处理,然后再进行运算就解决了上述问题。但是用霍夫变换的图不能在上面绘制霍夫找到的直线或圆,结果就要转换为BGR彩图,才能进行可视化显示~具体的两个操作为:

  • Canny(src, temp, 10, 140, 3);//提取边缘(如果不边缘提取就会浪费巨大时间)
  • cvtColor(temp, dst, CV_GRAY2BGR);//将边缘提取的灰度图转换为BGR图便于画线

2、去除其他圆的影响:

由于用霍夫找圆会找到比较多的圆,如何在这些圆中找出和表盘最相近的一个呢?这里采用了比较巧妙的一个方法:

 1 //储存检测圆的容器
 2 std::vector<Vec3f> circles;
 3 //调用Hough变换检测圆
 4 //参数为:待检测图像,检测结果,检测方法(这个参数唯一),累加器的分辨率,两个圆间的距离,canny门限的上限(下限自动设为上限的一半),圆心所需要的最小的投票数,最大和最小半径
 5 HoughCircles(temp,circles,CV_HOUGH_GRADIENT,2,50,200,100,100,300);
 6 //找出圆盘(因为最大的不一定是的,所以加了几个限制条件)
 7 int pos=0;
 8 int max=-1;
 9 for(size_t i = 0; i < circles.size(); i++ )
10 {
11     Vec3f f=circles[i];
12     if(f[2]>max && f[0]+f[2]<temp.rows && f[0]-f[2]>=0 && f[1]+f[2]<temp.cols && f[1]-f[2]>0)
13     {
14         max=f[2];
15         pos=i;
16     }
17 }
18 Point center(circles[pos][0],circles[pos][1]);//找到的圆心
19 int   radius= circles[pos][2];//找到的半径
20 circle(dst,center,radius,Scalar(255),2); 

如上面所示:遍历所有霍夫找的圆,记录其中半径最大的且满足整个圆在图像内的那个,作为目标圆,这样就巧妙地找出了我们需要的那个圆~

3、霍夫找到的直线转换为夹角表示的线段:

因为接下来要根据夹角进行分组,所以这里要把霍夫找到的直线进行转换,这里我自己定义一个MyLine的类,用于保存一条线段,该线段形式为夹角和长度,其中转换关系为:

 1 list<MyLine> list_MyLine;
 2 vector<Vec4i> lines2;//线段检测
 3 HoughLinesP(temp, lines2, 1, CV_PI/180, 50, 50, 10 );
 4 for( size_t i = 0; i < lines2.size(); i++ )
 5 {
 6     Vec4i l = lines2[i];
 7     Point A(l[0], l[1]),B(l[2], l[3]);
 8     if(DistancetoSegment(center,A,B)<30)//根据圆心到指针的距离阈值滤掉其他线段
 9     {
10         bool down=(A.y+B.y-2*center.y>0);//判断长的在过圆心的水平线上部还是下部
11         if(A.x==B.x){//斜率为无穷的情况
12             list_MyLine.push_back(MyLine(i,90+(down?180:0),Length(Point(A.x-B.x,A.y-B.y))));
13         }else if(A.y==B.y){//水平的情况
14             list_MyLine.push_back(MyLine(i,A.x+B.x-2*center.x>0 ? 0:180,Length(Point(A.x-B.x,A.y-B.y))));
15         }else{
16             if(down){
17                 if(A.y>center.y)
18                     list_MyLine.push_back(MyLine(i,360-atan2(A.y-B.y,A.x-B.x)*180/PI,Length(Point(A.x-B.x,A.y-B.y))));
19                 else
20                     list_MyLine.push_back(MyLine(i,360-atan2(B.y-A.y,B.x-A.x)*180/PI,Length(Point(A.x-B.x,A.y-B.y))));
21             }else{
22                 if(A.y<center.y)
23                     list_MyLine.push_back(MyLine(i,abs(atan2(A.y-B.y,A.x-B.x)*180/PI),Length(Point(A.x-B.x,A.y-B.y))));
24                 else
25                     list_MyLine.push_back(MyLine(i,abs(atan2(B.y-A.y,B.x-A.x)*180/PI),Length(Point(A.x-B.x,A.y-B.y))));
26             }
27         }
28         line(dst,A,B, Scalar(0,0,i*20+40), 2, CV_AA);
29     }
30 }

六、实验中的缺陷和不足

  虽然用我这种识别方法可以快速有效的识别很多时钟,但是也存在一些特殊情况无法处理,比如:1、圆心不准导致计算出错;2、影子出现导致出现诡异指针;3、另类指针导致霍夫求得的直线不能准确描述指针;4、非圆形的表盘根本Hold不住;5、当存在其他干扰时表盘找不准;6、指针后半部分影响导致误判为另一个指针;7、指针长度检测错误导致时分秒针分配错误…等情况,虽然采用一些限制条件可以去除其中一二个错误,但是当算法向这方面偏的时候,又会导致另一些识别好的情况出现错误。最后总结一句:用图像识别不能求普遍性解决问题,我们应该根据具体的问题,在限定条件下进行研究,否则将永远满足不了需求!  

链接:

本文链接:http://www.cnblogs.com/zjutlitao/p/4187476.html

文档下载:http://pan.baidu.com/s/1i3koenr

工程下载:http://pan.baidu.com/s/1jGst6lC

无法识别:http://pan.baidu.com/s/1o6t7rnG

更多精彩:http://www.cnblogs.com/zjutlitao/p/4125085.html

GitHub链接: https://github.com/beautifulzzzz/OpenCV-Clock-Identification

时间: 2024-08-28 07:13:22

[图像识别] 1、如何识别一个指针式的时种的时间?的相关文章

做一个响应式网站需要多长时间

以传统的响应式建站模式来讲,做一个响应式网站需要的时间是很长的.抛开页面设计和模块的结合,光是网站的结构和网站布局就需要花费很长时间.还没计算建站前的准备工作,网站的策划等等.所以一般让企业对响应式网站望而止步的因素不单单是建站的费用,还有就是建站的时间过长,让网站的上线收到延迟,没有办法能够抢在黄金时间上线.那做一个响应式网站要多久呢?一般以企业展示型网站来统计,找建站公司来搭建大概需要3-6个月时间,如果是自己找开发团队时间大概要2-3个月时间.所以企业对于响应式网站既是爱,又是狠. 有什么

南邮JAVA程序设计实验4 线程程序设计(指针式时钟)

南邮JAVA程序设计实验4  线程程序设计(指针式时钟) 实验目的: 本实验旨在通过实验,培养学生将JAVA 线程的相关知识点(包括线程调度,线程同步等)有机结合并加以综合应用,在实验中设计多线程程序的能力. 实验内容: 设计和编写一个编写一个指针式时钟程序,应用线程实现时钟的走动. 实验设计: 主要是控制时针分针秒针的转动度数,这个直接通过坐标的三角函数值求得,线程方面,隔一秒休眠一下,然后通过时分秒的换算关系来改变三个对应指示针在时钟上的位置 实验代码: import java.awt.*;

指针式压力表-自动读数(Auto Read the Value of Manometer)

指针式压力表的自动读数,用摄像头对准压力计,然后实时自动地读取压力计的读数.视频效果如下视频所示,红色数字为识别到的指针读数.

第二章 数组名是一个指针常量吗?

数组名是一个指针常量这种观点来源于数组名在表达式计算中与指针的结果等效性.例如下面的代码: int a[10], *p = a, *q; q = a + 1; q = p + 1; 在效果上看,a + 1与 p + 1是相同的,这很容易给人一种a就是p的假象,但,这仅仅是假象.鉴于指针常量包含了指针和常量两类概念,我们可以把这个问题分开两部分进行讨论. 一.数组名是指针吗? 在<C与指针>一书中,作者用一个著名的例子阐述了数组名与指针的不同.在一个文件中定义:int a[10];然后在另一个文

C++反汇编第二讲,反汇编中识别虚表指针,以及指向的虚函数地址

讲解之前,了解下什么是虚函数,什么是虚表指针,了解下语法,(也算复习了) 开发知识为了不码字了,找了一篇介绍比较好的,这里我扣过来了,当然也可以看原博客链接: http://blog.csdn.net/hackbuteer1/article/details/7558868 一丶虚函数讲解(复习开发,熟悉内存模型) 1.复习开发知识 首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数

访问一个绝对地址把一个整型数强制转换 (typecast)为一个指针是合法的

在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66.编译器是一个纯粹的ANSI编译器.写代码去完成这一任务. 解析:这一问题测试你是否知道为了访问一个绝对地址把一个整型数强制转换(typecast)为一个指针是合法的.这一问题的实现方式随着个人风格不同而不同.典型的代码如下: 一个较晦涩的方法是: 建议你在面试时使用第一种方案.答案:

JSON的put方法是一个指针引用

JSON的put方法是一个指针引用; import org.json.simple.JSONObject; JSONObject a=new JSONObject(); a.put("date","2015-11-08");a.put("time","15:48:28"); a.toJSONString() --> {"date":"2015-11-08","time&q

以带头节点的循环链表表示队列,并且只设置一个指针指向队尾元素,实现这样的功能的想法。

用循环链来表示队列,并且只有一个指针.我的想法就是在每个节点添加一个布尔型数据,可以用布尔型数据的true和false来判断此节点是否有数据. 这样生成队列的时候和书上类似. 插入数据的时候,先保存原指针指向的点,然后将此指针向下寻找,直到找到一个节点的布尔是false,而下一个是true时,表示此节点是在队尾,将数据插入,并将次节点的布尔值修改为true.如果找不到这样的节点,说明“上溢”或者是个空的链表.抛出异常. 删除数据的时候,和插入类似,先备份原指针,然后用原指针去不断向下寻找,直到找

简述人脸特异性识别&amp;&amp;一个基于LBP和SVM的人脸识别小例子

原谅我用图片,MAC在Safari里给文章进行图文排版太麻烦啦~ 本文适合初入计算机视觉和模式识别方向的同学们观看~ 文章写得匆忙,加上博主所知甚少,有不妥和勘误请指出并多多包涵. 本文Demo的代码由HZK编写,特征点由月神和YK选择和训练. 转载请注明 copyleft by sciencefans, 2014 为了方便大家学习,附上高维LBP的核心代码 1 ################################################### 2 # 3 # 4 # NO