淺談如何使用代碼實現手機敲擊識別

做者:鄭童宇ios

GitHub:https://github.com/CrazyZtygit

1.前言

  如今市面上有很多Android手機支持敲擊屏幕解鎖,敲擊屏幕解鎖是一項很實用的功能,但一來只支持敲擊屏幕,二來只能用於解鎖或鎖屏,再者咱們應用層的開發者切不進去,徹底沒法玩起來。開發者,開發者,咱們既然身爲開發者何不搞點大新聞,那麼此次我來教教各位如何用代碼來實現手機的敲擊識別,聽起來是否是頗有趣,有些躍躍欲試呢。事實上在ios上已經有實現這個功能的應用:Knock,一款敲擊來解鎖Mac電腦的應用,售價4.99美圓,約爲33人民幣。有時候真想去作ios開發,能夠開心的爲本身的應用訂價,愉悅的掙外快。言歸正傳,既然ios能夠實現,那咱們Android天然不能落伍,如今我就帶領你們來用代碼實現手機的敲擊識別吧。github

  本篇博文以Java爲示例語言,以Android爲示例平臺。算法

2.功能實現

2.1.實現思路

  說到敲擊識別,大家會考慮使用什麼來實現呢,傳感器?對,沒錯,做爲手機手勢姿態識別的惟一途徑,咱們天然須要使用傳感器來實現對敲擊的識別,但Android傳感器種類繁多,咱們應該選擇哪個呢?app

  在Android2.3的時代,Android系統就已經定義了11個傳感器,到了如今Android6.0的時代,系統定義的傳感器數目已經達到26個,這麼多傳感器咱們到底用哪個呢,事實上咱們只須要考慮2.3時代提供的那11個傳感器便可,由於一方面後期加入的傳感器部分如心跳傳感器等須要硬件支持,致使不少手機沒法支持此類傳感器,另外一方面2.3時代的11個傳感器功能已經至關強大,能夠支持絕大多數手勢姿態的識別,那麼如今我來列舉一下上述11個傳感器:iphone

  SENSOR_TYPE_ACCELEROMETER                 加速度
  SENSOR_TYPE_MAGNETIC_FIELD                 磁力
  SENSOR_TYPE_ORIENTATION                      方向
  SENSOR_TYPE_GYROSCOPE                         陀螺儀
  SENSOR_TYPE_LIGHT                                   光線感應
  SENSOR_TYPE_PRESSURE              壓力
  SENSOR_TYPE_TEMPERATURE            溫度 
  SENSOR_TYPE_PROXIMITY             接近
  SENSOR_TYPE_GRAVITY                               重力
  SENSOR_TYPE_LINEAR_ACCELERATION       線性加速度
  SENSOR_TYPE_ROTATION_VECTOR             旋轉矢量機器學習

  關於這11個傳感器的詳細描述,各位能夠去http://www.oschina.net/question/163910_28354查看,事實上我一直懷疑LG G3的敲擊解鎖與光線傳感器或接近傳感器有關,由於我用手指懸浮在LG G3的頭部正上方時一直沒法敲擊解鎖,移開後恢復正常,而敲擊鎖屏應該只和觸摸屏相關,由於不管我怎麼遮擋傳感器,敲擊鎖屏的功能徹底不受影響。工具

  言歸正傳,對這11個傳感器有所瞭解後,咱們須要選擇哪一個或哪些傳感器來實現功能呢,咱們來模擬一下手機敲擊的狀況,將手機平放在桌面上,手指敲擊手機的時候,手指給了手機一個力,同時桌面給予手機一個副作用力,考慮桌面不形變的狀況下,手機受力平衡加速度爲0,但這時手機的加速度傳感器數據是否會有變化呢,答案是會的,手機加速度傳感器的數據會有一段短暫但明顯的變化,爲何呢,手機受力平衡加速度爲0是由於它是一個總體,但內部構件仍是會受到相互之間複雜的力的左右,並不是受力的同時就達到受力平衡的,其實換個思路。用一個和手機形狀類似內部光滑的容器,容器裏面放幾個玻璃球,敲擊幾下,容器不會移動,但玻璃球是否是移動了呢。雖然手機內部的構件遠比玻璃球穩定,但也得遵循基本法,老老實實接受力的做用。性能

  上述場景是平放於桌面的場景,實際生活的場景每每更加複雜多樣,但不管處於哪一種場景,毫無疑問對手機的敲擊操做都應該致使加速度傳感器傳出數據的明顯變化,那麼咱們如今就明白了應該選擇什麼傳感器做爲咱們敲擊識別的工具了吧,但加速度相關的傳感器有兩個,加速度傳感器和線性加速度傳感器,咱們應該選擇哪個呢,加速度傳感器提供的數據是重力影響下的手機加速度,線性加速傳感器提供的數據是排除重力影響的手機加速度,能夠直觀的反映排除重力後手機的受力狀況,很合適用以敲擊識別,那咱們是否就應該選擇線性加速度傳感器呢,偏偏相反,咱們要選擇加速度傳感器,Android提供的線性加速度傳感器基於軟件的,不一樣平臺對於線性加速傳感器的處理未必相同,事實上,在敲擊三星S4,LG G3中一款機型的背面,就出現線性加速度傳感器傳出的數據沒有較大變化的狀況,保險起見,咱們仍是選用基於硬件的加速度傳感器更合適一些。順便吐槽一句,當時看到壓力傳感器的時候,我還覺得監測做用於手機的壓力的傳感器,那無疑是很適合用於識別敲擊,後面看到描述才知道是監測壓強的。學習

  如上所說,對手機的敲擊操做會致使加速度傳感器傳出數據的明顯變化,故而本次功能實現中,判斷是否有敲擊操做的方法是檢測手機線性加速度相比正常狀況是否有明顯變化。在功能實現過程當中爲排除重力的影響,須要對加速度傳感器的數據進行處理將其轉化爲線性加速度,由於轉化爲線性加速度是一個須要校準的過程,因此須要先投入必定數目的數據用於校準以得到更精確的線性加速度,同時考慮到現實生活存在可能致使誤識別的場景,好比搖動手機會帶給手機一個較長時間且明顯的線性加速度變化,因此提出穩態的概念,將手機處於相對穩定,沒有長時間出現明顯線性加速變化的情況視爲穩態,在穩態的狀況下才會進行對敲擊的識別,另外這次敲擊識別考慮到對手機邊框的敲擊使用可能性太低,所以僅考慮識別對手機屏幕或背面的敲擊,這樣在識別的過程當中可忽略X,Y軸的數據,僅考慮Z軸的線性加速度。

