電競AI之視覺篇:YOLO算法在電競中的應用(Darknet + TF)

轉戰米國,經過一段時間的調整和適應,終於有時間整理下最近做的一個項目。從infra到雲到大數據到AI,各個領域都應該保持學習,技術的道路從來都不是一帆風順。

1. 場景介紹

MOBA玩家都比較熟悉不論是DOTA2還是LOL,遊戲內會有minimap,爲玩家提供位置、視野及信號等信息,幫助對局勢進行判斷。

假設我們在一個非直播的比賽數據頁面,通過小地圖的數據,一方面幫助高玩在沒有流量的情況下也能合理分析比賽形勢,有更多的參與度;另一方面,一個選手的運動軌跡我們補獲後,做大數據的分析,對其某個英雄某一種行動路徑在什麼時間都可以有一個基於歷史的推演,有助於戰隊針對性的訓練。

那我們要怎麼獲取小地圖的行動軌跡數據呢,方法A成爲遊戲廠商的數據下游,花錢花關係妥妥的;方法B找下官方有沒有實時數據的接口,實時數據的接口裏有沒有位置的信息,方法C,自己想辦法人工實時錄(幾乎不可能), 上網一搜LOL小地圖識別,Farzaa大佬已經驗證過基於YOLO算法的英雄地圖識別的,但是也僅此一篇文章。

Farzaa的文章中還是存在幾個問題:

  • 只驗證了LCS的部分比賽,樣本集不夠多,怎麼快速覆蓋全英雄比賽?
  • 根據Farzaa的描述,獲取實時數據的方式貌似已經過時了,怎麼獲取到英雄位置數據的樣本?

接下來爲大家介紹我的實踐方式與最終的效果。

2. yolo - yolov2 - yolov3算法

在開始我們系統搭建之前,還是有必要說明下什麼是yolo算法。(作者也是AI starter,需要各位讀者也補充些AI的知識,比如吳恩達的CNN課程,神經網絡的課程及線性代數等等)

YOLO(You only look once) 是一種快速且高識別率的目標檢測和識別的算法和網絡模型。YOLO的基本邏輯是將輸入圖片或者視頻幀做多尺寸resize,然後送入CNN網絡,最後處理網絡預測結果得到檢測的目標的confidence和bounding box(處理大致流程如下圖)。

image.png

2.1 YOLOv1的CNN網絡模型

YOLOv1網絡

2.2 YOLOv2及v3在此基礎上做的部分優化(持續補充):

  • 正則化,做反向傳播時加速訓練
  • 結合Faster RCNN的思路,引入RPN(Region Proposal Network)替代v1的全連接層並減少網絡計算量, 大致步驟如
    • Step1: 圖片通過CNN網絡進行濾波,得到各通道特徵
    • Step2: 選擇滑動窗(一般3X3),每次滑動時會生成9個標定窗口,並計算IoU(Intersection over union)
    • Step3: 將得到的IoU進行classification和regression,最終得到檢測目標的boudning box
  • 特徵提取的優化,比如參考vgg16的darknet-19到v3的darknet-53

Dark-19

Dark-53

按照yolov3作者的操作說明(YOLOv3操作指南),基本可以成功得到以下的測試結果:
檢測與識別效果

3. 打標系統

CNN/DL沒有想象那麼複雜,一個深度學習的模型,部分精力是在設計怎麼提取特徵,如果對數據進行預處理,以及tuning parameter及hyperparamter。我們之前有了使用darknet訓練和識別的經驗,現在來說明下怎麼獲取LOL的minimap樣本的VOC數據。

LOL minimap

3.1 打標步驟

首先,我們做個簡單的截屏腳本,在各直播畫面截取上圖的minimap,頻率可以10-15s一個; 之後我們將圖片批量倒入我們的打標系統進行標定,這裏主要是生成每個英雄的boudning box座標(xmin, ymin, xmax, ymax),保存後會生成VOC所需的xml文件。

打標系統

這裏爲什麼需要打標系統,主要是打標依賴人工,以s9爲例,一場比賽就可以生成1000多張的圖片,所以打標系統服務化可以最大程度的多人同時標定。當然每個人獲取到的圖片也是隨機獲取顯示的,儘量不重複標定。(後續我們做了基於特定場景的自動生成樣本集和自動增量訓練系統,下一篇可以介紹)

3.2 打標數據

標定之後的xml數據格式如

<annotation>
    <folder>imgs</folder>
    <filename>xxxxxx.jpg</filename>
    <path>/xxxx/lol-champions/imgs/xxxxxx.jpg</path>
    <source>
        <database>Unknown</database>
    </source>
    <size>
        <width>290</width>
        <height>285</height>
        <depth>3</depth>
    </size>
    <segemented>0</segemented>
    <object>
        <name>urgot</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>94</xmin>
            <ymin>22</ymin>
            <xmax>110</xmax>
            <ymax>43</ymax>
        </bndbox>
    </object>
    <object>
        <name>thresh</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>189</xmin>
            <ymin>70</ymin>
            <xmax>203</xmax>
            <ymax>89</ymax>
        </bndbox>
    </object>
</annotation>

4. TF訓練

這裏貌似沒有什麼特別要介紹的,github上已經有挺多開源的YOLOv3的TF實現,不過這裏有必要提一下darknet是c的實現,會比tf的訓練速度快很多,GTX 1080的顯卡跑20000個epoch darknet基本一晚上就好了,tf跑的話要幾天,當然這個還取決於顯卡的內存,batch size的大小,圖片的大小以及epoch的設置,仁者見仁,智者見智吧。

這裏邏輯上說下我的思路,打標系統的圖片和生成的xml文件需要符合VOC的目錄結構,這樣可以用一些生成訓練集及測試集的腳本統一處理,再由下面的腳本處理提取出座標和分類名。

PASCAL VOC的目錄結構(自定義也可以)

.
└── VOCdevkit     
    └── VOC2012 
        ├── Annotations      #xml文件
        ├── ImageSets        
        │   ├── Main         #訓練集與測試集.txt文件
        ├── JPEGImages       #原圖片
def convert_voc_annotation(data_path, data_type, anno_path, use_difficult_bbox=True):

    classes = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus',
               'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse',
               'motorbike', 'person', 'pottedplant', 'sheep', 'sofa',
               'train', 'tvmonitor']
    img_inds_file = os.path.join(data_path, 'ImageSets', 'Main', data_type + '.txt')
    with open(img_inds_file, 'r') as f:
        txt = f.readlines()
        image_inds = [line.strip() for line in txt]

    with open(anno_path, 'a') as f:
        for image_ind in image_inds:
            image_path = os.path.join(data_path, 'JPEGImages', image_ind + '.jpg')
            annotation = image_path
            label_path = os.path.join(data_path, 'Annotations', image_ind + '.xml')
            root = ET.parse(label_path).getroot()
            objects = root.findall('object')
            for obj in objects:
                difficult = obj.find('difficult').text.strip()
                if (not use_difficult_bbox) and(int(difficult) == 1):
                    continue
                bbox = obj.find('bndbox')
                class_ind = classes.index(obj.find('name').text.lower().strip())
                xmin = bbox.find('xmin').text.strip()
                xmax = bbox.find('xmax').text.strip()
                ymin = bbox.find('ymin').text.strip()
                ymax = bbox.find('ymax').text.strip()
                annotation += ' ' + ','.join([xmin, ymin, xmax, ymax, str(class_ind)])
            print(annotation)
            f.write(annotation + "\n")
    return len(image_inds)

5. 結論

image.png
image.png

video效果.gif

是不是很有意思,歡迎討論。還有什麼電競中的場景可以結合deeplearning呢?

文獻參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章