AirTest : 關於識別效果的探索


背景:最近在調研AirTest這個測試工具,據說最近很火,同事說很多平台都在推,但是同事使用AirTest的IDE生成了代碼,然后發現在不同分辨率下,window大小不同的情況下,操作一些小的控件,並不是很穩定,后來我們就調研了一下AirTest的圖像識別。

1. 如何使用AirTest IDE

1.1 環境搭建:

如果你只想使用AirTest IDE,這就很簡單了,你只要下載AirTest IDE, 就可以直接使用了,IDE里面已經把需要的包都已經包含在內了,無需別的操作

解壓后的IDE 文件夾:

 

 執行里面的AirtestIDE.exe 就可以啟動AirTest IDE了。

 1.2 使用 AirTest IDE

關於如何使用AirTest IDE, 官網已經非常詳細,操作也比較簡單,相信大家去簡單看一下就會知道如何使用,就不多敘述了。

這里是一些官網的鏈接,大家可以參考一下:

5分鍾上手自動化測試

AirtestIDE使用文檔

快速上手系列教程

 

2. 查看AirTest 的測試代碼 

2.1 使用AirTest IDE編寫測試代碼

假設大家都已經會使用AirTest IDE了,現在展示一下最簡單的代碼,touch一下:

 

 2.2 代碼解釋

大家看到代碼其實非常簡單,第四行是從airtest的包中導入你需要的內容,第六行是進行一個touch操作。

在IDE中展示出來的是touch傳入一個圖片,后面我們對touch的整個流程進行分析,看看怎么能提高點擊的可靠性。

 

3. touch方法剖析

3.1 touch代碼解析

在AirTest IDE中,你看見的傳入一張圖片,如果你用別的IDE來打開,你看見的是如下的代碼:

1 # -*- encoding=utf8 -*-
2 __author__ = "Test"
3 
4 from airtest.core.api import *
5 
6 touch(Template(r"tpl1583817736399.png", record_pos=(2.282, 4.579), resolution=(500, 424)))

 如上所示, 真正的代碼中touch傳入的是一個 Template的對象,后面我們來一步一步的詳細的分析一下

3.2 touch 方法源碼解析

第一步,來看一下touch的源碼

 1 @logwrap
 2 def touch(v, times=1, **kwargs):
 3     """
 4     Perform the touch action on the device screen
 5 
 6     :param v: target to touch, either a Template instance or absolute coordinates (x, y)
 7     :param times: how many touches to be performed
 8     :param kwargs: platform specific `kwargs`, please refer to corresponding docs
 9     :return: finial position to be clicked
10     :platforms: Android, Windows, iOS
11     """
12     if isinstance(v, Template):
13         pos = loop_find(v, timeout=ST.FIND_TIMEOUT)
14     else:
15         try_log_screen()
16         pos = v
17     for _ in range(times):
18         G.DEVICE.touch(pos, **kwargs)
19         time.sleep(0.05)
20     delay_after_operation()
21     return pos
touch源碼

大家看到源碼中有幾個參數,下面也有比較清楚的解釋,v是一個目標對象,一個template對象或者是一個絕對坐標,times是你點擊多少次,后面的kwargs是傳給對應的device.touch的參數

好了,我們在往下看,如果傳的是坐標,那就直接點擊,如果不是坐標,那么來了,loop_find, 這應該是識圖了,后面的點擊是根據這個識圖的返回值來操作了,那我們就來看一下這個方法。

1 if isinstance(v, Template):
2         pos = loop_find(v, timeout=ST.FIND_TIMEOUT)
touch 識圖部分源碼

touch在拿到需要的坐標之后,使用G.DEVICE.touch(pos, **kwargs) 來調用對應的平台驅動來實現點擊事件,在這里就不深入討論了,后面探討一下識圖問題。

 

4. 識圖解析

