介紹一個“王者”演算法,它能認出遊戲裡的所有英雄
導語
為了能夠對自動提取王者榮耀視訊標籤,我們需要對王者榮耀遊戲視訊中的英雄進行檢測與識別,判斷該視訊中我方英雄以及友方和敵方,這就需要首先在視訊中檢測出英雄的位置和數量,然後對每個檢測到的英雄,判斷英雄的類別(我方/友方/敵方)並識別出英雄的姓名。筆者在實驗過程中,發現了一種two-stage演算法能夠較好地解決這一問題,採用基於血條模版匹配+後處理的方法實現英雄的檢測,第二階段採用深度神經網路對檢測到的英雄進行識別。
一、演算法調研
針對通用的影像或視訊中的目標檢測與識別任務,目前主流的演算法中有兩大類:一是檢測與識別作為兩個問題來解決,第一階段先在影像中檢測特定的目標,第二階段將檢測到的目標送到訓練好的CNN分類器裡,識別出檢測到的各個英雄;二是直接使用類似於SSD或者YOLO這樣的一步目標檢測與識別演算法,直接在影像上執行一遍演算法即可檢測並識別出所有出現的英雄。
考慮到王者榮耀這款具體遊戲,每個英雄頭頂上都會有一個固定形狀的血條。由於血條的外觀輪廓每個英雄都相同,僅僅是血條的顏色、血量、冷卻時間上存在差異,因此本文采用二階段法,第一階段通過模板匹配在影像中尋找所有英雄血條的位置,並根據血條位置摳取出對應的英雄影像;第二階段訓練一個深度CNN網路對摳取出的英雄影像進行分類識別。之所以選用二階段的演算法,是考慮到英雄血條的輪廓比較規整,使用模板匹配演算法會有較高的準確率,因此重心只需放在訓練影像分類網路,簡化演算法的複雜性。
二、素材收集
我們計劃使用基於CNN分類器的方法進行英雄識別,因此需要收集大量的視訊並從中提取英雄的圖片用於訓練。經過尋找,發現企鵝電競網站的素材庫裡有一個高光時刻欄目,裡面有大量網友上傳的王者榮耀精彩時刻視訊,且已經按英雄名稱進行了分類,因此可以直接下載使用。需要注意的是這個欄目裡的視訊有些是網友加了自己的後期處理,比如在遊戲視訊裡疊加了一些宣傳文字或主播頭像之類。我們儘量排除這種情況,篩選出取原始的、乾淨的遊戲視訊,使得演算法不受這些人為因素的干擾。由於使用模板匹配檢測血條的方法在影像中檢測英雄,所以必須保證所有輸入影像中血條的長寬比保持一致。經研究發現,部分視訊長寬比並非標準的16:9,如果直接將該視訊調整為16:9的長寬比將導致血條形狀失真而無法進行模板匹配,而保持視訊原有的長寬比則不會改變血條的長寬比。因此,對於非標準比例的視訊畫面,我們僅將其高度調整為與血條模板對應的高度(720畫素),而保留長寬比不變。
三、視訊中的英雄檢測
1. 血條模版匹配
我們通過檢測英雄血條的位置來定位英雄,由於血條中的血量、顏色、冷卻時間等內容會不斷變化,因此我們要設定掩碼,只對所有血條都有的相同部分進行模板匹配,而忽略不同的部分。具體來說,我們選取如下圖所示的血條及模板圖片
使用OpenCV自帶的模板匹配函式matchTemplate,可以方便地完成血條的模板匹配,由於matchTemplate函式僅支援單通道影像模板匹配,因此需將所有三通道彩色影像轉為單通道灰度影像。此外,由於讀入的視訊可能不是標準解析度(我們設定最常見的1280*720解析度為標準解析度),而血條和掩碼都是按照標準解析度製作的。同時為了儘量減小計算量,我們選擇縮放模版及掩碼而不選擇縮放影像,這是因為模版及掩碼畫素很少,縮放一次計算量小,而且只需在處理前縮放一次;縮放影像需要對每幀視訊都進行縮放,計算量較大。OpenCV縮放模版及掩碼影像的程式碼如下,其中scale_ratio為根據輸入視訊高度與標準高度之比計算出的縮放因子。
- img_template = cv2.resize(img_template, None, fx = scale_ratio, fy = scale_ratio, interpolation = cv2.INTER_LINEAR)
- img_mask = cv2.resize(img_mask, None, fx = scale_ratio, fy = scale_ratio, interpolation = cv2.INTER_LINEAR)
我們假設原影像為img_gray,血條模板影像為img_template,血條掩碼影像為img_mask,OpenCV模板匹配函式如下:
- img_result = cv2.matchTemplate(img_gray, img_template, cv2.TM_CCORR_NORMED, mask=img_mask)
匹配後的img_result影像是一個單通道32位浮點影像,各畫素值代表模板在該點與原影像的匹配程度,值越大表示匹配度越高。由於我們要檢測影像中的所有英雄的血條,而英雄數量是未知的,所以我們不能取固定的匹配度閾值。實踐也證明了固定匹配度的閾值效果也不好,容易產生漏檢和誤檢的現象。為了解決這一問題,我們首先觀察原影像和血條模版匹配後生成的匹配值影像,如圖所示:
不難發現,對於原影像中每個英雄血條的位置,在匹配圖中都會表現為一定範圍內的區域性極大值點,即中間亮周圍暗的模式,如匹配圖中的紅框所示,而在匹配圖的其他區域則這種區域性極大值效應不明顯。於是我們可以檢測匹配圖中的區域性極大值來濾除虛假響應,使用scipy函式庫的maximum_filter函式完成此功能。其中filter_size為濾波器核半徑,與影像大小成比例,對於標準解析度(1280*720)輸入影像,取值為10左右效果較好。該函式輸出仍然是一幅影像,輸出影像各點的畫素值代表該點周圍filter_size鄰域內的極大值點的畫素值。
- img_max = ndimage.maximum_filter(img, size = filter_size * 2)
使用該函式求得的影像區域性極大值點影像如下圖所示:
可以看出,在匹配圖上對應紅框位置的四個點,區域性極大值濾波器有較大的響應,意味著在匹配圖中這些位置確實存在區域性極大值。通過逐個畫素比較區域性極大值濾波前後的影像,即可找出影像的區域性極大值點。在匹配圖中查詢區域性極大值的程式碼如下,其中img和img_max分別為匹配圖及maximum_filter之後的影像。
- maximum_idx = np.argwhere(img == img_max)
該函式輸出一個所有找到的極大值的列表。一般情況下該函式會輸出大約幾百個區域性極大值,我們需要對這些值進行進一步的處理。我們可以計算每個極大值點與鄰域內其他點的亮度差異的絕對值,並在鄰域內求平均,並與極大值點本身的亮度值加權求和,從而得到每個極大值點的score。實踐中我們發現,計算數百個極大值點的score值比較耗時。由於影像中出現的英雄數量不會多於10個,沒有必要對幾百個極大值點都計算score值。於是我們在計算score操作之前,首先按照各極大值點的亮度值對所有的極大值點進行排序,取前20個點計算score,可以大大降低計算量且基本不會漏掉真實的血條。
我們維護一個極大值點的列表maximum_value_list,其元素為一個三元組(x, y, value),分別代表極大值點的x、y座標及極大值,按value值降序排序程式碼如下:
- maximum_value_list.sort(key = operator.itemgetter(2), reverse = True)
在選取前20個極大值點之後,我們對每個點分別計算score值,並將這20個點按score值從高到低排序(後續非極大抑制用)。由於無法預知影像中會有多少個英雄,仍然需要使用一個閾值來判斷。在這個階段使用閾值的效果已經遠遠好於在模版匹配圖上使用閾值的效果。只要選取一個合適的固定閾值,就可以實現對視訊中的各幀以及各個不同的視訊均有很好的檢測效果。檢測效果如圖所示:
可見畫面中的英雄血條均已正確檢測出來。
2. 非極大抑制
由於用於匹配的模版和掩碼均包含明顯的水平長線條(血槽的上下邊緣),導致在模版匹配時,模版在真實血條附近左右滑動一些畫素時,並不會顯著減小匹配的響應值。因此,有時候模版匹配會在水平條帶上產生多個鄰近的檢測結果,如圖所示:
為了應付這種情況,我們引入了非極大抑制演算法,對於基本處在同一水平位置,且相互之間鄰近的血條檢測結果,我們只取其中具有最大score值的檢測結果,而刪除其他的血條檢測結果,經過非極大抑制後的血條檢測結果如圖:
可見,通過非極大抑制,保留了正確的血條檢測結果而去掉了鄰近的虛假檢測結果。
3. 判斷英雄類別
通過判斷血條的顏色(綠色/藍色/紅色),可以將英雄先分為三類(我方、友方和敵方)。這部分演算法比較簡單,僅僅是根據血條檢測結果定位到血量的最左邊一格的顏色(考慮到英雄的血量可能會很低)。具體規則如下:當某個顏色通道的亮度值較大,且明顯大於另外兩個通道時,則直接給出對應的英雄類別;如果三個通道亮度值接近,且均在(70,100)區間內,則認為是空血條;如果以上兩種情況都不是,則認為是血條檢測錯誤,丟棄該血條檢測結果,可以進一步提高血條檢測的準確率。下圖為遊戲中切換道具的介面,該介面不應當檢測出有英雄的存在。但我們可以看到,在血條顏色檢測之前,會有一些誤檢,如紅框所示:
在血條顏色檢測之後,由於對應位置的顏色與任何一種血條顏色(包括空血)都不符,因此誤檢被濾除,如圖所示:
四、遊戲中的英雄識別
我們可以利用上述的英雄檢測演算法自動幫助提取用於訓練和驗證英雄分類器的樣本。由於下載的視訊檔案僅標註了主角英雄。因此我們在提取訓練和驗證樣本階段,僅將檢測範圍限定在影像中間的一個小範圍內(因為主角基本固定出現在畫面中間位置)。使用上述演算法對每個英雄的多段視訊進行檢測後,我們可以得到大量帶標註的英雄圖片。將提取出的每個英雄圖片分別存放在各自名稱的資料夾下面,檔名可以任意。下圖顯示了某個英雄資料夾下的樣本集圖片:
完成樣本收集後,我們採用基於Tensorflow且開源的TF-Slim庫完成訓練任務。採用TF-Slim庫的優點是可以直接使用自帶的多種主流CNN模型(包括AlexNet/VGG/Inception/Resnet等),不需要或僅僅編寫少量程式碼即可完成深度CNN網路的樣本集生成、訓練與驗證。
1. 訓練樣本集生成
首先將前述英雄檢測演算法中提取的我方英雄圖片按照英雄姓名放在各自的資料夾裡,每個英雄約有1000-2000張圖片。目前我們收集了12個英雄,共17931張英雄圖片。修改TF-Slim庫的相關程式碼,在其自帶的三個資料集基礎上,增加一個我們的英雄圖片資料集pvp_heros。我們隨機選擇其中的16000張圖片作為訓練集,剩下1931張圖片作為驗證集。
使用修改後的download_and_convert_data.py將這些圖片轉換為TF Record格式的檔案,命令列如下:
- $ DATA_DIR=/tmp/data/pvp_heros
- $ python download_and_convert_data.py
- --dataset_name=pvp_heros
- --dataset_dir="${DATA_DIR}"
程式執行結束後將在DATA_DIR目錄下分別生成一系列的以.tfrecord為副檔名的訓練集和相應的驗證集(具體每個資料集生成幾個.tfrecord檔案可以在修改相應程式碼中的_NUM_SHARDS常量設定),並生成一個labels.txt檔案,該檔案將類別名稱與數字建立一一對應關係,類似這樣:
0:周瑜
1:墨子
2:妲己
3:嬴政
4:安琪拉
5:小喬
6:扁鵲
7:武則天
8:甄姬
9:羋月
10:貂蟬
11:高漸離
……
2. 訓練CNN模型
我們選用流行且準確率非常高的Inception-ResNet-v2模型。使用train_image_classifer.py來訓練該模型的命令列如下:
- DATASET_DIR=/tmp/data/pvp_heros
- TRAIN_DIR=/tmp/checkpoints
- python train_image_classifier.py
- --train_dir=${TRAIN_DIR}
- --dataset_name=pvp_heros
- --dataset_split_name=train
- --dataset_dir=${DATASET_DIR}
- --model_name=inception_resnet_v2
DATASET_DIR為存放.tfrecord格式的訓練樣本所在的目錄,TRAIN_DIR為儲存訓練後的網路權值的目錄。訓練完成後,在TRAIN_DIR目錄下將會生成一系列checkpoint檔案,即為訓練後的網路權值。
3. 驗證模型
假設訓練後的最新checkpoint檔名為inception_resnet_v2.ckpt,我們使用如下程式碼來驗證剛訓練好的CNN模型,命令列如下:
- $ python eval_image_classifier.py
- --alsologtostderr
- --checkpoint_path=${TRAIN_DIR}/inception_resnet_v2.ckpt
- --dataset_dir=${DATASET_DIR}
- --dataset_name=pvp_heros
- --dataset_split_name=validation
- --model_name=inception_resnet_v2
其中TRAIN_DIR和DATASET_DIR含義如上所述。在驗證集上我們取得了Top-1準確率78%,Top-5準確率95%的成績。
4. 匯出模型與權值
CNN網路訓練完成後,為了能在英雄檢測程式中線上識別英雄,我們需要將模型圖和權值匯出,首先是匯出模型的結構圖:
- $ python export_inference_graph.py
- --alsologtostderr
- --model_name=inception_resnet_v2
- --output_file=${TRAIN_DIR}/inception_resnet_v2_inf_graph.pb
- --dataset_name=pvp_heros
然後我們需要凍結模型圖,即將模型的結構圖與權值凍結在一起,為此我們需要首先編譯一個tensorflow自帶的freeze_graph工具,然後用freeze_graph工具來實現模型圖的凍結操作:
- bazel build tensorflow/python/tools:freeze_graph
- bazel-bin/tensorflow/python/tools/freeze_graph
- --input_graph=${TRAIN_DIR}/inception_resnet_v2_inf_graph.pb
- --input_checkpoint=${TRAIN_DIR}/inception_v3.ckpt
- --input_binary=true
- --output_graph=${TRAIN_DIR}/frozen_inception_resnet_v2.pb
- --output_node_names=InceptionResnetV2/Logits/Predictions
- 其中輸出層的名字可以使用tensorflow自帶的summarize_graph工具檢視:
- bazel build tensorflow/tools/graph_transforms:summarize_graph
- bazel-bin/tensorflow/tools/graph_transforms/summarize_graph
- --in_graph=${TRAIN_DIR}/inception_resnet_v2_inf_graph.pb
5. 在影像上測試CNN模型
得到最終凍結後的模型圖,我們就可以在輸入影像上測試我們訓練後的inception-resnet-v2模型了,假設輸入影像已經讀入image_data中,輸出其英雄類別的關鍵程式碼如下:
- with tf.Session() as sess:
- softmax_tensor = sess.graph.get_tensor_by_name('InceptionResnetV2/Logits/Predictions:0')
- predictions = sess.run(softmax_tensor, {'input:0': image_data})
- predictions = np.squeeze(predictions)
- # Creates node ID --> English string lookup.
- node_lookup = NodeLookup(FLAGS.label_path)
- top_k = predictions.argsort()[-FLAGS.num_top_predictions:][::-1]
- for node_id in top_k:
- human_string = node_lookup.id_to_string(node_id)
- score = predictions[node_id]
- print('%s (score = %.5f)' % (human_string, score))
這段程式碼將輸出英雄的名稱(與labels.txt中的類別對應)及對應的分類器輸出score。我們在視訊中逐幀執行這段程式碼,即可得到我方英雄的名稱,如圖所示:
影像中顯示的c和s分別為英雄的類別和分類器輸出score,通過查詢labels.txt,得知類別6對應的名稱是扁鵲,和實際我方英雄名稱相符,英雄識別正確。
作者:姚文韜
來源:騰訊遊戲學院
原地址:https://mp.weixin.qq.com/s/GxpzFBzkAqKP5FWyNax6Yg
相關文章
- 英雄聯盟、王者榮耀、DNF,三大IP佈局格鬥遊戲,它能否重獲新生?遊戲
- CNN網路介紹與實踐:王者榮耀英雄圖片識別CNN
- Redux Hero Part 4:每個英雄都需要一個大反派(一種有趣的方式介紹 redux-saga)Redux
- Apache所有專案介紹Apache
- 《風暴英雄》裡那些超棒的遊戲設計遊戲設計
- 介紹一個MongoDB的替代方案MongoDB
- Istio所有模組、Service、Pod的功能介紹
- 幾個免費的頂級NFT遊戲介紹遊戲
- 給新加入遊戲行業的萌新:王者榮耀與英雄聯盟區別遊戲行業
- 推薦演算法(一)--基本介紹演算法
- Apriori演算法的介紹演算法
- Angular Ngrx store 裡的 Selector 介紹Angular
- 10月遊戲排行榜:黑馬小遊戲下載趕超王者榮耀;英雄聯盟手遊大放異彩或成新“王者”遊戲
- guava的wiki和Strings的所有方法介紹Guava
- 王者榮耀S14狂鐵的剋星英雄、最佳搭檔英雄詳解
- 介紹一個請求庫 — Undici
- 王者榮耀“吃雞模式”玩法介紹 王者榮耀邊境突圍怎麼玩模式
- 限流演算法介紹演算法
- GC演算法介紹GC演算法
- 王者榮耀英雄購買商城網頁版網頁
- 給開放世界遊戲一個“動作”支點,它能開啟未來嗎?遊戲
- 哪裡有介紹jive原始碼的文章?原始碼
- 詳細介紹在遊戲製作中的分形演算法(轉)遊戲演算法
- 介紹一個好用的 Laravel Menu 建構包Laravel
- python 介紹一個很好用的函式Python函式
- JavaScript 輸出介紹JavaScript
- FP-Growth演算法的介紹演算法
- 深入淺出JMS(一)——JMS簡單介紹
- 介紹一個軟體開發工具
- 介紹一個QTP基礎框架 - SIFLQT框架
- Java 8 中所有的包列表及介紹Java
- 不能出門躲在家玩《王者榮耀》的1.2億人,他們在遊戲的世界裡找到了什麼?遊戲
- 常用 API 演算法介紹API演算法
- 關於尋路演算法的一些思考(1):A*演算法介紹演算法
- 用Python爬取"王者農藥"英雄皮膚Python
- 王者榮耀:這個遊戲成了外媒口中的“神話”遊戲
- 這裡介紹了mysql for c的API ODBC JDBCMySqlAPIJDBC
- Oracle認證介紹及入門心得Oracle