天天看點

AI開發實踐丨客流分析之未佩戴口罩識别

本文分享自華為雲社群《客流分析之未佩戴口罩識别》,作者: HiLens_feige 。原文戳→https://bbs.huaweicloud.com/blogs/393391

疫情雖然過去了,口罩佩戴防護依然十分重要,本文在 基于頭肩部檢測的過線客流統計 博文基礎上增加口罩佩戴檢測功能:采用頭肩部檢測人形并進行跟蹤,當頭肩部中心點跨過事先劃定的線段時,增加客流計數,同時檢查此人是否佩戴口罩;過線前後的人形将使用不同顔色的框表示,畫面中會實時顯示客流數量,若有未佩戴口罩的人也會産生告警。

準備工作

本文将使用華為雲ModelArts進行人形檢測模型的訓練,并使用ModelBox架構進行應用開發,使用前開發者需要完成如下準備工作:

  1. 參考 此教程 完成裝置的注冊;
  2. 參考 此教程 完成ModelBox SDK的安裝。

技能開發

這個應用對應的ModelBox版本已經做成模闆放在華為雲OBS中,可以用sdk中的solution.bat工具下載下傳,接下來我們給出該應用在ModelBox中的完整開發過程:

1)下載下傳模闆

執行.\solution.bat -l可看到目前公開的技能模闆:

PS ███> .\solution.bat -l
...

Solutions name:
mask_det_yolo3
...
passenger_flow_mask_det_yolo7_LCNet
           

結果中的passenger_flow_mask_det_yolo7_LCNet即為過線客流統計+口罩佩戴識别應用模闆,可使用如下指令下載下傳模闆:

PS ███> .\solution.bat -s passenger_flow_mask_det_yolo7_LCNet
...
           

solution.bat工具的參數中,-l 代表list,即列出目前已有的模闆名稱;-s 代表solution-name,即下載下傳對應名稱的模闆。下載下傳下來的模闆資源,将存放在ModelBox核心庫的solution目錄下。

2)建立工程

在ModelBox sdk目錄下使用create.bat建立passenger_flow_mask_det工程

PS ███> .\create.bat -t server -n passenger_flow_mask_det -s passenger_flow_mask_det_yolo7_LCNet
sdk version is modelbox-xxx
success: create passenger_flow_mask_det in ███\modelbox\workspace
           

create.bat工具的參數中,-t 表示建立事務的類别,包括工程(server)、Python功能單元(Python)、推理功能單元(infer)等;-n 代表name,即建立事務的名稱;-s 代表solution-name,表示将使用後面參數值代表的模闆建立工程,而不是建立空的工程。

workspace目錄下将建立出passenger_flow_mask_det工程,工程内容如下所示:

passenger_flow_mask_det
|--bin
│  |--main.bat:應用執行入口
│  |--mock_task.toml:應用在本地執行時的輸入輸出配置,此應用預設使用本地視訊檔案為輸入源,最終結果輸出到另一本地視訊檔案,可根據需要修改
|--CMake:存放一些自定義CMake函數
|--data:存放應用運作所需要的圖檔、視訊、文本、配置等資料
│  |--passenger_flow.mp4:客流統計測試用視訊檔案
│  |--simsun.ttc:中文字型庫
|--dependence
│  |--modelbox_requirements.txt:應用運作依賴的外部庫在此檔案定義,本應用依賴pillow工具包
|--etc
│  |--flowunit:應用所需的功能單元存放在此目錄
│  │  |--cpp:存放C++功能單元編譯後的動态連結庫,此應用沒有C++功能單元
│  │  |--collapse_multi_face:合并功能單元,将同一張圖的多個人臉的口罩佩戴資料進行合并輸出
│  │  |--draw_passenger_bbox:客流畫圖功能單元
│  │  |--expand_face_images:展開功能單元,将同一張圖的多個人臉檢測框展開為多個輸出
│  │  |--face_condition:條件功能單元,根據圖中是否檢測到人臉輸出不同的分支
│  │  |--merge_track_face_info:将跟蹤資訊與口罩佩戴資訊整合輸出
│  │  |--object_tracker:目标跟蹤功能單元
│  │  |--yolov7_post:頭肩部檢測使用的是YOLO7模型,此處即為後處理功能單元
|--flowunit_cpp:存放C++功能單元的源代碼,此應用沒有C++功能單元
|--graph:存放流程圖
│  |--passenger_flow_mask_det.toml:預設流程圖,使用本地視訊檔案作為輸入源
│  |--modelbox.conf:modelbox相關配置
|--hilens_data_dir:存放應用輸出的結果檔案、日志、性能統計資訊
|--model:推理功能單元目錄
│  |--head_detection:頭肩部檢測推理功能單元
│  │  |--head_detection.toml:頭肩部檢測推理功能單元的配置檔案
│  │  |--head_det_yolo7_lite_224x352.onnx:頭肩部檢測onnx模型
│  |--mask_cls:口罩佩戴分類推理功能單元
│  │  |--mask_cls.toml:口罩佩戴分類推理功能單元的配置檔案
│  │  |--mask_cls.onnx:口罩佩戴分類onnx模型
|--build_project.sh:應用建構腳本
|--CMakeLists.txt
|--rpm:打包rpm時生成的目錄,将存放rpm包所需資料
|--rpm_copyothers.sh:rpm打包時的輔助腳本
           

3)檢視流程圖

passenger_flow_mask_det工程graph目錄下存放流程圖,預設的流程圖passenger_flow_mask_det.toml與工程同名,其内容為(以Windows版ModelBox為例):

[driver]
# 功能單元的掃描路徑,包含在[]中,多個路徑使用,分隔
# ${HILENS_APP_ROOT} 表示目前應用的實際路徑
# ${HILENS_MB_SDK_PATH} 表示ModelBox核心庫的實際路徑
dir = [
    "${HILENS_APP_ROOT}/etc/flowunit",
    "${HILENS_APP_ROOT}/etc/flowunit/cpp",
    "${HILENS_APP_ROOT}/model",
    "${HILENS_MB_SDK_PATH}/flowunit",
]
skip-default = true

[profile]
# 通過配置profile和trace開關啟用應用的性能統計
profile = false                       # 是否記錄profile資訊,每隔60s記錄一次統計資訊
trace = false                         # 是否記錄trace資訊,在任務執行過程中和結束時,輸出統計資訊
dir = "${HILENS_DATA_DIR}/mb_profile" # profile/trace資訊的儲存位置

[flow]
desc = "passenger flow count and mask detection example using yolo7 and LCNet for local video or rtsp video stream" # 應用的簡單描述

[graph]
format = "graphviz" # 流程圖的格式,目前僅支援graphviz
graphconf = """digraph passenger_flow_mask_det {
    node [shape=Mrecord]
    queue_size = 4
    batch_size = 1

    # 定義節點,即功能單元及其屬性
    input1[type=input, flowunit=input, device=cpu, deviceid=0]
    data_source_parser[type=flowunit, flowunit=data_source_parser, device=cpu, deviceid=0]
    video_demuxer[type=flowunit, flowunit=video_demuxer, device=cpu, deviceid=0]
    video_decoder[type=flowunit, flowunit=video_decoder, device=cpu, deviceid=0, pix_fmt="rgb"]
    resize[type=flowunit flowunit=resize device=cpu deviceid="0" image_width=352, image_height=224]
    color_transpose[type=flowunit flowunit=packed_planar_transpose device=cpu deviceid="0"]
    normalize[type=flowunit flowunit=normalize device=cpu deviceid="0" standard_deviation_inverse="0.0039215686,0.0039215686,0.0039215686"]
    head_detection[type=flowunit flowunit=head_detection device=cpu deviceid="0"]
    yolov7_post[type=flowunit flowunit=yolov7_post device=cpu deviceid="0"]
    object_tracker[type=flowunit flowunit=object_tracker device=cpu deviceid="0"]
    face_condition[type=flowunit flowunit=face_condition device=cpu deviceid="0"]
    expand_face_images[type=flowunit, flowunit=expand_face_images, device=cpu, deviceid=0]
    image_resize2[type=flowunit, flowunit=resize, device=cpu, deviceid=0, image_width=96, image_height=96]
    color_transpose2[type=flowunit flowunit=packed_planar_transpose device=cpu deviceid="0"]
    mean[type=flowunit flowunit=mean device=cpu deviceid="0" mean="123.675, 116.28, 103.53"]
    normalize2[type=flowunit flowunit=normalize device=cpu deviceid="0" standard_deviation_inverse="0.01742919,0.0175070,0.01712475"]
    mask_cls[type=flowunit, flowunit=mask_cls, device=cpu, deviceid=0]
    collapse_multi_face[type=flowunit, flowunit=collapse_multi_face, device=cpu, deviceid=0]
    merge_track_face_info[type=flowunit, flowunit=merge_track_face_info, device=cpu, deviceid=0]
    draw_passenger_bbox[type=flowunit, flowunit=draw_passenger_bbox, device=cpu, deviceid=0]
    video_out[type=flowunit, flowunit=video_out, device=cpu, deviceid=0]

    # 定義邊,即功能間的資料傳遞關系
    input1:input -> data_source_parser:in_data
    data_source_parser:out_video_url -> video_demuxer:in_video_url
    video_demuxer:out_video_packet -> video_decoder:in_video_packet
    video_decoder:out_video_frame -> resize:in_image
    resize:out_image -> color_transpose:in_image
    color_transpose:out_image -> normalize:in_data
    normalize:out_data -> head_detection:input
    head_detection:output -> yolov7_post:in_feat
    yolov7_post:out_data -> object_tracker:in_bbox
    video_decoder:out_video_frame -> face_condition:in_image
    object_tracker:out_track -> face_condition:in_track
    face_condition:no_face -> draw_passenger_bbox:in_image
    face_condition:has_face -> expand_face_images:in_image
    expand_face_images:roi_images -> image_resize2:in_image
    image_resize2:out_image -> color_transpose2:in_image
    color_transpose2:out_image -> mean:in_data
    mean:out_data -> normalize2:in_data
    normalize2:out_data -> mask_cls:input
    mask_cls:output -> collapse_multi_face:in_feat
    collapse_multi_face:out_data -> merge_track_face_info:in_face_info
    face_condition:has_face -> merge_track_face_info:in_image
    merge_track_face_info:out_image -> draw_passenger_bbox:in_image
    draw_passenger_bbox:out_image -> video_out:in_video_frame
}"""
           

整個應用邏輯較為複雜,視訊解碼後做圖像預處理,接着是頭肩部檢測,模型後處理得到頭肩框,送入跟蹤算法進行實時跟蹤與過線判斷,此時根據是否有人過線并檢測到人臉分成兩個分支,一個分支直接輸出跟蹤資訊,另一個分支将人臉檢測框裁剪出來做口罩佩戴識别,最後将跟蹤資訊與口罩識别結果整合後畫到圖像輸出到視訊中。應用中使用到了 條件功能單元、展開/合并功能單元。

4)核心邏輯

本應用的核心邏輯有幾處:

  • 過線時刻人臉圖檔的提取,用于判斷是否佩戴口罩

    這一段邏輯展現在 object_tracker 功能單元 object_tracker.py 的 get_tracking_objects 函數中:

def get_tracking_objects(self, line_y):
        '''從跟蹤器中擷取跟蹤目标,儲存到結構化資料中'''
        def _is_pass_line(bbox, line_y):
            '''根據檢測框的中心點與線段的水準位置關系判斷是否過線'''
            center_y = (bbox[1] + bbox[3]) / 2
            return center_y > line_y

        track_bboxes = [track.det for track in self.tracker.tracks]
        matches, _, _ = match(face_bboxes, track_bboxes, self.face_cover_ratio, True)

        tracking_objects = []  # 所有跟蹤目标
        face_info = {}  # 剛剛過線的人頭目标對應的人臉框,track_id -> face_bbox
        for ix, track in enumerate(self.tracker.tracks):
            # 隻記錄CONFIRMED狀态的跟蹤目标
            if track.state != EasyTracker.TrackingState.CONFIRMED:
                continue
            tracking_obj = {}  # 使用字典儲存跟蹤目标
            tracking_obj["id"] = track.track_id  # 跟蹤id
            tracking_obj["bbox"] = track.det  # 跟蹤框
            if not track.passline and _is_pass_line(track.det, line_y):  # 剛好過線
                track.passline = True
                self.flow_count += 1
                if ix in matches:  # 找到了比對的人臉框
                    face_info[track.track_id] = face_bboxes[matches[ix]]
            tracking_obj["passline"] = track.passline  # 記錄過線資訊
            tracking_objects.append(tracking_obj)
        return tracking_objects, face_info
           

可以看到,與 過線客流統計 中的代碼相比,此處還做了頭肩部與人臉的比對,當某頭肩部過線時,若它比對到了人臉(人臉框在頭肩部框中),将它們進行關聯(使用跟蹤id關聯)。後面的功能單元跟人臉框資訊裁剪出人臉圖檔進行口罩佩戴分類。

  • 口罩佩戴資訊的傳遞,用于最後畫圖和生成告警

    這一段邏輯展現在 merge_track_face_info 功能單元 merge_track_face_info.py 的 process 函數中:

def process(self, data_context):
        # 從DataContext中擷取輸入輸出BufferList對象
        in_image = data_context.input("in_image")
        in_face_info = data_context.input("in_face_info")
        out_image = data_context.output("out_image")

        # 循環處理每一個輸入Buffer資料
        for buffer_img, buffer_mask in zip(in_image, in_face_info):
            # 擷取輸入圖像Buffer的寬、高、通道數等屬性資訊
            width = buffer_img.get('width')
            height = buffer_img.get('height')
            channel = buffer_img.get('channel')

            # 将輸入圖像Buffer轉換為numpy對象
            img_data = np.array(buffer_img.as_object(), dtype=np.uint8, copy=False)
            img_data = img_data.reshape(height, width, channel)

            # 提取口罩佩戴資訊,轉換為numpy對象
            mask_info = np.array(buffer_mask.as_object(), copy=False)

            # 提取圖像Buffer中附帶的json跟蹤資料,轉換為dict對象
            track_info = json.loads(buffer_img.get("track_info"))
            face_info = json.loads(track_info['face_info'])
            for ix, id in enumerate(face_info):  # 将口罩佩戴資訊更新到人臉資料中
                face_info[id] = int(mask_info[ix])

            # 更新跟蹤資料dict,轉換為json對象
            track_info["face_info"] = json.dumps(face_info)
            buffer_img.set("track_info", json.dumps(track_info))

            # 将輸出Buffer放入輸出BufferList中
            out_image.push_back(buffer_img)

        # 傳回成功标志,ModelBox架構會将資料發送到後續的功能單元
        return modelbox.Status.StatusCode.STATUS_SUCCESS
           

可以看到,口罩佩戴資料整合到跟蹤資料中,作為屬性附在圖檔上往後傳遞。

5)三方依賴庫

本應用中的畫圖功能單元以來 pillow 工具包以實作中文輸出,ModelBox應用不需要手動安裝三方依賴庫,隻需要配置在 dependence\modelbox_requirements.txt 即可。另外,中文輸出還需要對應的字型庫,存放在 data 目錄下,畫圖功能單元初始化時将從此目錄加載資源。

6)檢視輸入輸出配置

檢視任務配置檔案bin/mock_task.toml,可以看到其中的任務輸入和任務輸出配置為如下内容::

[input]
type = "url"
url = "${HILENS_APP_ROOT}/data/passenger_flow.mp4"  # 表示輸入源為本地視訊檔案

[output]
type = "local"
url = "${HILENS_APP_ROOT}/hilens_data_dir/passenger_flow_result.mp4"  # 表示輸出為本地視訊檔案
           

即,使用本地視訊檔案data/passenger_flow.mp4作為輸入,統計過線客流後,畫圖輸出到本地視訊檔案data/passenger_flow_result.mp4中。

7)用啟動腳本執行應用

啟動應用前執行.\build_project.sh進行工程建構,該腳本将編譯自定義的C++功能單元(本應用不涉及)、将應用運作時會用到的配置檔案轉碼為Unix格式(防止執行過程中的格式錯誤):

PS ███> .\build_project.sh
...
PS ███>
           

然後執行.\bin\main.bat運作應用:

PS ███> .\bin\main.bat
...
           

運作結束後在hilens_data_dir目錄下生成了passenger_flow_result.mp4檔案,可以打開檢視:

AI開發實踐丨客流分析之未佩戴口罩識别

可以看到,黃色線段即為過線統計的水準線段,未過線的人使用灰色框标記其頭肩部,已過線且未佩戴口罩的使用紅色框,已過線且佩戴口罩的使用藍色框,畫面左上角實時顯示總的過線客流數量,若畫面中有未佩戴口罩的人會輸出告警。

3. 小結

通過本教程,我們學習了客流統計應用的擴充——過線客流統計+口罩佩戴識别,可用于商超、寫字樓入口安檢。

附錄:

1.基于頭肩部檢測的過線客流統計:https://bbs.huaweicloud.com/blogs/393389

2.注冊教程:https://developer.huaweicloud.com/develop/aigallery/article/detail?id=4702c210-092b-4e60-a3db-df9b4ec4e5f5

3.SDK安裝教程:https://developer.huaweicloud.com/develop/aigallery/article/detail?id=e314c856-d169-4f4b-a19d-94e4c70515cd

4.條件功能單元、展開/合并功能單元:https://developer.huaweicloud.com/develop/aigallery/article/detail?id=6922e4a3-831f-42fa-9705-4af1860d6d95

5.過線客流統計:https://developer.huaweicloud.com/develop/aigallery/article/detail?id=509aa76f-ad71-4e79-9692-b9708d0182b5

關注#華為雲開發者聯盟#,第一時間了解華為雲新鮮技術~

繼續閱讀