智能筆算法總結


智能筆算法總結

         一周前,我在CocoaChina和博客園的問答區都提了一個問題,就是本篇文章將要描述的“智能筆問題”。遺憾的是,至今沒有朋友給予有效地回復,但是,還是感謝回復我的朋友們。

經過一周的琢磨和研究,終於在昨天搞定了這個問題。

 

看着上圖,回想自己渴求幫助的心情,想必有的朋友還是需要這樣的算法的,再者在此也做一個總結,所以記錄一下,互相學習指正。

 

首先描述下這個功能的需求:在平板上,用手指自由地一筆畫出一個圖形,然后智能識別用戶所畫圖形的類型,比如直線、圓、三角形或者矩形,並自動變換為最接近的幾何圖形。

 

從上面的描述來看,就是自動識別圖形的算法。並且已經獲得了所勾勒自由圖形的點軌跡。在此需要說明一下,我做的項目中,已經有獲取點的方法,但並不是將所有像素點記錄,而是一系列的軌跡離散點。

 

所以,開始下面的算法前提就是,已經有一個記錄軌跡的點數組了。

 

我不想寫成學術報告一樣,先羅列一堆概念什么的,所以就直接按照算法的步驟了,涉及到概念再描述一下。

 

總體步驟:

1、  求頂點

2、  判斷直線

3、  判斷圓

4、  判斷矩形

5、  判斷其他多邊形

6、  未知自由圖形

 

整體很直觀吧,那開始逐步介紹。

一、求頂點

還記得向量么,還記得怎么求兩向量的夾角么,還記得正弦余弦么。好吧,不記得的少年,需要溫故一下了。

頂點即是圖形中的“轉折點”,是兩條邊的交點,所以存在一定夾角,那么就可以用這兩條邊的方向向量求出夾角theta的值。方向向量可以根據一條直線上的兩點求得,夾角theta根據公式可以得到。

 

可以參考偽代碼:

 1 點數組為points;
 2 
 3 用於存儲頂點的可變數組 vertexes;
 4 
 5 前兩個點數組 aheadTwoPoints;
 6 
 7 aheadTwoPoints[0] = points[0];
 8 
 9 aheadTwoPoints[1] = points[1];
10 
11 上一個參考向量 aheadDirectionVector =
12 
13 Point2d(aheadTwoPoints[1].x – aheadTwoPoints[0].x , aheadTwoPoints[1].y – aheadTwoPoints[0].y);
14 
15  
16 
17 For(int i=2; i<points.Length; i++){
18 
19 當前向量 currentDirectionVector = Point(points[i].x – aheadTwoPoints[1].x, points[i].y -  aheadTwoPoints[1].y);
20 
21 臨時向量 tempDirectionVector = currentDirectionVector;
22 
23                   
24 
25 Double cosTheta = (currentDirectionVector.x * aheadDirectionVector.x  +  currentDirectionVector.y * aheadDirectionVector.y) / (sqrt(pow(currentDirectionVector.x, 2) + pow(currentDirectionVector.y , 2)) * sqrt(pow(aheadDirectionVector.x , 2) + pow(aheadDirectionVector.y , 2)));
26 
27  
28 
29 if(fabs(cosTheta) <= 閥值1){
30 
31          將aheadTwoPoints[1]加入vertexes;
32 
33 }else{
34 
35          根據points[i]和aheadTwoPoints[0]得到cosTheta2;
36 
37          If(fabs(cosTheta2 <= 閥值2)){
38 
39          將aheadTwoPoints[1]加入vertexes;
40 
41 }
42 
43 }
44 
45  
46 
47 aheadTwoPoints[0] = aheadTwoPoints[1];
48 
49 aheadTwoPoints[1] = points[i];
50 
51 aheadDirectionVector = tempDirectionVector;
52 
53 }

 

 

說明:閥值1和閥值2有待進一步探究,目前我采用的閥值1為cos(M_PI / 3),閥值2為cos(M_PI / 6).歡迎大家提出優化建議。

 

二、判斷直線

當vertexes.Length為0的時候,初步判斷為直線或者圓。