2.2.功能簡介

  本次實現的功能是識別對手機屏幕或背面的敲擊操做,功能實現流程: 註冊傳感器,採集數據,投入指定數目的數據校準以獲取較精準的線性加速度,校準結束後判斷當前是否穩態,若是爲非穩態,則等待下次數據,若是爲穩態,則調用方法判斷是否存在敲擊操做,在進行敲擊識別的同時也將處理獲得的線性加速度和最近敲擊次數,穩態狀態顯示到界面上去,

2.3.功能實現

2.3.1.獲取傳感器數據

  註冊傳感器的方法屬於系統原生的方法,就不過多講解,不過須要注意一點,在註冊加速度傳感器時標識傳感器數據採樣間隔的參數最好使用SENSOR_DELAY_GAME,由於敲擊致使的加速度數據變化很短暫,若是使用SENSOR_DELAY_UI或SENSOR_DELAY_NORMAL每每採集不到敲擊引起的加速度變化,固然若是使用SENSOR_DELAY_FASTEST天然不會有這個問題,但性能消耗會比較大。

  註冊傳感器後就能夠在回調方法裏等待處理數據, 下面我給出實現代碼,綜合代碼講解實現過程。

 1     public void onSensorChanged(SensorEvent sensorEvent) {
 2         if (sensorEvent.sensor == null) {
 3             return;
 4         }
 5 
 6         if (sensorEvent.sensor.getType() == accelerometerSensorType) {
 7             float accelerationZ = sensorEvent.values[2];
 8 
 9             if (accelerationZ > 0) {
10                 recognitionKnockRatio = 20;
11                 recognitionUniqueRatio = 10;
12 
13                 smoothSectionMaxRatio = 5f;
14             } else {
15                 recognitionKnockRatio = 7.5f;
16                 recognitionUniqueRatio = 6;
17 
18                 smoothSectionMaxRatio = 2.5f;
19             }
20 
21             gravityZ = alpha * gravityZ + (1 - alpha) * accelerationZ;
22 
23             linearAccelerationZ = accelerationZ - gravityZ;
24 
25             if (calibrateLinearAcceleration) {
26                 calibrateLinearAccelerationIndex++;
27 
28                 if (calibrateLinearAccelerationIndex <= calibrateLinearAccelerationSectionNumber) {
29                     return;
30                 }
31 
32                 calibrateLinearAcceleration = false;
33             }
34 
35             if (sensorDataShowIndex >= sensorDataShowNumber) {
36                 sensorDataShowIndex = sensorDataShowNumber - sensorDataShowDurationNumber;
37 
38                 Iterator<?> it = linearAccelerationZShowList.listIterator(0);
39                 for (int i = 0; i < sensorDataShowDurationNumber; i++) {
40                     it.next();
41                     it.remove();
42                 }
43 
44                 MainActivity.UpdateSensorData(linearAccelerationZShowList);
45             }
46 
47             linearAccelerationZShowList.add(linearAccelerationZ);
48 
49             sensorDataShowIndex++;
50 
51             if (!stable) {
52                 linearAccelerationZList.add(linearAccelerationZ);
53 
54                 if (linearAccelerationZList.size() >= stableSectionNumber) {
55                     stableRecognition();
56 
57                     linearAccelerationZList.clear();
58                 }
59 
60                 return;
61             }
62 
63             knockRecognition(linearAccelerationZ);
64         }
65     }

  傳感器數據回調的方法中對加速度傳感器獲取的數據分別進行了處理,首先,根據z軸加速度的正負,爲recognitionKnockRatio,recognitionUniqueRatio,smoothSectionMaxRatio三個變量賦予不一樣的數值,至於爲何要進行這樣處理,是由於對Android手機實際進行敲擊操做發現,加速度傳感器對正面敲擊操做反饋敏感,對背面敲擊操做反饋相對遲鈍,反饋到數據層面就是,敲擊正面致使的加速度傳感器數據變化相比敲擊背面明顯不少,故而針對敲擊屏幕和敲擊背面要分配不一樣的數值,然而事實上站在手機的角度,運用如今的數據是徹底沒法分析敲擊操做致使的加速度明顯變化來源於敲擊正面仍是敲擊背面,因此就使用z軸加速度的正負來簡單判斷,畢竟絕大多數狀況下z軸加速度爲正,那就是手機背面偏向地面,用戶更可能敲擊手機屏幕,而爲負就是手機屏幕偏向地面,用戶更可能敲擊手機背面。至於致使敲擊屏幕和敲擊背面加速度傳感器反饋敏感程度不一樣這種狀況的緣由不外乎兩個,一是加速度傳感器相比於背面距離屏幕更近,再者就是Android手機外殼的問題了,這一點在LG G3上尤其明顯,LG G3的是有必定弧度的塑料外殼,在背面敲擊引起的傳感器數據變化相比於敲擊屏幕要低不少,而金屬外殼的三星S6,在背面敲擊引起的傳感器數據變化接近於敲擊屏幕。事實上上述三個係數屬於經驗係數,而且對於不一樣類型手機儘可能提供不一樣的數值,緣由可參見剛纔所說的LG G3和三星S6,再一次感慨Android手機的多樣性,Android手機種類太多,硬件設計的不一樣致使在一款手機上適用的係數在另外一款手機上可能徹底沒法適用,要是如iphone同樣只有那幾款機型的話無疑好處理不少。

  接着對加速度進行濾波處理以獲取線性加速度,獲取線性加速度的方法參考了Android SensorEvent源碼中建議的方法:

 1     * <p>
 2     * It should be apparent that in order to measure the real acceleration of
 3     * the device, the contribution of the force of gravity must be eliminated.
 4     * This can be achieved by applying a <i>high-pass</i> filter. Conversely, a
 5     * <i>low-pass</i> filter can be used to isolate the force of gravity.
 6     * </p>
 7     *
 8     * <pre class="prettyprint">
 9     *
