物体検出ライブラリ「Detectron2」を使うと比較的簡単に物体検出処理を実装することができます。ここでは、私がお尻発見機を開発しながら作成したプログラムの概要をご紹介していきましょう。
まずは実際に物体検出処理を行いインスタンスセグメンテーションの領域データを作成するサーバー側プログラムです。
お尻発見機では、画像をサーバーに送信し「お尻と胸があるピクセル領域」のデータを取得して、そのデータをもとに色付けを行っています。
Webページで画像を選択して「送信」ボタンをクリックすると、Base64形式の文字列データとしてサーバー(PythonのCGIプログラム)に画像が送られます。
サーバーでは、まず送られてきたBase64形式の文字列からOpenCVで画像を作成します。その画像に対して、Detectron2のインスタンスセグメンテーション機能による物体検出処理を行う形にしました。
Base64文字列image_dataからOpencvの画像imgを作るPythonのコードは、以下のようになります。
img_buf = base64.b64decode(image_data)
img_bin = numpy.frombuffer(img_buf, dtype=numpy.uint8)
img = cv2.imdecode(img_bin, cv2.IMREAD_COLOR)
画像が用意出来たら、別に作成しておいたお尻と胸の学習データを指定して物体検出を行うためDetectron2のDefaultPredictorを作成します。
cfg = get_cfg()
cfg.MODEL.DEVICE='cpu'
cfg.merge_from_file("./detectron2/configs/COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml")
cfg.DATALOADER.NUM_WORKERS = 2
cfg.MODEL.WEIGHTS = "detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x/137849600/model_final_f10217.pkl"
cfg.MODEL.WEIGHTS = お尻と胸の学習データのパス
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.5
cfg.MODEL.ROI_HEADS.NUM_CLASSES = 2
predictor = DefaultPredictor(cfg)
続いて、DefaultPredictorとimgを物体検出結果をJSON形式で作成する関数createDetectedObjestsJson()に渡します。
実際にDetectron2(DefaultPredictor)による物体検出を行っているのは、以下の部分です。
def detectImage(predictor, img):
output = predictor(img)
instances = output["instances"].to("cpu")
return createDetectedObjectList(instances)
def createDetectedObjestsJson(predictor, img):
detected_objects_list = detectImage(predictor, img)
・・・
関数detectImage()内のpredictor()でDetectron2による物体検出データoutputが作成され、以降はそのoutputから情報を取得していくわけですね。ただし、Detectron2の出力は通常のプログラム(CPU処理)で扱いにくい形式になっているので、to("cpu")で扱いやすい形に変換しておきます。
Detectron2の処理結果から作成したinstancesには、インスタンスセグメンテーションを行って検出された物体の情報が格納されています。
検出された物体の種類(今回はお尻と胸の2種類)はpred_classesに、物体が存在するピクセル領域の情報はpred_masksに……といった感じで分かれて格納されているので、検出された物体の数だけ処理を行って各物体の情報をまとめます。
このうちpred_masksは、画像全体のマスク(画像の幅×高さ分のTrue/False配列)になっているので、まず物体が存在する領域の範囲を算出してその範囲分のマスクデータを作成するようにしました。
def createDetectedObjectList(instances):
instance_num = len(instances.scores)
if instance_num == 0:
return None
object_list = []
scores = instances.scores
obj_classes = instances.pred_classes
pred_masks = instances.pred_masks
pred_masks = numpy.asarray(pred_masks)
for i in range(instance_num):
obj = DetectedObject()
obj.setScore(scores[i].item())
obj.setClass(obj_classes[i].item())
obj.setPredMask(pred_masks[i])
object_list.append(obj)
return object_list
マスクデータの作成処理は、プログラム先頭で定義しているDetectedObjectクラスで実装していますので、コードを参照してください。
最終的には、検出された物体の種類、範囲、ピクセル単位の領域情報をインスタンス毎にJSONにまとめて出力しています。
今回作成したお尻発見機では、Detectron2に用意されているモデルに機械学習させたお尻と胸のデータを追加する転移学習を行っています。このモデルの選択でも結果が違ってくるのが面白いところですね。