作者:曾成訓(CH.Tseng)
利用 COCO dataset 所抓取的 person 物件,雖然可以得到數量非常多的標記圖片(總計有 45,174 標記檔),但若打算應用在人物計算和追蹤上,其實不太合用,最主要的原因在於 COCO 針對多人群聚的標記方式有問題,例如下方這張相片:

(圖片來源:曾成訓提供)
後方的一排球員,並不是每一個都有標識,此外還有一個大標記來註明這一區也是「person」,這種作法會造成我們在計算或需要明確的個人形體識別時造成困擾,因此我們需要找一個對於 person 有明確且嚴謹標識 dataset。
目前最為完整適用於群聚 person 檢測的 dataset 有兩個:CityPersons 及 CrowdHuman,都是由對岸中國開源釋出的,這類的資料集最大的用途在於行人檢測、跟蹤和檢索等。
- 行人檢測:將影片或相片中的所有行人框選出來
- 行人跟蹤:將影片中不同行人的軌跡串連起來,進而識別個體及行進方向
- 行人檢索:將感興趣的人物從影片中檢索出來
CityPersons
由南京理工大學的張姍姍教授於 2017 年發表,請見相關論文,如果您有興趣,下面是該 dataset 發表時的介紹影片,由張姍姍教授主講。
- dataset 影像來源:
Cityscapes資料集(攝於德國18個城市)
- 圖片及標記的行人數目:
- Train dataset: 2975張圖片/ 19,654個行人
- Val. dataset: 500張圖片/ 3,938個行人
- Test dataset: 1575張圖片 / 11,424個行人
- Classes(標記 labels):
- pedestrian(walking,running,standing up)
- rider(riding bicycles or motorbikes)
- sitting person
- other person(非正常姿勢)
- Dataset download:
- 標記檔及 trained models
- 圖片檔需另外從 Cityscapes 網站申請下載
Cityscapes 的 dataset 必須透過線上申請才能下載,我申請了登入帳號並已啟用,但狀態一直停留在等待管理員核可的階段,因此無法下載此 dataset。
CrownHuman
這是由中國最知名的 AI 獨角獸公司矌視科技(Face++)所釋出的資料集,圖片及標記數目比起 CityPersons 更多更完整。以下為各 dataset 的比較,可見 CrowdHuman 是目前最適合用於訓練行人檢測的資料集。

(圖片來源:曾成訓提供)
從下這張圖,可看出 CrowdHuman 驚人的檢測效果,上方由左至右的綠黃藍框,分別為 COCO、Caltech、CityPersons 所訓練出的識別結果,下方紅框則為 CrowdHuman 的識別效果。

(圖片來源:曾成訓提供)
另外,CityPersons 可直接經由官方網站下載,不需申請。
CrowdHuman 標記格式
針對同一個 human 提供如下三種 BBox:
- Head BBox:針對頭部的標記
- Visible BBox:影像中的身體可見區域的標記
- Full BBOX:完整身體區域,看不見區域為預測

(圖片來源:曾成訓提供)

(圖片來源:曾成訓提供)
由 CrowdPerson 提供的這三種標記,我們除了能精確地標識出影像中的所有人,也能得到該人被遮蔽的程度。
標記檔格式
Train 與 Validation 的標記資訊分別存放於 annotation_train.odgt 以及 annotation_val.odgt 兩個檔案,odgt 是一種標注檔案的格式,每一行為一獨立的 json 格式文字,代表一張影像檔的標記內容,因此 dataset 有多少張相片,該 odgt 檔便有幾行,以其中一行為例:
這串文字的格式可整理成:

(圖片來源:曾成訓提供)
- ID:圖片檔案名稱
- gtboxes:所有的 BBox list
- tag:用來 label 名稱,共兩種:person 及 mask;mask 是指:crowd、reflection、something like person 這類的情況,一般我們只需要取出 person 即可
- hbox:頭部的 BBox(Head)
- head_attr:頭部的一些屬性
- fbox:全身的 BBox(Full body)
- vbox:沒有被遮蔽的 BBox(Visible body)
- extra:BBox 的額外屬性
以上持續重複 tag、hbox、extra,視影像中有多少人而定。
撰寫擷取物件的程式
如果只想抓取 head 部份,則 target_class 內容請設定成 target_class = [ “head” ],取出的 class 名稱計有 person_head 和 mask_head 兩種,這是因為 CrowdHuman 的 gtboxes 標記當中的 tag 有兩種內容:person 及 mask,代表影像中的是真人或假人倒影。
如果想抓取 full body 及 visible body,那麼 target_clas 內容要設定成 target_class = [ “fbody”, “vbody” ],這樣取出的 class 將會有四種:person_fbox、person_vbox、mask_fbox 及 mask_vbox。
要抓取全部(head、full body、visible body)的話,則 target_class 內容設為空值即可:target_class = [],這樣取出的 class 將會有六種:person_head、person_fbox、person_vbox、mask_head、mask_fbox 及 mask_vbox。
執行後,產生的 dataset 可以用 labelImg 開啟,而且已經轉換為 PASCAL VOC 標記格式,會發現影像中每一位人物皆予以鉅細靡遺的標記,有的相片標記人數甚至高達數十或上百個,可見 CrowdHuman dataset 所投入的人力與時間真的不在話下。

(圖片來源:曾成訓提供)

