티스토리 뷰

MLDL/Theory

MaskRCNN

Lazyer 2018. 6. 27. 18:15

Mask RCNN

@by lazyerIJ _ 18.06.27


 
 

 

페이스북에서 공개한 Image Masking 알고리즘. Object classification을 pixel단위로 수행하는 것이 특징이다. Faster RCNN과 크게 다르지 않기 때문에, 기본 로직은 제외하고 Faster RCNN과 다른점을 위주로 정리하였다.
먼저, Faster RCNN은 classification과 bounded-box regression을 함께 학습한다. rpn을 통하여 ROI영역을 추출하고 classification을 진행하기 때문에  2개의 Loss를 사용하게된다.

 

Mask RCNN은 3개의 Loss값을 사용하는데, 가 추가되어 가 된다.
Mask RCNN의 흐름은 'ROI 탐색 -> Classification(Faster RCNN)' -> 'Binary classification' 인데, Binary classification은 Object인지 아닌지를 분류한다. ROI영역에서 Classification된 Label이 Person이라면, Mask RCNN은

을 이용하여 해당 마스크게 속하는 Person Object인지, 아닌지를 Pixel단위로 분류한다.

Classification, Bounded-box Regression은 Faster-RCNN의 로직에서 담당한다.
Mask의 역할은 주어진 Mask에 대하여, Mask에 속하는 pixel들을 binary 분류를 할 뿐, 어느 객체인지 판별에 대한 것에는 관여하지 않는다는 것을 기억하자. 그리고 이것을 수행하는 것이 FCN(Fully Convolution Network) 이다.

 

Classification의 기능을 Faster-RCNN에서 수행하기 때문에 FCN의 구현에 있어 softmax는 필요없으며, Sigmoid와 같은 Activation Function으로 구성되어, Intput인 Convolution Featuremap으로부터의 Output은 1과 0으로 이루어져있으며 1일 경우 해당 객체에 속한 픽셀, 0일 경우 아닌 것으로 분류된다.
Convolution과 함께 Pooling을 수행함에 있어 Faster RCNN에서의 ROI pooling은 픽셀단위의 Entropy 손실이 발생하였다.
  • ROI Polling

     

     

ROI Pooling의 Region Proposal Network로 인해 실수값의 bounded-box가 예측되는데, 이러한
실수값을 정수값 픽셀로 근사시킬 때 에 손실이 발생한다. 다음으로 5x5 Input Feature을 2x2 Output Feature로 Pooling할 때에, pooling size를 2,3 크기로 각기 다르게 Pooling하게 되어 또 손실이 발생한다.
  • ROI Align

     

    ROI Align은 RPN은 그대로 이용하고 Pooling단계만 변형한 것인데, 별도의 네트워크적 구조가 있는 것이 아니라, 선형보간을 이용하였다. 실수값으로 ROI영역이 예측되었을 때, 정수 값으로 근사시키지 않고 해당되는 픽셀의 값을 모두 활용한다. 픽셀을 버렸던 모든 상황에서 선형보간을 이용하여 Max pooling을 실시한다.
    ROI Align에 사용되는 Input Feature들은 Conv-net의 최종 Output이 아니라, Conv-net의 각 단계별 Feature map을 이용한다.
종합하면 로직은 다음과 같다.

 

 

ROI Align의 Output을 Mask로 Binary classification을 하게 되고, 예측 값을 원래 이미지에 씌워 픽셀단위로 객체를 나눈다.

github.com/matterport/Mask_RCNN에 있는 python코드에서 위 로직을 찾아보면 다음과 같다. (자세히 보기에는 코드가 너무 길다..)
  1. Mask RCNN
class MaskRCNN():
...
if callable(config.BACKBONE):
_, C2, C3, C4, C5 = config.BACKBONE(input_image, stage5=True,
train_bn=config.TRAIN_BN)
else:
_, C2, C3, C4, C5 = resnet_graph(input_image, config.BACKBONE,
stage5=True, train_bn=config.TRAIN_BN)
# Top-down Layers
# TODO: add assert to varify feature map sizes match what's in config
P5 = KL.Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (1, 1), name='fpn_c5p5')(C5)
P4 = KL.Add(name="fpn_p4add")([
KL.UpSampling2D(size=(2, 2), name="fpn_p5upsampled")(P5),
KL.Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (1, 1), name='fpn_c4p4')(C4)])
P3 = KL.Add(name="fpn_p3add")([
KL.UpSampling2D(size=(2, 2), name="fpn_p4upsampled")(P4),
KL.Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (1, 1), name='fpn_c3p3')(C3)])
P2 = KL.Add(name="fpn_p2add")([
KL.UpSampling2D(size=(2, 2), name="fpn_p3upsampled")(P3),
KL.Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (1, 1), name='fpn_c2p2')(C2)])
# Attach 3x3 conv to all P layers to get the final feature maps.
P2 = KL.Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (3, 3), padding="SAME", name="fpn_p2")(P2)
P3 = KL.Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (3, 3), padding="SAME", name="fpn_p3")(P3)
P4 = KL.Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (3, 3), padding="SAME", name="fpn_p4")(P4)
P5 = KL.Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (3, 3), padding="SAME", name="fpn_p5")(P5)
# P6 is used for the 5th anchor scale in RPN. Generated by
# subsampling from P5 with stride of 2.
P6 = KL.MaxPooling2D(pool_size=(1, 1), strides=2, name="fpn_p6")(P5)
 
# Note that P6 is used in RPN, but not in the classifier heads.
rpn_feature_maps = [P2, P3, P4, P5, P6]
mrcnn_feature_maps = [P2, P3, P4, P5]
...
 
Model에 속하는 MaskRCNN 클래스의 내부 로직 중 일부이다. resnet_graph에서 C2~C5까지 4개의 Output이 나온 것을 볼 수 있다. P2 ~ P5를 보면 Upsampling 과 conv2d(FPN) 을 함께 쓴 것을 볼 수 있는데 Upsampling은 이전 단계 Output을, Conv2d는 resnet_graph의 단계별 Output을 함께 쓴 것을 볼 수 있다.
  1. Loss Function
...
mrcnn_class_logits, mrcnn_class, mrcnn_bbox =\
fpn_classifier_graph(rois, mrcnn_feature_maps, input_image_meta,
config.POOL_SIZE, config.NUM_CLASSES,
train_bn=config.TRAIN_BN,
fc_layers_size=config.FPN_CLASSIF_FC_LAYERS_SIZE)
 
mrcnn_mask = build_fpn_mask_graph(rois, mrcnn_feature_maps,
input_image_meta,
config.MASK_POOL_SIZE,
config.NUM_CLASSES,
train_bn=config.TRAIN_BN)
 
# TODO: clean up (use tf.identify if necessary)
output_rois = KL.Lambda(lambda x: x * 1, name="output_rois")(rois)
 
# Losses
rpn_class_loss = KL.Lambda(lambda x: rpn_class_loss_graph(*x), name="rpn_class_loss")(
[input_rpn_match, rpn_class_logits])
 
rpn_bbox_loss = KL.Lambda(lambda x: rpn_bbox_loss_graph(config, *x), name="rpn_bbox_loss")(
[input_rpn_bbox, input_rpn_match, rpn_bbox])
 
class_loss = KL.Lambda(lambda x: mrcnn_class_loss_graph(*x), name="mrcnn_class_loss")(
[target_class_ids, mrcnn_class_logits, active_class_ids])
 
bbox_loss = KL.Lambda(lambda x: mrcnn_bbox_loss_graph(*x), name="mrcnn_bbox_loss")(
[target_bbox, target_class_ids, mrcnn_bbox])
 
mask_loss = KL.Lambda(lambda x: mrcnn_mask_loss_graph(*x), name="mrcnn_mask_loss")(
[target_mask, target_class_ids, mrcnn_mask])
...
1번에서 구한 feature_maps들을 통하여 mrcnn_mask값이 계산되고 mask_loss 계산에 이용되는 것을 볼 수 있다.
3 ROI Align
class PyramidROIAlign(KE.Layer):
....
image_shape = parse_image_meta_graph(image_meta)['image_shape'][0]
# Equation 1 in the Feature Pyramid Networks paper. Account for
# the fact that our coordinates are normalized here.
# e.g. a 224x224 ROI (in pixels) maps to P4
image_area = tf.cast(image_shape[0] * image_shape[1], tf.float32)
roi_level = log2_graph(tf.sqrt(h * w) / (224.0 / tf.sqrt(image_area)))
roi_level = tf.minimum(5, tf.maximum( 2, 4 + tf.cast(tf.round(roi_level), tf.int32)))
roi_level = tf.squeeze(roi_level, 2)
...
input image의 shape에 따라 roi_level이 계산하고 선형보간에 이용한다.

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함