4.1 loop_find源碼解析

 1 @logwrap
 2 def loop_find(query, timeout=ST.FIND_TIMEOUT, threshold=None, interval=0.5, intervalfunc=None):
 3     """
 4     Search for image template in the screen until timeout
 5 
 6     Args:
 7         query: image template to be found in screenshot
 8         timeout: time interval how long to look for the image template
 9         threshold: default is None
10         interval: sleep interval before next attempt to find the image template
11         intervalfunc: function that is executed after unsuccessful attempt to find the image template
12 
13     Raises:
14         TargetNotFoundError: when image template is not found in screenshot
15 
16     Returns:
17         TargetNotFoundError if image template not found, otherwise returns the position where the image template has
18         been found in screenshot
19 
20     """
21     G.LOGGING.info("Try finding:\n%s", query)
22     start_time = time.time()
23     while True:
24         screen = G.DEVICE.snapshot(filename=None, quality=ST.SNAPSHOT_QUALITY)
25 
26         if screen is None:
27             G.LOGGING.warning("Screen is None, may be locked")
28         else:
29             if threshold:
30                 query.threshold = threshold
31             match_pos = query.match_in(screen)
32             if match_pos:
33                 try_log_screen(screen)
34                 return match_pos
35 
36         if intervalfunc is not None:
37             intervalfunc()
38 
39         # ???raise,??????????:
40         if (time.time() - start_time) > timeout:
41             try_log_screen(screen)
42             raise TargetNotFoundError('Picture %s not found in screen' % query)
43         else:
44             time.sleep(interval)
loop_find源碼

 參數解析:

  • query:你想要查找的圖片的template
  • timeout: 你查找的圖片的超時時間
  • threshold:你查圖的閾值,就是打分了,你想找的圖片在整個的圖像里面的打分,超過就算識別到,低於就是沒識別到,你可以降低這個閾值,但是也有可能導致找錯,按照你的需求來,不給的話會有一個默認值
  • interval:查圖有時並不是一次,會等待一段時間后繼續查詢
  • intervalfunc:查圖失敗之后做的一些操作,當前不討論, 在touch函數中並不涉及此參數

 流程解析:

 loop_find所做的操作流程是這樣的:

  • 先拿到當前操作界面的整個截圖
  • 拿你傳入的截圖template來進行匹配
  • 拿匹配值和當前的期望閾值進行比價
  • 比較成功則返回預期坐標,失敗則等待后繼續匹配
  • 若匹配超時,最終返回異常

 由上述流程來看,其核心操作其實就是在用你的template進行匹配,其代碼如下所示

1 if threshold:
2     query.threshold = threshold
3 match_pos = query.match_in(screen)
loop_find圖像匹配部分代碼

 大家看到,圖像匹配其實在match_in方法之中,我們再來看下這個方法。

 4.2 match_in源碼解析

match_in這個方法是從哪來的呢,從參數來看,是query的方法,query是什么,是template,那就看下這個方法的源碼:

1 def match_in(self, screen):
2         match_result = self._cv_match(screen)
3         G.LOGGING.debug("match result: %s", match_result)
4         if not match_result:
5             return None
6         focus_pos = TargetPos().getXY(match_result, self.target_pos)
7         return focus_pos
match_in源碼

源碼中最主要的是match_result, 后面都是對該變量的處理,那圖像匹配的方法就在_cv_match這個方法里面了,下面再看下_cv_match的源碼。

4.3 _cv_match源碼解析

 1 @logwrap
 2     def _cv_match(self, screen):
 3         # in case image file not exist in current directory:
 4         image = self._imread()
 5         image = self._resize_image(image, screen, ST.RESIZE_METHOD)
 6         ret = None
 7         for method in ST.CVSTRATEGY:
 8             # get function definition and execute:
 9             func = MATCHING_METHODS.get(method, None)
10             if func is None:
11                 raise InvalidMatchingMethodError("Undefined method in CVSTRATEGY: '%s', try 'kaze'/'brisk'/'akaze'/'orb'/'surf'/'sift'/'brief' instead." % method)
12             else:
13                 ret = self._try_match(func, image, screen, threshold=self.threshold, rgb=self.rgb)
14             if ret:
15                 break
16         return ret
_cv_match源碼

根據源碼,我們可以看到,整個匹配的核心流程就是先resize image然后根據ST.CVSTRATEGY里面所包含的策略方法進行輪訓匹配, 最后返回匹配結果。

ST.CVSTRATEGY里面的方法都是在open cv的基礎上進行封裝的,是不變的模式,沒有辦法調整,那么我們能調整的是什么,是image resize 這個行為,圖片到底是怎么resize的,我們來進一步看一下

4.4 _resize_image源碼解析

 1 def _resize_image(self, image, screen, resize_method):
 2         """模板匹配中,將輸入的截圖適配成 等待模板匹配的截圖."""
 3         # 未記錄錄制分辨率,跳過
 4         if not self.resolution:
 5             return image
 6         screen_resolution = aircv.get_resolution(screen)
 7         # 如果分辨率一致,則不需要進行im_search的適配:
 8         if tuple(self.resolution) == tuple(screen_resolution) or resize_method is None:
 9             return image
10         if isinstance(resize_method, types.MethodType):
11             resize_method = resize_method.__func__
12         # 分辨率不一致則進行適配,默認使用cocos_min_strategy:
13         h, w = image.shape[:2]
14         w_re, h_re = resize_method(w, h, self.resolution, screen_resolution)
15         # 確保w_re和h_re > 0, 至少有1個像素:
16         w_re, h_re = max(1, w_re), max(1, h_re)
17         # 調試代碼: 輸出調試信息.
18         G.LOGGING.debug("resize: (%s, %s)->(%s, %s), resolution: %s=>%s" % (
19                         w, h, w_re, h_re, self.resolution, screen_resolution))
20         # 進行圖片縮放:
21         image = cv2.resize(image, (w_re, h_re))
22         return image
_resize_image源碼

通過源碼,其實注釋已經很清楚了,如果你沒有記錄了截圖時的分辨率,那么就會用原圖的原始比例在window的截圖上匹配,如果記錄了分辨率,會根據你的分辨率和當前window截圖的分辨率根據策略來進行圖片縮放。

那這個就簡單了,核心就是說怎么才能讓你的截圖在不同的分辨率下更接近當前的window截圖中的圖片,后面我們來分析一下。

 

5. 優化圖片縮放

5.1 圖片縮放策略

當你測試的window分辨率變化時,圖片大體上會有幾種變化,如果還有別的模式,歡迎補充。

  • 圖片不隨window的變化而變化
  • 圖片隨window的變化而同比例變化
  • 圖片有不同的尺寸,在window特定的尺寸范圍內變化
  • 圖片隨window特定尺寸變化,且尺寸有縮放 (有種window,在不同尺寸下,整體布局也不一樣,這種操作就比較復雜,我就描述了。)

5.2 測試前提條件

現在我們來模擬一下測試場景。

我打開了Microsoft Store的一個game 頁面,如下圖,在圖片最大時,在圖中截取了一塊圖片,后面開始匹配。

 

 此圖片在不同的window大小情況下會呈現不同的大小,上述第三種情況。

測試代碼如下所示:

1 from vendor.airtest.core.api import Template, touch, auto_setup, connect_device, loop_find
2 
3 auto_setup(__file__)
4 connect_device("Windows:///?title_re=.*Microsoft Store.*")
5 
6 pos = loop_find(Template("./data/h.png", record_pos=(1.269, 0.013), resolution=(820, 679)))

我先連接了Microsoft Store的window,然后去查找我截圖的部分,看下匹配結果。(使用loop_find來替換touch,結果更清晰。)

5.3 測試策略優化

上面談到了圖片的幾種變化,我們就來根據圖片的變化來優化我們的代碼,來看一下效果如何。

  • 場景一: window放大,圖片尺寸不變

我這次放大了window的尺寸,然后看一下匹配的結果是多少。

1 threshold=0.7, result={'confidence': 0.45194211602211, 'result': (129, 499), 'rectangle': ((105, 477), (105, 521), (154, 521), (154, 477))}

如上所示,匹配結果只有0.45,遠小於0.7的期望值,查詢失敗了。

我放大了size,但是圖片不變,其中的resize執行之后就匹配不到了,這不太科學,那我們這次改變測試代碼,不傳入分辨率。

1 pos = loop_find(Template("./data/h.png", record_pos=(1.269, 0.013)))

結果如下所示:

1 threshold=0.7, result={'confidence': 1.0, 'result': (129, 500), 'rectangle': ((110, 483), (110, 517), (148, 517), (148, 483))}

嗯,1.0完成,也就是百分百匹配到,也就是說對於window尺寸來說,不變化的圖像,就刪除分辨率屬性,保持原有尺寸,不進行縮放來匹配,這樣的效果會比較好。

  • 場景二:圖片尺寸隨着window的變化而變化

由於Microsoft Store上面沒有隨window比例變化的圖片,我就把圖片截圖然后放在相冊里來測試,因為相冊不是完全隨比例變化的,所有查詢精度會變低,但是不影響結果,如下所示:

1 threshold=0.7, result={'confidence': 0.96263587474823, 'result': (98, 227), 'rectangle': ((85, 217), (85, 238), (111, 238), (111, 217))}

這說明如果是隨比例縮放的圖片,就保持AirTest IDE中的代碼,保持分辨率參數,就可以很好的查找圖片了。

  • 場景三:圖片隨window變化而伴隨幾種特定尺寸

這種就比較麻煩了,第一不是不變的,第二不是隨比例變化的。

我推薦的策略是:

第一,測試過程中先將你的window resize到固定的尺寸大小,這樣測試也會相對穩定

第二,如果window並不能固定,那我推薦把圖片變成一個變量,根據不同的分辨率,先初始化一套符合當前的圖片組,然后進行測試

具體哪套更合適你,或者有更好的方式,可以留言,大家共同探討一下。

 

以上就是關於AirTest識圖准確性的一點個人看法,歡迎探討。

 


免責聲明!

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



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