(圖片來源:曾成訓提供)

(圖片來源:曾成訓提供)

(圖片來源:曾成訓提供)

(圖片來源:曾成訓提供)
參考程式
import json import cv2 import imutils import time import os, glob target_class = [] #["head", "fbody", "vbody", "mask"] #[] --> all #target_class = [] annotations_path = "/WORK1/dataset/crowdHuman/annotation_train.odgt" crowdHuman_path = "/WORK1/dataset/crowdHuman/Images" xml_file = "xml_file.txt" object_xml_file = "xml_object.txt" #output datasetPath = "/DATA1/Datasets_mine/labeled/crowd_human_dataset" imgPath = "images/" labelPath = "labels/" imgType = "jpg" # jpg, png def check_env(): if not os.path.exists(os.path.join(datasetPath, imgPath)): os.makedirs(os.path.join(datasetPath, imgPath)) if not os.path.exists(os.path.join(datasetPath, labelPath)): os.makedirs(os.path.join(datasetPath, labelPath)) def writeObjects(label, bbox): with open(object_xml_file) as file: file_content = file.read() file_updated = file_content.replace("{NAME}", label) file_updated = file_updated.replace("{XMIN}", str(bbox[0])) file_updated = file_updated.replace("{YMIN}", str(bbox[1])) file_updated = file_updated.replace("{XMAX}", str(bbox[0] + bbox[2])) file_updated = file_updated.replace("{YMAX}", str(bbox[1] + bbox[3])) return file_updated def generateXML(imgfile, filename, fullpath, bboxes, imgfilename): xmlObject = "" for (labelName, bbox) in bboxes: xmlObject = xmlObject + writeObjects(labelName, bbox) with open(xml_file) as file: xmlfile = file.read() img = cv2.imread(imgfile) cv2.imwrite(os.path.join(datasetPath, imgPath, imgfilename), img) (h, w, ch) = img.shape xmlfile = xmlfile.replace( "{WIDTH}", str(w) ) xmlfile = xmlfile.replace( "{HEIGHT}", str(h) ) xmlfile = xmlfile.replace( "{FILENAME}", filename ) xmlfile = xmlfile.replace( "{PATH}", fullpath + filename ) xmlfile = xmlfile.replace( "{OBJECTS}", xmlObject ) return xmlfile def makeLabelFile(filename, bboxes, imgfile): jpgFilename = filename + "." + imgType xmlFilename = filename + ".xml" xmlContent = generateXML(imgfile, xmlFilename, os.path.join(datasetPath ,labelPath, xmlFilename), bboxes, jpgFilename) file = open(os.path.join(datasetPath, labelPath, xmlFilename), "w") file.write(xmlContent) file.close if __name__ == "__main__": check_env() img_filename = {} img_bboxes = {} f = open(annotations_path) lines = f.readlines() total_lines = len(lines) for lineID, line in enumerate(lines): data = eval(line) img_id = data["ID"] total_iid = len(data["gtboxes"]) for iid, infoBody in enumerate(data["gtboxes"]): tag = infoBody["tag"] hbbox = infoBody["hbox"] head_attr = infoBody["head_attr"] fbbox = infoBody["fbox"] vbbox = infoBody["vbox"] extra = infoBody["extra"] if(len(target_class)==0 or ("head" in target_class)): if(img_id in img_bboxes): last_bbox_data = img_bboxes[img_id] last_bbox_data.append((tag+"_head", hbbox)) img_bboxes.update( {img_id:last_bbox_data} ) else: img_bboxes.update( {img_id:[(tag+"_head", hbbox)]} ) if(len(target_class)==0 or ("fbody" in target_class)): if(img_id in img_bboxes): last_bbox_data = img_bboxes[img_id] last_bbox_data.append((tag+"_fbox", fbbox)) img_bboxes.update( {img_id:last_bbox_data} ) else: img_bboxes.update( {img_id:[(tag+"_fbox", fbbox)]} ) if(len(target_class)==0 or ("vbody" in target_class)): if(img_id in img_bboxes): last_bbox_data = img_bboxes[img_id] last_bbox_data.append((tag+"_vbox", vbbox)) img_bboxes.update( {img_id:last_bbox_data} ) else: img_bboxes.update( {img_id:[(tag+"_vbox", vbbox)]} ) filename = img_id + ".jpg" img_filename.update( { filename:img_id } ) print("[Left over] {}/{} , {}: head:{} full:{} visible:{}".format(total_lines-lineID, total_iid-iid, img_id,\ hbbox, fbbox, vbbox)) for file in os.listdir(crowdHuman_path): file_name, file_extension = os.path.splitext(file) if(file in img_filename): bbox_objects = {} if(file in img_filename): img_id = img_filename[file] if(img_id in img_bboxes): bboxes = img_bboxes[img_id] makeLabelFile(file_name, bboxes, os.path.join(crowdHuman_path, file))
(本文經作者同意轉載自 CH.TSENG 部落格、原文連結;責任編輯:賴佩萱)
- 【模型訓練】訓練馬賽克消除器 - 2020/04/27
- 【AI模型訓練】真假分不清!訓練假臉產生器 - 2020/04/13
- 【AI防疫DIY】臉部辨識+口罩偵測+紅外線測溫 - 2020/03/23
訂閱MakerPRO知識充電報
與40000位開發者一同掌握科技創新的技術資訊!