[圖像識別] 一、如何識別一個指針式的時種的時間?

 

目錄html

1、算法基本原理git

一、圖片預處理github

二、找錶盤算法

三、找指針函數

四、指針映射編碼

五、求時間spa

2、算法流程圖3d

3、程序關鍵函數說明指針

1Cannycode

2HoughCircles

3HoughLines2

4MyLine

5平面幾何相關函數

4、運行結果

5、實驗中遇到的主要問題及解決方法:

1、在處理速度方面

2、去除其餘圓的影響

3、霍夫找到的直線轉換爲夾角表示的線段

6、實驗中的缺陷和不足


 

1、算法基本原理

時鐘識別,顧名思義:就是根據一張帶有鐘錶的圖片識別出鐘錶上所展現的時間。對於這個問題我把它分爲四步處理:

  • 一、 圖片預處理

  因爲一張圖片中包含的圓圈和直線的信息量較大,直接進行霍夫處理要浪費大量的運算量,並且也不利於去除各類干擾。因而這裏所謂的預處理就是利用canny算法先對原圖進行邊緣提取,一方面濾掉部分干擾,另外一方面將原圖轉換爲邊緣圖後只剩下主要線條信息,有利於加快處理。實驗證實,用此方法有用信息丟失較少,能夠採用!

  •  二、 找錶盤

  找錶盤主要運用霍夫找圓,可是因爲一張圖裏會找出不少圓,所以關鍵仍是濾掉其餘有影響的圓!這裏採用的方法是找出全部用霍夫找到的圓中半徑最大且整個圓都在當前圖片中的那個圓做爲錶盤。實踐證實,用這種方法可以較爲準確的去除其餘圓的影響並準確找到錶盤所對應的圓(對了,這裏的鐘默認爲圓形的錶盤的指針式鍾)。

  • 三、 找指針

  找指針主要運用霍夫找線段,可是可想而知一張圖裏除了指針外必定還存在其餘線段,而這些線段會對指針識別形成影響。所以我採用判斷線段到圓心的距離小於必定的距離才默認爲指針。而後對獲取求得的線段進行轉換爲我定義的用斜率表示的線段的形式。

  • 四、 指針映射

  因爲上面求得的線段並非嚴格上的指針,而是不少線段,可是若是仔細觀察會發現雖然並非嚴格的對應關係,可是他們會出現成簇的分組效果!即:多條線段會彙集在某一個指針左右,而這些線段的斜率和預求得指針的斜率相差不大,我正是利用它的這個特色首先根據指針與X軸正方向的夾角從小到大進行排序,而後每次遇到相鄰兩個線段的夾角存在較大的跳變就進行切割,最後全部的線段被分割爲一些組,再利用一個特殊的公式從新計算這組所表明指針的長度:

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

  •  五、 求時間

  經過上面四步的計算咱們已經可以將線段分爲三組或者兩組,這樣再根據長短來和時分秒三個或者時分指針進行對應,再根據夾角就能求出對應的時間。

 

2、算法流程圖

 

 

3、程序關鍵函數說明

一、 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 當中的小閾值用來控制邊緣鏈接,大的閾值用來控制強邊緣的初始分割。

 

二、 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:最大圓半徑。

 

三、 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).

 

四、 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 //-----------------------------------------------------------------------------

 

五、 平面幾何相關函數

 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 //-----------------------------------------------------------------------------

 

4、運行結果

PS:因爲篇幅有限,這裏就不把所有的圖片列出了~

 

5、實驗中遇到的主要問題及解決方法:

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 }

 

6、實驗中的缺陷和不足

  雖然用我這種識別方法能夠快速有效的識別不少時鐘,可是也存在一些特殊狀況沒法處理,好比:一、圓心不許致使計算出錯;二、影子出現致使出現詭異指針;三、另類指針致使霍夫求得的直線不能準確描述指針;四、非圓形的錶盤根本Hold不住;五、當存在其餘干擾時錶盤找不許;六、指針後半部分影響致使誤判爲另外一個指針;七、指針長度檢測錯誤致使時分秒針分配錯誤…等狀況,雖然採用一些限制條件能夠去除其中一二個錯誤,可是當算法向這方面偏的時候,又會致使另外一些識別好的狀況出現錯誤。最後總結一句:用圖像識別不能求廣泛性解決問題,咱們應該根據具體的問題,在限定條件下進行研究,不然將永遠知足不了需求!  

 

 

連接:

本文連接: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

相關文章
相關標籤/搜索