10     *     public void onSensorChanged(SensorEvent event)
11     *     {
12     *          // alpha is calculated as t / (t + dT)
13     *          // with t, the low-pass filter's time-constant
14     *          // and dT, the event delivery rate
15     *
16     *          final float alpha = 0.8;
17     *
18     *          gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
19     *          gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
20     *          gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];
21     *
22     *          linear_acceleration[0] = event.values[0] - gravity[0];
23     *          linear_acceleration[1] = event.values[1] - gravity[1];
24     *          linear_acceleration[2] = event.values[2] - gravity[2];
25     *     }
26     * </pre>

  經過高通濾波和低通濾波對加速度進行處理排除重力影響以獲取線性加速度,但在此過程當中是須要傳入必定數量的數據進行校準以獲取較精準的線性加速度,在這裏咱們設定calibrateLinearAccelerationSectionNumber做爲用以校準數據的數據長度,用calibrateLinearAccelerationIndex和calibrateLinearAcceleration來控制什麼時候校準結束。

  校準結束後使用linearAccelerationZShowList存儲顯示到應用界面上的傳感器線性加速度,接着若是處於非穩態,則開始穩態識別,判斷當前狀態是否穩態,若是處於穩態狀態則開始敲擊識別。

2.3.2.穩態識別 

  如上文提到的,用戶若是進行搖動手機之類的操做,是會產生明顯的加速度變化,頗有可能致使誤識別的狀況,因此在此提出了穩態的概念,即爲手機加速度沒有長時間明顯變化的狀態,延伸到現實場景就是用戶沒有對手機進行明顯移動的狀態,嚴格來講,通常用戶在對手機進行明顯移動如搖動手機的同時進行敲擊操做的可能性極低,因此能夠將穩態這個概念正式運用到功能實現中。

  已經瞭解穩態這個概念,那咱們應該如何定義什麼狀況屬於穩態,什麼狀況屬於非穩態,下面我給出實現代碼,綜合代碼進行講解。 

 1     private void stableRecognition() {
 2         int exceptionNumber = 0;
 3 
 4         float accelerationZValue;
 5         float minAccelerationZValue = Integer.MAX_VALUE;
 6         float maxAccelerationZValue = Integer.MIN_VALUE;
 7 
 8         for (int i = stableSectionNumber - 1; i >= 0; i--) {
 9             accelerationZValue = linearAccelerationZList.get(i);
10 
11             if (Math.abs(accelerationZValue) > maxStableOffset) {
12                 exceptionNumber++;
13             } else {
14                 if (accelerationZValue > maxAccelerationZValue) {
15                     maxAccelerationZValue = accelerationZValue;
16                 } else {
17                     if (accelerationZValue < minAccelerationZValue) {
18                         minAccelerationZValue = accelerationZValue;
19                     }
20                 }
21             }
22         }
23 
24         stable = exceptionNumber <= maxExceptionNumber;
25 
26         if (stable) {
27             if (linearAccelerationZStableSection == 0) {
28                 linearAccelerationZStableSection =
29                         (maxAccelerationZValue - minAccelerationZValue) / 2;
30             }
31 
32             if (linearAccelerationZStableSection > maxStableOffset) {
33                 linearAccelerationZStableSection = maxStableOffset;
34             }
35         }
36 
37         MainActivity.UpdateStable(stable);
38 
39         LogFunction.log("stable", "" + stable);
40         LogFunction.log("exceptionNumber", "" + exceptionNumber);
41         LogFunction.log("linearAccelerationZStableSection", "" + linearAccelerationZStableSection);
42     }

  在這次功能實現過程當中,判斷穩態的方式是採樣50個點,而後計算每一個點的絕對值,若是大於最大誤差maxStableOffset就視爲異常點,異常點大於最大異常點數目maxExceptionNumber就視爲非穩態,反之視爲穩態。判斷穩態結束後,若是處於穩態則將剔除異常點數據後的Z軸最大加速度和最小加速度之間差值的一半視爲波動區間linearAccelerationZStableSection。maxStableOffset與maxExceptionNumber相同都是經驗係數,是對Android手機實際提供的不一樣場景下的線性加速度分析得出的。如今存在一個問題,那就是若是本來狀態處於穩態,而後用戶忽然對手機進行操做,將手機狀態轉變爲非穩態那要如何處理,不要着急,這個問題會在敲擊識別的過程當中進行處理的。