偽代碼:

 1 If(points[0].distanceTo(points[length - 1]) > points[0].distanceTo(points[length / 2 - 1])){
 2 
 3          中點midPoint = Poin(t(points[0].x + points[length - 1].x) / 2 , (points[0].y + points[length - 1].y) / 2);
 4 
 5          If(midpoint.distanceTo(points[length / 2 - 1]) < 閥值3){
 6 
 7         實例化一條直線 newLine;
 8 
 9            newLine.StartPoint = points[0];
10 
11            newLine.EndPoint = points[length - 1];
12 
13            畫出直線;
14 
15      }else{
16 
17              實例化一條曲線 newSpline;
18 
19              newSpline的數據點為points;
20 
21              畫出曲線;
22 
23          }
24 
25 }

 

三、判斷圓

當二中直線判斷條件不滿足時,初步判斷為圓。

接下來主要根據圓心特點來判斷。

偽代碼:

先根據points取到1/4、1/2、3/4這三個點,再加上起點,相對的兩點求中點,然后近似得到圓心點 centerPoint;

 

If(points[0].distanceTo(centerPoint) < points[0].distanceTo(points[length - 1])){

         判斷為曲線,並畫出曲線;

         break;

}

 

初始化圓 circle;

circle.center = centerPoint;

circle.radius = points[0].distanceTo(centerPoint);

 

說明:這里只描述了主要的思想,更多細節就根據具體情況而定了。還有一點,在初始化圓之前,還可以再加一個判斷:

先求得points[0]和points[length/4 - 1]的直線為y1,points[length/8 - 1]和圓心的直線為y2,得到y1和y2的交點P,然后求得P和圓心的距離dis1、points[length/8 - 1]和圓心的距離dis2,判斷dis1和dis2的大小關系。

 

在圓里面,dis1/dis2 = 1 – sqrt(2) / 2。

 

所以如果points[length/8 - 1]和points[length*3/8 - 1]同時滿足,那判斷就更准了。但是,由於上述代碼中基本確定為圓了,所以為了提高容錯,我就沒有再加說明中的判斷了。感興趣的朋友可以一試。

 

四、判斷矩形

當vertexes.Length為3的時候,直接判斷為矩形。

 

最大的問題就是,重新計算矩形的四個頂點位置。

1、我采用以起點和vertexes[1]為參照點、以過起點和vertexes[0]的直線為參照直線。

2、從vertexes[1]向參照直線做垂直直線,求得交點為新的vertexes[0]

3、然后求出vertexes[0]到vertexes[1]的向量vector

4、根據points[0]加上向量vector,得到新的vertexes[2]。

 

實例化一個多段線 polyline;

polyline.setPointAt(0, points[0]);

For(int i=0; i<vertexes.length ; i++){

         polyline.setPointAt(i + 1, vertexes[i]);

}

polyline.setPointAt(vertexes.length + 1, points[0]);

 

說明:當然,在開始重新計算四個頂點位置前,還可以加一個判斷,對角線的距離差值是否在一定閥值范圍內,或許更好一點。

但是,我非常希望畫出矩形,所以就強制實現四個頂點的圖形為矩形了。

 

這里的算法,還有值得推敲的,因為實際測試中,矩形是最難畫出的=_=

 

五、判斷其他多邊形

多邊行就太輕松了。。。。

只要排除上述幾個前提條件,就可以判斷圖形為(vertexes.length + 1)邊行。

然后畫出多段線就可以了。

 

六、未知自由圖形

這個。。。。畢竟我的算法有限,還是有很多情況不能識別,主要難點在頂點的識別。所以不滿足判斷條件的圖形就只能將其“原封不動”地畫出來了。

 

心得:

一開始覺得無從下手,發了一些帖子,但是都沒有得到回答,心里比較煩。但是當自己全心投入,去研究去思考以后,還是會有回報,會有意外的收獲。

這讓我聯想起一路學習編程的情況。起初對編程很恐懼,覺得密密麻麻的代碼,看得心煩,但是當靜下心來去讀代碼,去思考代碼中的邏輯時,就會不斷發現驚喜,贊嘆牛人的思維邏輯,贊嘆各種算法,贊嘆各種設計模式,贊嘆強大的API。就是這樣的一種心情,引領着我這個菜鳥一步一步走進編程的世界,也開始了自己的成長之路。

 

不知不覺,走出學校參加工作已經有五個月了。這段時間,心理變化也是很多很復雜的。過去的不必糾結,大家都知道着眼未來,可我還是很樂意為今天現在的自己贊一個的。自己認可自我的提升和改變,這也是一件意義重大的事呢。

 

這個算法就描述到這里了,希望能夠給需要的朋友一個提示,也希望得到指正和討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM