摘要:本案例我們使用FairMOT進行車輛檢測與跟蹤、yolov5進行車牌檢測、crnn進行車牌識別,在停車場入口、出口、停車位對車輛進行跟蹤與車牌識別,無論停車場路線多複雜,小車在你掌控之中!
本文分享自華為雲社群《AI尋車》,作者:杜甫蓋房子。
本案例我們使用FairMOT進行車輛檢測與跟蹤、yolov5進行車牌檢測、crnn進行車牌識別,在停車場入口、出口、停車位對車輛進行跟蹤與車牌識別,無論停車場路線多複雜,小車在你掌控之中!最終效果如下:
我們使用ModelBox Windows SDK
進行開發,如果還沒有安裝SDK
,可以參考ModelBox端雲協同AI開發套件(Windows)裝置註冊篇、ModelBox端雲協同AI開發套件(Windows)SDK安裝篇完成裝置註冊與SDK
安裝。
技能開發
這個應用對應的ModelBox版本已經做成模板放在華為雲OBS中,可以用sdk中的solution.bat工具下載,接下來我們給出該應用在ModelBox中的完整開發過程:
1)下載模板
執行.\solution.bat -l可看到當前公開的技能模板:
PS ███> .\solution.bat -l
...
Solutions name:
mask_det_yolo3
...
vehicle_plate_multi_centernet_yolov5_crnn
結果中的vehicle_plate_multi_centernet_yolov5_crnn即為AI尋車應用模板,可使用如下命令下載模板:
PS ███> .\solution.bat -s vehicle_plate_multi_centernet_yolov5_crnn
...
solution.bat工具的引數中,-l 代表list,即列出當前已有的模板名稱;-s代表solution-name,即下載對應名稱的模板。下載下來的模板資源,將存放在ModelBox核心庫的solution目錄下。
2)建立工程
在ModelBox sdk目錄下使用create.bat建立vehicle_plate工程:
PS ███> .\create.bat -t server -n vehicle_plate -s vehicle_plate_multi_centernet_yolov5_crnn sdk version is modelbox-xxx success: create vehicle_plate in ███\modelbox\workspace
create.bat工具的引數中,-t 表示建立事務的類別,包括工程(server)、Python功能單元(Python)、推理功能單元(infer)等;-n 代表name,即建立事務的名稱;-s 代表solution-name,表示將使用後面引數值代表的模板建立工程,而不是建立空的工程。
workspace目錄下將建立出vehicle_plate工程,工程內容如下所示:
vehicle_plate |--bin │ |--main.bat:應用執行入口 │ |--mock_task.toml:應用在本地執行時的輸入輸出配置,此應用預設使用4路本地影片檔案為輸入源,最終結果拼接為四宮格輸出到螢幕,可根據需要修改 |--CMake:存放一些自定義CMake函式 |--data:存放應用執行所需要的圖片、影片、文字、配置等資料 │ |--chuchang_10.mp4:停車場出口測試影片 │ |--ruchang_10.mp4:停車場入口測試影片 │ |--ruku_10.mp4:停車位1測試影片 │ |--kong_10.mp4:停車位2測試影片 │ |--plate_keys.txt:車牌字元檔案 │ |--content_file.json:技能引數全域性配置檔案 |--dependence │ |--modelbox_requirements.txt:應用執行依賴的外部庫在此檔案定義,本應用依賴pillow、scipy等工具包 |--etc │ |--flowunit:應用所需的功能單元存放在此目錄 │ │ |--cpp:存放C++功能單元編譯後的動態連結庫,此應用沒有C++功能單元 │ │ |--collapse_ocr:歸攏功能單元,車牌識別後處理 │ │ |--condition:條件功能單元,判斷是否檢測到車輛/車牌 │ │ |--draw_full_screen:多路影片拼接輸出功能單元 │ │ |--draw_plate:車牌檢測結果繪製 │ │ |--draw_track_bbox:車輛跟蹤結果繪製 │ │ |--expand_image:展開功能單元,展開車輛檢測/車牌檢測結果並行推理 │ │ |--letter_resize:車輛檢測預處理功能單元 │ │ |--object_tracker:跟蹤功能單元 │ │ |--plate_det_post:車牌檢測後處理功能單元 │ │ |--url_cfg:流單元,多路輸入解析 │ │ |--vehicle_det_post:車牌檢測結果繪製 |--flowunit_cpp:存放C++功能單元的原始碼,此應用沒有C++功能單元 |--graph:存放流程圖 │ |--vehicle_plate.toml:預設流程圖,使用本地影片檔案作為輸入源 │ |--modelbox.conf:modelbox相關配置 |--hilens_data_dir:存放應用輸出的結果檔案、日誌、效能統計資訊 |--model:推理功能單元目錄 │ |--vehicle_det:車輛檢測推理功能單元 │ │ |--vehicle_det.toml:車輛檢測推理功能單元的配置檔案 │ │ |--vehicle_det_320x576.onnx:車輛檢測onnx模型 │ |--plate_det:車牌檢測推理功能單元 │ │ |--plate_det.toml:車牌檢測推理功能單元的配置檔案 │ │ |--plate_det.onnx:車牌檢測onnx模型 │ |--plate_rec:車牌識別推理功能單元 │ │ |--plate_rec.toml:車牌識別推理功能單元的配置檔案 │ │ |--plate_rec.onnx:車牌識別onnx模型 |--build_project.sh:應用構建指令碼 |--CMakeLists.txt |--rpm:打包rpm時生成的目錄,將存放rpm包所需資料 |--rpm_copyothers.sh:rpm打包時的輔助指令碼
3)檢視流程圖
vehicle_plate工程graph目錄下存放流程圖,預設的流程圖vehicle_plate.toml與工程同名,其內容為(以Windows版ModelBox為例):
# 功能單元的掃描路徑,包含在[]中,多個路徑使用,分隔 # ${HILENS_APP_ROOT} 表示當前應用的實際路徑 # ${HILENS_MB_SDK_PATH} 表示ModelBox核心庫的實際路徑 [driver] 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資訊的儲存位置 [graph] format = "graphviz" # 流程圖的格式,當前僅支援graphviz graphconf = """digraph vehicle_plate{ node [shape=Mrecord] queue_size = 1 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] url_cfg[type=flowunit, flowunit=url_cfg, 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"] letter_resize[type=flowunit, flowunit=letter_resize, device=cpu] 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.003921568627451, 0.003921568627451, 0.003921568627451"] vehicle_det[type=flowunit, flowunit=vehicle_det, device=cpu, deviceid=0, batch_size=1] vehicle_det_post[type=flowunit, flowunit=vehicle_det_post, device=cpu, deviceid=0] object_tracker[type=flowunit, flowunit=object_tracker, device=cpu, deviceid=0] vehicle_condition[type=flowunit, flowunit=condition, device=cpu, deviceid=0] expand_car[type=flowunit, flowunit=expand_image, device=cpu, deviceid=0, img_h=640, img_w=640] plate_color_transpose[type=flowunit flowunit=packed_planar_transpose device=cpu deviceid="0"] plate_normalize[type=flowunit flowunit=normalize device=cpu deviceid=0 standard_deviation_inverse="0.003921568627451,0.003921568627451,0.003921568627451"] plate_det[type=flowunit flowunit=plate_det device=cpu deviceid="0", batch_size=1] plate_det_post[type=flowunit, flowunit=plate_det_post, device=cpu, deviceid=0] plate_condition[type=flowunit, flowunit=condition, device=cpu, deviceid=0, key="plate"] expand_plate[type=flowunit, flowunit=expand_image, device=cpu, deviceid=0, img_h=48, img_w=168, key="plate"] ocr_color_transpose[type=flowunit flowunit=packed_planar_transpose device=cpu deviceid="0"] ocr_mean[type=flowunit flowunit=mean device=cpu deviceid="0" mean="149.94,149.94,149.94"] ocr_normalize[type=flowunit flowunit=normalize device=cpu deviceid=0 standard_deviation_inverse="0.020319,0.020319,0.020319"] plate_rec[type=flowunit flowunit=plate_rec device=cpu deviceid="0", batch_size=1] collapse_ocr[type=flowunit flowunit=collapse_ocr device=cpu deviceid="0"] draw_plate[type=flowunit, flowunit=draw_plate, device=cpu, deviceid=0] draw_track_bbox[type=flowunit, flowunit=draw_track_bbox, device=cpu, deviceid=0] draw_full_screen[type=flowunit, flowunit=draw_full_screen, device=cpu, deviceid=0] video_out[type=flowunit, flowunit=video_out, device=cpu, deviceid=0, full_screen=true] input1:input -> data_source_parser:in_data data_source_parser:out_video_url -> url_cfg:in_1 url_cfg:out_1 -> video_demuxer:in_video_url video_demuxer:out_video_packet -> video_decoder:in_video_packet video_decoder:out_video_frame -> letter_resize:in_image letter_resize:resized_image -> color_transpose:in_image color_transpose:out_image -> normalize:in_data normalize:out_data -> vehicle_det:input vehicle_det:output -> vehicle_det_post:in_feat letter_resize:out_image -> vehicle_det_post:in_image vehicle_det_post:out_feat -> object_tracker:in_feat object_tracker:out_track -> vehicle_condition:in_track video_decoder:out_video_frame -> vehicle_condition:in_image vehicle_condition:out_track -> expand_car:in_image expand_car:out_image -> plate_color_transpose:in_image plate_color_transpose:out_image -> plate_normalize:in_data plate_normalize:out_data -> plate_det:input plate_det:output -> plate_det_post:in_feat expand_car:out_image -> plate_det_post:in_image plate_det_post:out_tracks -> plate_condition: in_track vehicle_condition:out_track -> plate_condition: in_image plate_condition:out_track -> expand_plate:in_image expand_plate:out_image -> ocr_color_transpose:in_image ocr_color_transpose:out_image -> ocr_mean:in_data ocr_mean:out_data -> ocr_normalize:in_data ocr_normalize:out_data -> plate_rec:input plate_rec:output -> collapse_ocr:in_feat expand_plate:out_image -> collapse_ocr:in_image collapse_ocr:out_tracks -> draw_plate:in_feat plate_condition:out_track -> draw_plate:in_image draw_plate:out_image -> draw_track_bbox:in_image plate_condition:out_image -> draw_track_bbox:in_image draw_track_bbox:out_image -> draw_full_screen:in_image vehicle_condition:out_image -> draw_full_screen:in_image draw_full_screen:out_image -> video_out:in_video_frame }""" [flow] desc = "vehicle_plate run in modelbox-win10-x64"
將流程圖視覺化:
圖示中,灰色部分為預置功能單元,其餘顏色為我們實現的功能單元,其中綠色為一般通用功能單元,紅色為推理功能單元,藍色為條件功能單元,黃色為展開歸攏功能單元。整個應用邏輯相對複雜一些,影片解碼後做影像預處理,接著是車輛檢測,模型後處理得到車形框與128維車輛reid特徵,送入跟蹤演算法進行實時跟蹤,經過條件功能單元判斷,檢測到車輛的圖送入展開功能單元,切圖進行車牌檢測,車牌檢測結果歸攏後同樣要判斷是否檢測到車牌,檢測到車牌的幀再展開並行進行車牌識別,未檢測到的則直接繪製車輛資訊。而未檢測到車輛的幀則直接送入多路拼接功能單元,最終輸出。
4)核心邏輯
本應用核心邏輯中的跟蹤與區域判斷參照客流統計實戰營的應用設計,跟蹤邏輯在object_tracker功能單元中,檢測與跟蹤使用的是FairMOT演算法,演算法介紹可參考論文。
首先檢視object_tracker功能單元中返回的跟蹤物件結構:
def get_tracking_objects(self, online_targets_dict): tracking_objects = {} for cls_id in range(self.num_classes): online_targets = online_targets_dict[cls_id] for t in online_targets: obj = {} tlwh = t.tlwh if tlwh[2] * tlwh[3] < self.min_box_area: continue tid = t.track_id obj["bbox"] = [max(0, tlwh[0]), max(0, tlwh[1]), tlwh[0] + tlwh[2], tlwh[1] + tlwh[3]] obj["licence"] = "" obj["licence_score"] = 0.0 obj["plate"] = np.zeros((4, 2)).tolist() obj["plate_score"] = 0.0 obj["bbox_score"] = t.score tracking_objects[tid] = obj return tracking_objects
可以看到,我們返回的跟蹤物件包括車型框、車輛檢測得分等已有資訊以及車牌、車牌得分、車牌框、車牌框得分等包含預設資料的佔位資訊,方便後續功能單元獲取更新。
從流程圖中可以看到,object_tracker後結果送入車輛檢測條件功能單元,同樣的在流程圖中還包含車牌檢測條件功能單元,我們當然是希望使用同一個功能單元完成兩個判斷,所以在條件功能單元condition中,我們配置了引數key,預設為key = "bbox",即對結構體中的車型框進行判斷,具體實現為:
if track_result and np.any([v.get(self.key) for k, v in track_result.items()]): buffer_img.set("track", track_json) out_track.push_back(buffer_img) else: buffer_img.set("track", track_json) out_image.push_back(buffer_img)
這樣的話如果是對車型框進行判斷,只需要在流程圖中配置key = “plate”即可。
同樣的,圖展開功能單元expand_image也使用了同樣的方法,使車輛檢測與車牌檢測可以共用功能單元:
tracking_objects = json.loads(buffer_img.get("track")) for idx, target in tracking_objects.items(): box = np.array(target.get(self.key)) ...
此外,由於本應用輸入為4路影片,因此需要在url_cfg單元中進行session級別資訊配置:
url_str = str(self.count) + input_meta.get_private_string("source_url") self.count += 1 data_context.get_session_context().set_private_string("multi_source_url", url_str)
session級別的資訊在功能單元之間是同步的,這樣就可以在後續的功能單元中獲取當前輸入為哪路輸入:
url = data_context.get_session_context().get_private_string("multi_source_url") image_index = int(url[0])
同樣的,對於多路輸入,我們需要在本地mock時在bin/mock_task.toml檔案中進行輸入配置:
[input] type = "url" url = "${HILENS_APP_ROOT}/data/ruchang_10.mp4" [input1] type = "url" url = "${HILENS_APP_ROOT}/data/chuchang_10.mp4" [input2] type = "url" url = "${HILENS_APP_ROOT}/data/ruku_10.mp4" [input3] type = "url" url = "${HILENS_APP_ROOT}/data/kong_10.mp4"
對於多路輸入的感興趣區域劃定,我們使用content_file配置:
[common] content_file = "../data/content_file.json"
配置檔案內容為:
[ { "vehicle_area": "190,245,382,656,1265,630,956,249", "plate_area": "190,245,382,656,1265,630,956,249" }, { "vehicle_area": "663,467,228,675,994,682,1167,459", "plate_area": "663,467,228,675,994,682,1167,459" }, { "vehicle_area": "0,0,1280,0,1280,720,0,720", "plate_area": "0,0,1280,0,1280,720,0,720" }, { "vehicle_area": "0,0,1280,0,1280,720,0,720", "plate_area": "0,0,1280,0,1280,720,0,720" } ]
即針對不同輸入配置各自的車型車牌感興趣區域,在後續功能單元中獲取配置的引數資訊進行處理,如plate_det_post功能單元:
self.areas = json.loads(data_context.get_session_config().get_string("iva_task_common")) url = data_context.get_session_context().get_private_string("multi_source_url") image_index = int(url[0]) self.area = self.areas[image_index].get("plate_area") if self.area: self.area = np.array(list(map(int, self.area.split(",")))).reshape(-1, 1, 2).astype(np.int32)
我們目前對於車型和車牌檢測的引數配置是保持一致的,也可以配置為不同引數。
5)三方依賴庫
本應用依賴scipy等工具包,ModelBox應用不需要手動安裝三方依賴庫,只需要配置在dependence\modelbox_requirements.txt,應用在編譯時會自動安裝。
6)用啟動指令碼執行應用
在專案目錄下執行.\bin\main.bat執行應用:
PS ███> .\bin\main.bat
...
可以看到螢幕出現技能畫面:
白線即配置的感興趣區域,區域外/未過線車輛根據id賦色,區域內/已過線車輛的使用灰色框,可在輸入輸出配置中修改劃區域任務型別與座標點。
7)技能除錯
我們1.5.0版本SDK提供了debug工具,在VSCode中開啟專案根目錄,在建立專案時已經自動生成了除錯配置檔案:
如果有其他安裝包,可以在配置檔案中增加PYTHONPATH引數。
在除錯時,可以直接在感興趣的程式碼處打斷點除錯即可:
啟動除錯後與其他程式除錯操作一致: