YOLOは、シンプルなCNNでオブジェクトのカテゴリとバウンディングボックスを検出することができるネットワークです。概要はChainerでYOLOからどうぞ。

YOLOの入力は448x448ピクセルの画像を、RRR...GGG...BBB...で並べて与えます。その際、入力画素のレンジは+-1.0に正規化する必要があるので、RGBが8bitの場合は、normalizedRGB=RGB/255.0f*2-1.0fとします。また、Bitmapの上下のスキャン順にも注意します。

対して、YOLOの出力は1470次元のベクトルであり、バウンディングボックスを計算するには、やや複雑な計算が必要です。

バウンディングボックスの計算方法はYOLOtiny_chainer/predict.pyのコードを参考にさせて頂きました。

YOLOの出力は、順に以下の項目で構成されます。

  probs=ans[0:980].reshape((7,7,20))     # class probabilities
  confs=ans[980:1078].reshape((7,7,2))   # confidence score for Bounding Boxes
  boxes = ans[1078:].reshape((7,7,2,4))  # Bounding Boxes positions (x,y,w,h)


probsは64x64ピクセルを1グリッドとして、画像を7x7個のグリッドに区切ったそれぞれで、以下の20カテゴリの推定確率が入っています。

  classes = ["aeroplane", "bicycle", "bird", "boat", "bottle",
             "bus", "car", "cat", "chair", "cow",
             "diningtable", "dog", "horse", "motorbike", "person",
             "pottedplant", "sheep", "sofa", "train","tvmonitor"]


confsには7x7個のグリッドにつき、2つのバウンディングボックスの信頼度が入っています。

boxesには7x7個のグリッドの2つのバウンディングボックスの座標が、(x,y,w,h)の順に入っています。wとhはsqrtがかかっています。そのため、有効なバウンディングボックスの座標は以下で計算することができます。

for(int by=0;by<7;by++){
  for(int bx=0;bx<7;bx++){
    for(int box=0;box<2;box++){
       for(int category=0;category<20;category++){
         if(confs[by][bx][box]*probs[by][bx][category]>0.1f){
           x= (bx+boxes[by][bx][box][0])*(448/7);
           y = (by+boxes[by][bx][box][1])*(448/7);
           w = pow(box[by][bx][box][2],2)*448;
           h = pow(box[by][bx][box][3],2)*448;
           x1 = x - w/2;
           x2 = x + w/2;
           y1 = y - h/2;
           y2 = y + h/2;
           class = classes[category];
        }
     }
  }
}


尚、このままでは、1つのオブジェクトに対して複数のバウンディングボックスが描画されてしまいます。この問題を解決するには、You Only Look Once:Unified, 統合されたリアルタイムオブジェクトの検出を参考に、計算したバウンディングボックスに対してNon-Maximum suppressionを適用する必要があります。Non-Maximum suppressionでは、クラスごとに推定確率の高い順にバウンディングボックスをソートし、iou関数でバウンディングボックスの重なりを検出、バウンディングボックスが重なっている場合は除外します。

    # suppress non-maximal boxes
    for c in range(nb_class):
        sorted_indices = list(reversed(np.argsort([box.classes[c] for box in boxes])))

        for i in range(len(sorted_indices)):
            index_i = sorted_indices[i]
            
            if boxes[index_i].classes[c] == 0: 
                continue
            else:
                for j in range(i+1, len(sorted_indices)):
                    index_j = sorted_indices[j]
                    
                    if bbox_iou(boxes[index_i], boxes[index_j]) >= nms_threshold:
                        boxes[index_j].classes[c] = 0

basic-yolo-kerasにおけるNon-Maximum suppressionの実装