2.3.3.敲擊識別

  如今到了整個功能實現最核心的地方:敲擊識別,如上文所說敲擊會引發加速度傳感器數據的明顯變化,可是咱們要如何使用代碼進行檢測敲擊,以及如何排除用戶對手機其餘操做引起的誤識別問題,事實上這些問題都會在這裏進行處理,如今我給出實現代碼,綜合代碼進行講解。

 1     private void knockRecognition(float linearAccelerationZ) {
 2         float linearAccelerationZAbsolute = Math.abs(linearAccelerationZ);
 3 
 4         float linearAccelerationZAbsoluteRadio =
 5                 linearAccelerationZAbsolute / linearAccelerationZStableSection;
 6 
 7         if (linearAccelerationZAbsoluteRadio > recognitionUniqueRatio) {
 8             uniqueLinearAccelerationZList.add(linearAccelerationZ);
 9 
10             currentForecastNumber = forecastNumber;
11         } else {
12             if (uniqueLinearAccelerationZList.size() > 0) {
13                 if (currentForecastNumber > 0) {
14                     currentForecastNumber--;
15                 } else {
16                     handleUniqueLinearAccelerationZ();
17                 }
18             }
19         }
20 
21         if (linearAccelerationZAbsoluteRadio < smoothSectionMaxRatio) {
22             float offsetWeight = 0.001f;
23 
24             linearAccelerationZStableSection =
25                     weightedMean(offsetWeight, linearAccelerationZAbsolute,
26                             linearAccelerationZStableSection);
27         }
28     }

  knockRecognition就是用來處理線性加速度進而確認是否有敲擊操做的方法,首先對傳入參數線性加速度進行處理,獲取線性加速度絕對值,接着若是線性加速度絕對值與波動區間的比值大於recognitionUniqueRatio,那就認爲手機正在受到力的做用,爲肯定是敲擊操做仍是用戶其餘操做,先將線性加速度加入到獨特線性加速度列表中, 反之若是小於等於recognitionUniqueRatio,那就認爲手機處於相對穩定狀態,在此時若是此時獨特線性加速度列表長度大於0,若是currentForecastNumber大於0,則currentForecastNumber減1,若是currentForecastNumber小於等於0,則開始處理獨特線性加速度列表,而在處理獨特線性加速度列表的過程當中正式開始識別是否敲擊,以及當前狀態是否轉變爲非穩態。在進行上述操做的同時,若是線性加速度絕對值與波動區間的比值小於smoothSectionMaxRatio則用線性加速度絕對值來平滑波動區間。

  在這裏,你們確定對currentForecastNumber有疑問,這個變量表明什麼含義,爲何會有這個變量,緣由是這樣的,一次敲擊可能致使兩個接近但不連續的獨特線性加速度。若是沒有currentForecastNumber這個變量就會致使現實的一次敲擊可能被識別爲兩次敲擊操做。

  而若是線性加速度絕對值與波動區間的比值小於smoothOffsetMaxRatio則用線性加速度絕對值來平滑波動區間,是由於一方面手機的狀態可能隨時改變,波動區間應該隨着手機狀態的改變跟着改變,另外一方面穩態識別時計算的波動區間可能存在問題,並不能正確的反映當前手機的加速度波動,這個時候就須要根據最新的數據進行學習以平滑波動區間,而爲何比值要小於smoothSectionMaxRatio是由於比值大於smoothSectionMaxRatio的基本是非正常狀況的線性加速度,不適合用於平滑波動區間,而若是現實狀況中的線性加速度與波動區間比值基本都超過smoothSectionMaxRatio,那說明如今手機多半處於非穩態狀態了,等待新的穩態識別重置波動區間便可,另外如上文所說,recognitionUniqueRatio,smoothOffsetMaxRatio屬於經驗係數,徹底能夠自主設定。

 1     private void handleUniqueLinearAccelerationZ() {
 2         LogFunction.log("linearAccelerationZStableSection", "" + linearAccelerationZStableSection);
 3 
 4         int recognitionKnockNumber = 1;
 5 
 6         int uniqueLinearAccelerationZListLength = uniqueLinearAccelerationZList.size();
 7 
 8         float accelerationZOffsetAbsolute;
 9         float maxAccelerationZOffsetAbsolute = 0;
10 
11         for (int i = 0; i < uniqueLinearAccelerationZListLength; i++) {
12             accelerationZOffsetAbsolute = Math.abs(uniqueLinearAccelerationZList.get(i));
13 
14             if (maxAccelerationZOffsetAbsolute < accelerationZOffsetAbsolute) {
15                 maxAccelerationZOffsetAbsolute = accelerationZOffsetAbsolute;
16             }
17 
18             LogFunction.log("uniqueLinearAccelerationZList index" + i,
19                     "" + uniqueLinearAccelerationZList.get(i));
20         }
21 
22         uniqueLinearAccelerationZList.clear();
23 
24         LogFunction.log("uniqueLinearAccelerationZListLength",
25                 "" + uniqueLinearAccelerationZListLength);
26 
27         if (uniqueLinearAccelerationZListLength > unstableListLength) {
28             stable = false;
29             MainActivity.UpdateStable(stable);
30             return;
31         }
32 
33         LogFunction.log("maxAccelerationZOffsetAbsolute / linearAccelerationZStableSection",
34                 "" + (maxAccelerationZOffsetAbsolute / linearAccelerationZStableSection));
35 
36         if (maxAccelerationZOffsetAbsolute >
37                 linearAccelerationZStableSection * recognitionKnockRatio) {
38             LogFunction.log("recognitionKnockRatio", "" + recognitionKnockRatio);
39             LogFunction.log("recognitionUniqueRatio", "" + recognitionUniqueRatio);
40 
41             knockRecognitionSuccess(recognitionKnockNumber);
42         }
43     }

  終於到了最後的handleUniqueLinearAccelerationZ方法,顧名思義,就是用來處理獨特線性加速度列表的,在這個方法內,進行了敲擊識別和穩態狀態是否轉變的斷定,若是獨特線性加速度列表長度超過非穩態獨特線性加速度列表長度,則認爲如今手機狀態此刻狀態轉變爲非穩態並結束方法,若是發現加速度偏移數據列表中最大偏移值超過波動區間必定倍數則識別爲敲擊。

3.總結

  至此,敲擊識別的流程咱們算是走完了。事實上我提供的敲擊識別方法仍是存在着誤識別的狀況,ios的Knock我使用過,擁有着符合價格的能力,識別率至關的好,不知道他們是經過機器學習仍是別的方法歸結了一套他們的識別係數,固然我在此提供的敲擊識別僅僅是一種敲擊識別的方法,我也沒法說它成熟,由於並無通過真正用戶的考驗,你們徹底能夠按照本身的思想更換算法甚至更換傳感器來實現本身的敲擊識別,而我在此其實至關於提供一個實現思路。

  這是第三篇博客了,第一篇博客屬於試水就選擇了作過的一個比較偏門但並很差處理的一個小模塊:爲MP3文件寫入ID3標籤,第二篇博客選擇了一個很嚴謹的實用模塊:音頻合成,前兩個模塊都有一個共同點就是各類規範已經很明確,雖然代碼實現上可能有所不一樣但實現思路必然相同,而第三篇的博客的敲擊檢測無疑寬鬆不少,因此我也是第一次寫了實現思路這一小節,由於我也不肯定個人實現思路是否徹底正確,做爲傳感器的實際應用是存在着無數的可能性,咱們徹底能夠按照本身的想法去嘗試,錯了大不了換一個方向罷了。

  另外博客或者代碼中若是存在什麼問題,歡迎各位朋友們提出來。

  這篇博文就到這裏結束了,本文全部代碼已經託管到https://github.com/CrazyZty/KnockDetect,你們能夠自由下載。

相關文章
相關標籤/搜索